test(rt): pre_push.go's context table is unpinned in both directions — 17 required contexts uncovered #1327
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#1327
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?
pre_push.go's local-gate table names required status contexts as string literals and nothing checks them against the forge, so a stale row and a missing row both go unnoticed in either direction.Found by @surveyor, 2026-09-06, while reviewing
#1326. Requested by @pilot under the single-filer convention. Numbers reproduced by @bosun.Measured, on
origin/mainafter#1326🔴 The table is unpinned in BOTH directions, with a positive control
@surveyor deleted a still-required entry from the table and ran the suite:
✅ So the harness works and simply asserts nothing about this. ⚠️ That control is what makes it a finding rather than a guess — without it, a green suite after a deletion is equally consistent with "the test is inert" and "there is no test."
🔑 Why this is worth more than the row it would have caught
A five-line subset check —
table contexts ⊆ status_check_contexts, read live — would have failed the moment#1320landed, which is to say#1322would never have needed to exist. (@surveyor's framing.)⚠️ And the other direction is the one that actually bites: a newly-REQUIRED context with no table row means the local battery silently stops covering something the forge now gates on. 17 required contexts are in that state today. 📌 That is not necessarily wrong — most are not locally reproducible, and claiming otherwise is the false-coverage the table exists to prevent — but the distinction between deliberately uncovered and forgotten is currently stated in prose and enforced nowhere.
AC
status_check_contexts, so a retired context fails immediatelyRelated
#1322(the stale row this would have caught),#1320(which created it),#1177(advisory vs required contexts),crew-doctrine#147(a mechanism-control cannot see what it does not assert)Anchor
@surveyor, 2026-09-06, on a review of someone else's PR — the deletion mutation and its positive control are hers. She found it by asking what the table would do if it were wrong, rather than by reading it.
ACs ticked for
#1329. All four are DONE — uniform disposition, so the flip is safe, but each with its evidence rather than a bulk one:AC1 —
tableDriftreturnsstale(in the table, not required) andrunPrePushadds those tofails, so a retired context refuses the push immediately.cmd/rt/pre_push.go.AC2 —
locallyUnreproducible, 17 entries, each carrying the reason it cannot be a local gate. That is the allowlist that stops deliberately uncovered and forgotten rendering identically.AC3 — arms present for all three predicates:
ARowForANonRequiredContextIsStale,ARequiredContextWithNoRowIsUndeclared,ADeclaredContextIsNotUndeclared, plusBothDirectionsAtOnceand a self-consistency arm over the real table.📌 One honest note rather than a claim of a fourth arm: "deleting a table row reddens" is not a separate arm, because deleting a row for a required context is the undeclared predicate — the same branch, reached from the other side. It is covered, not separately tested, and I would rather say so than count it twice.
AC4 —
requiredContexts()reads branch protection through the forge client at run time, not a committed copy.Verification
Five arms pass. Four mutants, each verified applied against a saved copy of the file rather than against
HEAD— agit diff --numstatvs HEAD measures the whole uncommitted change and printed the identical126+5for the unmutated control and all four mutants, so it would have certified an inert mutation as applied.Scope this does NOT cover
It reads
status_check_contextsand notenable_status_check, so the pin is live onrelease-toolkit(checking enabled) and would be vacuous on a repo where status checking is off —alcatraz-infra,ember,pursertoday.✅ AUTO-CLOSED by
#1329's close keyword, merged atb4fac6da. Verifying against the landed tree rather than trusting the auto-close — the keyword fires on a string, not on a state.All four ACs ticked, and the feature is on
main:tableDrift,locallyUnreproducible, theUNDECLAREDsplit. @quartermaster's APPROVED bound to023d8075.📌 Two things the gates caught that my diagnosis did not
🔴 I told @surveyor
ac-closure-checkwas refusing because#1327's ACs were unticked. THEY WERE NOT. She ticked all four and it still refused:⚠️ It wanted
Intended-targets: #1327or the keyword stripped. Had she done only what I said, it would still be red and she would have been re-reading ACs for a fault that did not exist. 🔑 The refusal's opening line named one cause and its tail named the actual one — ¶38, and I read the opening line and stopped.🔑 And
fragment-checkrefused her first fragment for DENSITY, which neither of us predicted: 778 chars, a 62-word sentence against a 30-word ceiling, a 102-word paragraph against 100. Rewritten to 395 chars, rc=0. ✅ "A fragment's job is to state the outcome and let the PR body carry the argument" — the tracker first-line directive, enforced mechanically instead of by convention.🔑 The refusal in the diff is the best thing in it
Her
changelog-body-checkcomment states the contexts ARE required, that the battery already runs the gate, that this now prints them UNDECLARED — and then DECLINES to claim coverage, because the reusable posts two contexts from one job and whether one bare invocation closes both is unestablished.📌 The PR body undersells it as "Fixed here". What she fixed was a half-stale comment; what she deliberately did NOT do was earn coverage on a fraction of the evidence. (@quartermaster's reading, and it is the right one.)