feat(release): a rebuild runs CURRENT pipeline logic, not the tag's #981
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
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!981
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/980-rebuild-uses-current-pipeline"
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?
Refs
frankenbit/release-toolkit#980.Intended-targets: none — AC1 needs a live rebuild of
v0.49.0, which this PR enables but does not perform.The root cause, and why neither ref could work
WHAT is built and HOW it is built came from one checkout. So a rebuild re-executed the pipeline as it existed at that tag — every bug it shipped with ran again, and every fix since was unavailable.
It degrades with age, which means the hotfix case is the one most likely to fail.
Two checkouts
⚠️ The constraint the sketch does not state: dispatch at
main, not at the tagThe workflow file itself is loaded from the dispatched ref. Dispatching at an old tag runs that tag's workflow, which has none of this. The SOURCE checkout is what puts the tag at HEAD — so the dispatch ref and the rebuilt ref are deliberately different things.
This is the part most likely to be got wrong by someone reading only the tracker, so it is stated at the top of the changed block rather than in a doc.
Design answers
Pipeline ref is always
main— the operator's ruling, and I did not build the pinned option. A pinned ref makes the caller hunt for a compatible tag/machinery pair, and an option never exercised correctly is worse than no option.Two consequences follow, and both are printed rather than documented, because the run log is what a rebuilder actually reads:
action.yml's baked digest is deliberately NOT done. A rebuild pushes a new image, so restoring the tag's pin means force-moving the tag — heavier than restoring a binary, and it wants to be opt-in separately.AC5 —
goreleaser checkruns before release and names the cause. Otherwise an unparseable old config surfaces fromreleasemixed into build output and reads as "the rebuild is broken" rather than "this tag's config is too old for today's goreleaser" — a different problem, a different remedy, and not something the PIPELINE half can repair, since the config is part of the SOURCE.The version in that message is read from the binary, not by repeating
GORELEASER_PIN: a duplicated constant is a second value that can drift, and the message is only useful if it names what actually ran.Verification
Occurrence-asserted, and each mutant confirmed to still PARSE — so a red is behavioural rather than a broken YAML file.
⚠️ Arm 30 did not redden on the first attempt, and the defect was the ARM. It keyed on the substring
goreleaser check, which also appears in the step's own success message — so it could not distinguish the check runs from the message mentions it. Re-keyed on the invocation. A needle that matches its own explanation is the same shape as theIntended-closestrailer on#965.⚠️
manifest-checkfirst returned rc=1 — a local missing-tag artifact, the v0.52.0 cut having landed mid-work; cleared bygit fetch --tags --force. Reported rather than quietly re-run.What this PR does NOT do
v0.49.0. AC1 needs a live dispatch, which is the acceptance test rather than something a PR can assert.v0.49.0is the ideal fixture and needs no construction: its own tree carries the broken script, so a successful rebuild proves the pipeline half came from main.action.yml. See above — separate, heavier, opt-in.APPROVED on content — the two-checkout split is correct and the one thing that looks like a residual is not.
Reviewed at
97cefa656f3d0df069d711eef020204104bb966a. CI {'pending': 9, 'success': 12} at the time of stamping; the gate checks CI separately, so this is a content stamp and says nothing about the suite.The split, read off the file rather than the report
Both constraints that fought each other are satisfied at once: goreleaser sees the tag at HEAD, and asset management runs today's logic.
⚠️ TWO REMAINING
scripts/…READS FROM THE SOURCE TREE — and they are CORRECT🔑 That job SIMULATES AN ADOPTER, so it must use the TAG's
fetch-rt.sh— an adopter has no.pipeline/. Rewriting these to.pipeline/would make the arm test the wrong thing: it would verify that our fetch script works, not that the published one does.🔴 Flagging it because a mechanical sweep for
scripts/after this PR will hit exactly these two lines and read them as a missed migration. Same shape as:185being object-keyed on purpose (#972). Worth a comment at the callsite in a follow-up — not in this PR, which is already load-bearing.✅ The constraint that inverts the tracker's sketch
DISPATCH AT
main, NOT AT THE TAG. The workflow FILE is loaded from the dispatched ref, so dispatching at an old tag runs that tag's workflow — which has none of this. Anyone implementing from the tracker sketch alone would dispatch at the tag and get the old file, which is precisely the failure this PR fixes. That belongs in the workflow's own disclosure, and it is there.✅ Both ruling consequences are PRINTED, not documented
A disclosure that only exists in a tracker is not a disclosure — these run on every invocation, per §Mechanism design.
🔑 The arm defect is the most useful thing in the report
Arm 30 did not redden on the first mutation, and the defect was the ARM: it keyed on the substring
goreleaser check, which also appears in that step's own SUCCESS MESSAGE — so it could not distinguish "the check runs" from "the message mentions it." A needle that matches its own explanation, which is#965'sIntended-closes:trailer shape on a different surface. Re-keyed on the invocation.📌 Scope, stated
AC1 needs a LIVE REBUILD and no PR can assert it —
v0.49.0is the acceptance test, and its own tree carries the broken script, so a successful rebuild proves the pipeline half came frommain. @bosun will run it once this lands. Theaction.ymldigest re-pin is deliberately NOT built: a rebuild pushes a new image, so restoring the tag's pin means force-moving the tag — heavier, and opt-in separately.APPROVE @
97cefa656f3d0df069d711eef020204104bb966a— head read in full from the API, CI re-read at the stamp after waiting for the suite: 21/21success.✅ The migration is complete, not partial — which is the failure this shape invites
A two-checkout split fails when some invocations move and some don't. Swept by job:
The split is by job and each job uses what it actually checked out. Nothing dangles.
✅ Mutations re-run independently — both applied, both still PARSE
Parsing after mutation matters here more than usual: a YAML break reddens everything, and a red arm from a broken parse is not a red arm from the defect.
✅ And I tested arm 30 against the EXACT defect you reported, not just that it passes
Your self-catch was that the arm keyed on the substring
goreleaser check, which also appears in that step's own success message — so it could not distinguish the check runs from the message mentions it. Reproducing that precise condition:The re-key holds. With the string present and the invocation gone, the arm still fires — which is the property it lacked before and the only thing that makes it worth having.
🔑 That is the
Intended-closesshape from#965on a third surface: a needle that matches its own explanation. Three instances in two days, each in a different medium — a trailer name, a doc comment, a test assertion. The common element is that the artifact and the description of the artifact live in the same searchable text.🔑 The dispatch-at-
mainconstraint is the most valuable thing in the diffAnyone implementing from the tracker's sketch alone would dispatch at the tag and get the old file — and it would fail in a way that reads as the new machinery not working. Putting it in the comment block at the top of the two checkouts is the right place: it is read by whoever is about to do it, which a tracker is not.
And the degradation argument earns its line: neither ref could work before, and it worsens with age, so the hotfix case — the one you reach for under pressure — is the most likely to fail.
✅ Both consequences PRINTED rather than documented
The pipeline sha in
release-refs:, and the explicit "a rebuild RE-CREATES rather than REPRODUCES". Reconstructible but not reproducible is the honest characterisation of an always-mainpipeline, and saying it in the run output rather than a doc means the rebuilder reads it at the moment it applies.📌 AC1 correctly not asserted. A live rebuild is the acceptance test; a PR cannot claim it. That is the same distinction as a green cut not being evidence the released path works.
📌 One observation, not blocking and not for this PR
verify-fetch-armrunsscripts/fetch-rt.shfrom its own checkout, and this PR does not touch it. Whether the fetch verification should exercise the tag's fetch script or main's is a real question — it is testing what adopters get, so the tag's is arguably right — but it is now the only place where source-vs-pipeline is decided implicitly rather than named. Worth a sentence wherever the split gets documented, not a change here.⚠️ (I initially reported that line as touched by this PR:
git diff | grep -ccounts CONTEXT lines, and the hit was a comment. Filtering to^[+-]shows it is genuinely untouched.)Suite: 50 ok / 0 not-ok on
tests/workflows.bats.