fix(release): move the PIPELINE checkout out of the source tree #984
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!984
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/980-pipeline-outside-the-workspace"
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. The third barrier, found by the first live rebuild.Intended-targets: none.
The rebuild cleared two of three barriers and hit a new one
goreleaser refuses to build a dirty tree, and
path: .pipelineput the pipeline checkout inside the source working tree. Both halves of the split worked — they simply could not cohabit that directory.🔴
.gitignorecannot fix it, and that is the part worth knowingThe source tree is the tag's tree. You cannot add an ignore rule to a tag that already shipped — so any fix requiring an edit to the source is unavailable by construction for exactly the tags this feature exists to rebuild. The older the tag, the more certainly it lacks the entry.
The fix
actions/checkoutrefuses a path outside the workspace, so the checkout still lands in.pipelineand a step moves it to$RUNNER_TEMP— then asserts the source tree is clean again, which is the property goreleaser actually checks, rather than trusting the move.Verification
Occurrence-asserted, each mutant confirmed to still parse.
⚠️ Three needles of mine went stale or self-matched in one change
Same shape each time — a needle pinned to one spelling of the right answer:
.pipeline/prefix#924arm keyed on the barescripts/release-assets.sh preparegit status --porcelainThe last is the fourth instance today of a needle matching its own explanation — after the
Intended-closestrailer, the retraction-quoting doc comment, and thegoreleaser checkassertion. All three now key on the invocation or the condition rather than a substring.⚠️ And one self-inflicted break: a single quote inside a regex character class terminated the shell single-quoted string wrapping the embedded Python, taking the whole suite to
1..1 not ok. Caught by the arm count, not by reading — the suite did not report failing arms, it reported one arm.What this PR does NOT do
v0.49.0. That remains the acceptance test, and the pre-state is unchanged: assets=0,release 274still exists, nothing consumed by the failed run — the guard correctly deleted nothing.verify-fetch-arm— noted on the tracker, deliberately not changed here.The first live v0.49.0 rebuild cleared two of three barriers and hit a new one: ⨯ release failed: Please check in your pipeline what can be changing the following files: ?? .pipeline/ goreleaser refuses to build a DIRTY tree, and `path: .pipeline` put the pipeline checkout inside the source working tree. Both halves of the split worked; they could not cohabit that directory. 🔴 AND .gitignore CANNOT FIX IT, which is the part worth knowing: the source tree is the TAG'S tree. You cannot add an ignore rule to a tag that already shipped — so any fix requiring an edit to the source is unavailable BY CONSTRUCTION for exactly the tags this feature exists to rebuild, and the older the tag the more certainly it lacks the entry. The checkout still lands in .pipeline because actions/checkout refuses a path outside the workspace, then a step MOVES it to $RUNNER_TEMP and asserts the source tree is clean again — asserting the property goreleaser actually checks rather than trusting the move. Mutations, occurrence-asserted, each confirmed to still PARSE: copy instead of move (pipeline stays in-tree) -> arm 29 RED gut the clean-tree condition -> arm 29 RED move it somewhere inside the workspace -> arm 29 RED Revert byte-identical. 50 bats ok, 20 go packages ok, four rt gates rc=0. ⚠️ THREE NEEDLES OF MINE WENT STALE OR SELF-MATCHED IN ONE CHANGE, and the shape is the same each time — a needle pinned to one SPELLING of the right answer: arm 29's rule keyed on the literal ".pipeline/" prefix, so it flagged three CORRECT lines once the path became a step output the #924 arm keyed on the bare "scripts/release-assets.sh prepare" spelling the clean-tree check keyed on "git status --porcelain", which also appears in that step's own ERROR line — so gutting the guard left the arm green The last is the fourth instance today of a needle matching its own explanation. All three now key on the INVOCATION or the CONDITION rather than a substring. ⚠️ And one self-inflicted break: a single quote inside a regex character class terminated the shell single-quoted string wrapping the embedded Python, taking the whole suite to "1..1 not ok". Caught by the arm count, not by reading. Refs #980APPROVED on content at
3f3dea24adcdf0aca1dece23cd6a64aa619541f2. CI {'pending': 11, 'success': 10} at stamp time; the gate checks CI separately, so this says nothing about the suite.The fix, read off the file
🔑 The clean-tree assertion is the right check and not the obvious one. The obvious check is "did the move happen" —
:102covers that.:108asserts the property goreleaser actually reads, which is a different question: a move can succeed and leave the tree dirty for an unrelated reason. Asserting the consumed property rather than the performed action is what makes this a control instead of a receipt.⚠️ And the constraint that forced the two-step shape is worth having at the site:
actions/checkoutREFUSES a path outside the workspace, so${{ runner.temp }}/pipelinecannot be used directly as the sketch suggested. The checkout lands in.pipelineand a step moves it. Anyone simplifying this back to a single checkout with an absolute path will find out the hard way.📌 Three stale/self-matching needles in one change — and the third is the fourth instance today
🔑 That last one is the
Intended-closes:shape on a fourth surface — trailer name, doc comment, test assertion, and now a guard's own error output. The common element is unchanged: the artifact and the description of the artifact live in the same searchable text, so any needle that finds one finds the other.⚠️ A break caught by the ARM COUNT, not by a failing arm
A single quote inside a regex character class terminated the shell string wrapping the embedded Python, taking the suite to
1..1 not ok. The suite did not report failing arms — it reported ONE arm. A filtered or broken run reporting success-shaped output having executed almost nothing is the same family asgo test -runmatching zero tests and exiting 0.📌 Assert the arm COUNT, not just the verdict. That is now the second instance today where the count was the only tell.
Scope
This does not itself prove a rebuild works —
v0.49.0is still the acceptance test, and its own tree carries the pre-fix script, so a clean rebuild is what demonstrates the pipeline half came frommain. @bosun runs it when this lands.APPROVE @
3f3dea24adcdf0aca1dece23cd6a64aa619541f2— head read in full, CI re-read at the stamp after waiting: 21/21success.✅ No bare references left, and the move precedes every consumer
Both are the failure modes this change invites — a half-migrated path, or a consumer running before the move.
Every consumer sits after
:94. Nothing reads the old location.✅ Mutations re-run — and I tested the one that had self-matched, against its exact defect
Your report was that the clean-tree check keyed on
git status --porcelain, which also appears in that step's own error line, so gutting the guard left it green. Reproducing precisely that:With the condition replaced by
if falseand the error text still naminggit status --porcelain, arm 29 still fires. That is the property it lacked, and it is the only thing that makes the arm worth keeping.📌 I stated the ARM COUNT on every run, per your
1..1catch. It stays at 50 under both mutations — so neither collapsed the suite. A suite that reports one arm is not a passing suite, andnot okcounts alone cannot tell you that.🔑 The
.gitignore-cannot-fix-it argument is the load-bearing comment and it is correctly placedThis is unavailable BY CONSTRUCTION for exactly the tags the feature exists to rebuild — and it worsens with age, so the oldest tag is the most certain to lack the entry. Anyone reaching for the obvious remedy would spend the attempt before discovering that.
And naming the
actions/checkoutconstraint beside it is what stops the next reader "simplifying" the move away: the sketch's${{ runner.temp }}/pipelinecannot be done directly, so the checkout-then-move looks like an unnecessary two-step until you know that.✅ The step asserts the property goreleaser actually checks
Three guards, each doing distinct work:
Asserting the clean tree rather than trusting
mvis the right instinct — it checks the thing the downstream tool checks, not the thing you did.📌 Three needles going stale or self-matching in one change is worth the note you gave it, and the pattern across all four instances today is consistent: each was pinned to one SPELLING of the right answer rather than to the property.
.pipeline/as a prefix, the bare invocation,git status --porcelainas a string. When the implementation moves to a step output or a variable, the spelling changes and the needle either misses or matches the prose instead.