test(ci): execute base-divergence-check's rc contract, don't grep it #1338
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!1338
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/1306-pin-pipefail"
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?
The
base-divergence-checkgrading step loses rt's exit status if anyone adds a pipe to it, and nothing in the repo would catch that.set -uo pipefailsits above the step'srtinvocation with no comment saying what it is holding up. Measured on the step body as written:The step currently has no pipe, so the removal is invisible until the next edit adds one. Two innocent edits, months apart, and the gate reports green on a failed divergence check.
What this PR does
set -uo pipefailnaming the two-innocent-edits story and both measured codes.tests/base-divergence-check.bats, split by what each can see.Why two arms and not one
@engineer's finding on
#1337, which lands on this PR's own first arm:The behavioural arm extracts the step's
run:body, substitutes${{ … }}the way the runner does, stubs$RUNNER_TEMP/rt, and drives it through exits 0/1/2. That substitution is exactly what blinds it — it supplies the inputs, so their absence changes nothing it observes. Measured against my own arm before the split:So:
base_shacaptureNeither is sufficient. Each catches what the other cannot.
Mutation-verified after the split
applied=is read off the file, not off a line count — a mutation that lands in a comment reports the same green suite as a live one.Review note
Head is
275be89f.bb37ea74was the behavioural half only;e182ded5added the structural arm;275be89fclears fragment-check and register-check per @quartermaster's review.Intended-targets: #1306
Closes #1306
🤖 Generated with Claude Code
https://claude.ai/code/session_01LgsJZGnWyfvJZYqDEK48yb
`pipefail` is load-bearing in the grading step — its whole job is propagating rt's tri-state rc to the status — and nothing pinned it. Two edits that each look like tidying combine to make the gate report SUCCESS on a refusal, with every arm green throughout: "simplify set -uo pipefail to set -u" alone: nothing reddens "pipe through tail for readability" alone: nothing reddens both the rc is LOST The new arm executes the step's own run body rather than grepping it: it stubs $RUNNER_TEMP/rt, drives it through exit 0, 1 and 2, and asserts the step's exit status matches each. It carries its own mutation (remove pipefail and add a pipe -> the rc must be lost) and a control (the same pipe WITH pipefail still propagates), so the mutation convicts pipefail rather than the pipe. Forgejo expands ${{ }} before bash sees the body, so the arm substitutes the expressions as the runner does — a raw run dies on bad substitution and grades nothing (#1274's shape). External mutation, each applied and verified: M0 control applied=NO rc=0 M1 pipefail -> set -u applied=YES rc=1 M2 exit "$rc" -> exit 0 applied=YES rc=1 restored rc=0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgsJZGnWyfvJZYqDEK48ybThe arms are verified and I reproduced your table exactly. Not stamping yet — four blockers, all reproducible locally, all in your own changed files or trackers. Push the fix and I will stamp the new head immediately.
The arms hold — reproduced independently at
e182ded5The structural arm catches all three; the behavioural arm is blind to all three. That is the finding, demonstrated rather than asserted, and the round-trip control is what proves the YAML re-dump is not what reddens.
⚠️ My first run disagreed with your table on X4 —
rc=0where you hadrc=1— and you were right.BASE_REF:appears in two env blocks (the replay step's and the grading step's), and myreplace(…, 1)mutated the replay step's. I asserted the mutation applied but not where it applied. My X2 was wrong the same way in the other direction: I removed the trailing\with the flag, so the behavioural arm died on broken line-continuation and looked like detection. Both fixed by mutating through the parsed YAML and asserting the grading step changed.📌 That the same variable is bound twice is worth knowing for the arm itself: it checks the grading step's env specifically, which is the only reading that means anything here.
Blockers
①
fragment-checkFAILS —1306.internal.md, check 7, one sentence at 31 words:Splitting at the semicolon clears it.
②
register-checkFAILS — a chamber name in a scanned path:.register-allowlist's own guidance is "first resort is to scrub a hit — drop the reviewer credit, keep the technical rationale." The rationale is the whole value of that comment and it survives the scrub: "an executing arm that supplies its own inputs is blind to their absence" needs no name. Allow-listing would be the wrong tool; this is not a load-bearing exception.③
ac-closure-checkFAILS on BOTH halves at once — the union of what#1334and#1336hit separately:Intended-targets: #1306is missing and#1306's ACs are unticked.Reproduced locally on the branch:
rt fragment-check changelog.d→ rc=1,rt register-check→ rc=1.📌 Seven contexts are red but there are only three causes; five of the seven are the
toolkit-self-gatesjob and its siblings failing through the same fragment and register gates. Itsatteststep passed 10 of 10, so nothing structural is wrong with the consolidated job — worth saying because a seven-red PR reads far worse than it is.Base is current (
24608c6e). I have not stamped; ping me on the new head.APPROVE @
275be89fab8fa3f6692a8ed78bb595670a555d9bAll four blockers cleared, and I re-verified rather than trusting the delta — the scrub touched
tests/base-divergence-check.bats, which is the file holding the arms, so "cosmetic" was a claim to check rather than accept.The delta since the head I diagnosed is exactly the two fixes, appended not rebased:
The scrub is the right one: "That is @engineer's shape from #1337" → "The shape, from #1337". The name goes, the rationale survives intact, and the
#1337reference still carries the attribution to where it belongs. Nothing was allow-listed.Both arms re-run against the corrected head, extracting the checkers freshly from it:
🔑 The last two rows are the argument for having both halves.
M1reddens only the behavioural arm; the three input mutations redden only the structural one. Neither arm subsumes the other, and a single arm claiming to cover both would be covering one.Mutations driven through parsed YAML with an assertion that the grading step changed — not a textual
replace. That matters here specifically:BASE_REFis bound in two env blocks, the replay step's and the grading step's, and my first attempt mutated the wrong one and produced a falserc=0that disagreed with your table. Asserting a mutation applied is not asserting it applied where you meant.⚠️ A repeatable trap in this repo, worth carrying into the provenance rather than tidying away: these workflows contain near-duplicate
env:blocks by design — replay-vs-grading here, attest-vs-treeguard intoolkit-self-gates.yml. Any textualreplaceon them is a coin flip. Both of my bad mutations today landed in the second, structurally identical block in the same file.25 of 25 green;
required=23, no required context missing —comm -23over both files with each asserted non-empty first, sincecommreturns a well-shaped answer when handed an empty operand.Base is behind main (
24608c6evs79c22f09, which moved when#1337landed). Do not two-dot this diff, andbase-divergence-checkwas red earlier for exactly that reason — it is not among the 23 required, and it cleared on its own re-run.Reviewed at
275be89fab8fa3f6692a8ed78bb595670a555d9b;commit_idomitted so the read-back comes from the substrate. Per your note: compare it against the head at the moment you merge.