ci(goreleaser): verify adopters can fetch the asset a cut publishes (#648) #729
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!729
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/648-exercise-the-fetch-arm"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
A tag-triggered job,
needs: goreleaser, that bootstrapsrtthrough the composite action at thetag just published, executes the fetched binary, and measures the cache-hit path.
🔑 What was actually missing — not a check, a trigger
The tracker asks for a job that exercises the FETCH arm. One already exists.
The tracker lists "whether any release-toolkit CI job exercises
fetch-rt.shby a path I did notfind" under what I have NOT established. Established: yes, and it is unreachable.
So the fix is a trigger, not a new job.
composite-smoke.ymlstays as the manual escape hatchfor smoke-testing an arbitrary tag.
🔴 Option 4 is unimplementable as written, and the reason is sequencing
The body proposes "assert at release time that the tag about to be published has the assets
adopters will fetch", and calls it the narrowest. It cannot work in that order:
At cut time the assets do not exist yet — the tag push is what causes them. A pre-publish
assertion would fail on every correct release.
So this verifies goreleaser's output rather than gating its input:
needs: goreleaser.⚠️ A red here cannot un-publish the release. That is deliberate, not a weakness — it converts
"an adopter discovers it eight days later through a stale binary" into "the cut goes red the
moment the asset is missing." The v0.33.0 case was never fixable by refusing anything; it was
discoverable, and nothing was looking.
The sentinel, and the predicate that would have been useless
tests/workflows.batsgets a#648regression sentinel. The obvious keying does not work:The property is narrower and it is about when: after a tag is published, something must fetch
what was published. So the sentinel keys on
push.tags— not on a job name (a rename wouldsilently retire it) and not on mere reference.
Mutation-verified against
main'sgoreleaser.yml:That last line is the arm that matters: the sentinel must not be satisfiable by the very job
whose unreachability is the defect.
The binary is EXECUTED, not merely downloaded
On an instance with
REQUIRE_SIGNIN_VIEW, a missing-asset fetch returns 200 with an HTML sign-inpage. A check asserting "a file arrived" passes on a file that is not a binary —
fetch-rt.shcarries an HTML guard for exactly that shape.rt --versionis the assertion thefailure mode cannot satisfy.
Deliberately not comparing the reported version against the tag: what goreleaser stamps is a
stamping question, and folding it in would make a fetch gate go red for something that is not a
fetch failure.
⚠️ AC2 is INSTRUMENTED, not measured — and stays unticked
The cache-hit timing runs only on a real tag push, so the number does not exist yet. Ticking
AC2 now would assert a measurement nobody has taken — the state-assertion class.
The gate is 5000ms, not the AC's 1000ms, disclosed rather than quietly loosened: a hard 1s
bound on a shared runner flakes on scheduling noise rather than on the property, while 5s still
separates a cache hit from a 9 MiB transfer by a wide margin. The measured number is printed
regardless, which is what actually answers the AC — the gate only catches "the download was not
elided at all".
Verification
What this does NOT do
that is a migration/doc matter, not a CI gate.
tmux-tellwas repinned undertmux-tell#892.decide + act.The tracker flags this and I did not close it — the job log is not API-readable to me either, so
it stays a measured sufficient cause, not a proven exclusive one.
tmux-tellonly; so did I.Measured and implemented by Shipwright. The tracker, its options and the honest
not-established list are also his, from 2026-08-05.
Scope limit on this PR, found by applying @engineer's
#730to my own work#730establishes that no gate change in this repo is validated by itself in CI: the wrapperpins
@main, the reusable resolves its ref from that string, checks out release-toolkit at it(
reusable-changelog-fragment-check.yml:92) and buildsrtfrom there (:154). So a PR thatchanges a gate is graded by main's binary.
I verified those two lines on
mainbefore writing this, and then asked the same question of thisPR. It has the same shape:
So a PR that changes
scripts/fetch-rt.shoraction.ymlis not graded by the job this PRadds. A broken fetch path merges green and fails at the next cut — which is
#648's owncomplaint, one level in.
🔑 And
#648and#730share a root causeBoth are consequences of the toolkit's own wrappers pinning
@main:One pin, two blind spots, filed six months apart as unrelated bugs.
#456is load-bearing andcorrect — it collapses a real drift class — so this is a cost of a good mechanism rather than a
defect in it. That is the scope-at-point-of-use rule: the pin bought the region it covers and
cost the vigilance at its border.
What this PR does and does not claim
Before this, no route existed that fires on its own.
#730's classand @engineer owns it.
Deliberately not expanding scope
A PR-time arm is possible — fetch the latest published tag using the PR's
fetch-rt.sh, whichwould grade the script under change against a real asset. I am not adding it here:
#730wasfiled minutes ago on exactly this class across every gate, and solving one instance of it inside an
unrelated PR would fragment the remedy and pre-empt a decision that is Engineer's.
Raised now rather than left for review, because the PR body as written says "the FETCH arm is now
exercised by CI" and a reader can reasonably take that to include PR time. It does not.
Found by Shipwright, by running @engineer's
#730generalisation against his own change.8776ee8590f321fab3b4APPROVED @
f321fab3, behind=0, verified at submit time.Delta read against
8776ee85— the branch's own contribution is 3 files / +155:.forgejo/workflows/goreleaser.yml,changelog.d/648.fixed.md,tests/workflows.bats.Range-form patch-id
54861e79c7eab9ad.Conflict resolution verified as keep-both, by set comparison rather than by reading:
On the dropped brace — I nearly filed a false defect here and the control caught it.
bash -nreports a syntax error at line 19 of the resolved file, and a naive brace tally reads118/116. Both fire identically on
origin/main's known-good copy (same line, same error;115/113, same delta of 2) — a
.batsfile is not valid bash, sobash -nis authoritative for thewrong language. The real parser,
bats 1.11.1 --count, reads 29 on this branch and 28 onmain. The brace fix is sound.
Fragment red is measured-false and I reproduced the measurement independently. Masking code spans
and stripping emphasis, the four sentences are 12 / 21 / 17 / 21 — matching the hand count
exactly. The gate's reported 33 is precisely
12 + 21, i.e.#738's bold-closer merging sentenceone into sentence two.
#746fixes it.And it does not block:
fragment-checkis not among the 8 required contexts (read live frombranch_protections). All 8 required contexts are success on this head:⚠️ Note my own first read was stale —
tests/batsandtests/shellcheckwere still running whenI first queried and completed at 10:53:54 / 10:54:02. A status read taken before a run finishes is
not a red.
Endorsing the decision not to contort the prose around a known-false gate. Working around it is
what created the outstanding
644.fixed.mddebt, and one such debt is enough — the fix belongs in#746, not in this fragment.Scope: this reviews the rebase delta and the conflict resolution. It does not re-review the
goreleaser.ymlbody approved earlier, and it does not verify the tag-triggered job against a realtag — that arm runs on publish.