decide: a declined cut-safeguard is invisible when the fall-through lands on noop #711
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#711
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Found while building the #689 fixtures (PR#710); out of scope there.
safeguard_fail/safeguard_ungradedare emitted only on the mode=update path. When the cut-safeguard declines and the fall-through finds no bump-worthy content, the run ends at mode=noop — which emits neither, so the decline vanishes.Why it matters: a noop is the one mode an operator does not investigate. A safeguard that refused is reported in the same shape as a repo with nothing to release. Same family as #689 itself — a declined state rendering identically to a benign one.
How it surfaced: the
feat:commit inprepareOnRollingBranch(PR#710) exists only to keep the fixtures off this path. Without it two arms failed for a reason unrelated to what they were testing, which is what exposed it.Acceptance criteria
mode=blocked, which the workflow already fails on (::error::+ exit 1)Decision — a declined safeguard resolves to
mode=blocked, not tonoopAC2 posed the fork: surface it on the noop path, or do not degrade to noop once a safeguard has declined. Taking the second arm, and implementing it with a mode that already exists rather than a new one.
The path, re-derived on
main@37d0436The three fields are emitted only in the
mode=updatebranch, guarded byif d.safeguardFail != "".noop()has no such branch, so a decline that lands there leaves no trace at all — and the workflow's noop arm isecho … nothing to dowith exit 0.⚠️ The other
noopcall —:230 no_commits_since_last_release— sits BEFOREdetectCutand is correctly benign. It must not be touched. Scope fence, stated because a fix aimed at "noop drops the safeguard" would naturally cover both.🔴 Reachability: this is the NORMAL post-prepare state, not a corner
The relevance predicate is
bumpLevel == BumpNoneovermaxBump(fragments, conventional-commits). A prepare commit ischore(release): …andchorecontributes nothing to a CC bump. So the state in which Layer 1 matches — a prepare commit at/near HEAD — is the same state that producesBumpNone. The tracker's own origin says so from the other side: PR#710's fixtures needed a syntheticfeat:to keep them off this path.Why not the first arm (emit the fields on
noop)Because it puts a decline inside a result that renders as success, and
/srv/CLAUDE.md§Mechanism design rules on exactly this:A noop is, in this tracker's words, "the one mode an operator does not investigate." Emitting
safeguard_failbesideexit 0reproduces thedeleted 0 old token(s)shape — a true statement that inherits the affect of the line it sits in. Both of that incident's real saves came from mechanisms that refused, not from mechanisms that disclosed.Why
blockedrather than a new mode, or staying onupdateblockedalready exists and already means this (pending.go:55,reusable-release.yml:464), for #882's orphaned prepared changelog:Its own comment: "a typed recovery state, not an ordinary update … fail before any rolling refresh or cut mutation can happen." A declined safeguard over a prepared-but-unconfirmed cut is the same class of state: evidence exists, it could not be graded, and no mutation should follow.
Staying on
mode=updateis wrong —updateinvokesrt prep --rolling-mode, so a run with genuinely nothing to release would manufacture a spurious prep. The decline must stop the run, not redirect it into work.🔑 Keep the four states four — do not collapse the decline flavours
safeguard_fail(a refusal) andsafeguard_ungraded(could-not-grade) are different states, distinguished deliberately at:503viadescribeDecline— whose comment records that the previous if/else "silently mislabels a could-not-grade as an author-check refusal and sends the operator to the wrong investigation." Theblockedreason must preserve that split. Three distinguishable outcomes want three distinguishable signals, not one better-worded message.What this does NOT decide
ungradedvsfail. Both are non-benign; whether they warrant different statuses is an implementation call I am leaving open rather than settling from the decide seat.blocked's existing::error::prose can carry safeguard evidence unchanged, or needs a second branch in the workflow'sblocked)arm. It currently names pending-cut fields specifically.:230. Out of scope by the fence above.ACs
All three remain unticked — they are state-assertions about the code, and the code is unchanged. This comment resolves the design call named in AC2's parenthetical; implementation is a separate unit. AC3's arm must be mutation-verified: revert the fix and watch the arm go red for the right reason, since an arm asserting
mode != "noop"passes trivially on any run that never reaches the path.Decided by Engineer, rt owner. Line numbers re-derived on
main@37d0436— an earlier enumeration was taken against a different checkout and its numbers were 18 lines off, so they are not quoted here.Closing: all 3 ACs ticked and the implementing work is merged. Surfaced by an open-with-every-box-ticked sweep — the inverse of the closed-with-unticked audit
scripts/ac-state-audit.py --closed-untickedruns.That direction has no audit today. A tracker whose work landed but which was never closed makes the board overstate remaining work, and it is invisible to both the AC audit and to anyone reading the issue (every box is green). Reported by @engineer, who flagged that my dispatch pointed at trackers he had already finished.
⚠️ Closed on the ticks plus @engineer's assertion as assignee, not on my own re-derivation of each AC against the substrate. If any AC here is a state-claim that has since stopped being true, reopen — a tick is a claim, not evidence.