Repository navigation
ci: gate a breaking range on a new UPGRADING section - #1598
Merged
Merged
Conversation
davidfarah2003
changed the base branch from
main
to
fix/1578-upgrading-contract
September 13, 2026 21:10
davidfarah2003
added a commit
that referenced
this pull request
Sep 13, 2026
An external report on 0.49.0 found that nothing told an operator what changed for a deployment already running, so the reporter read a flag that had existed for many minors as new and stopped on it. docs/UPGRADING.md is that page: what keeps working across the release, what an operator must do and in which order, the boot wait bound to the new manager rather than to whatever the append-only log already holds, and the reasoned-versus-measured labelling on the two topologies. Every source claim on the page was checked against v0.48.2 and v0.49.0, every fenced block was executed as a program from a dirty log state, and the regenerated docs bundle matches the committed blob. A repository check that refuses a breaking range with no new section on this page is proposed separately in #1598. Refs #1578
davidfarah2003
changed the base branch from
fix/1578-upgrading-contract
to
main
September 13, 2026 22:01
davidfarah2003
force-pushed
the
fix/1578-upgrade-section-gate
branch
3 times, most recently
from
September 14, 2026 06:17
b92db62 to
6649492
Compare
Split out of the documentation PR so the instrument can be argued about on its own terms. Adds scripts/upgrade-section-gate.mjs, its mutation fixture, the ci-suites.d fragment that registers it, and the two package.json scripts. docs/design/upgrade-section-gate.md states what the gate proves (a section for the release was written), what it cannot (whether that section is correct, and any break whose commit was never marked breaking), and why a marker-keyed detector is still worth running. One claim in the note is measured and corrects the premise I was given. The change behind #1578 is 36d1779 feat(core)!, found by searching the release range for the renewal refusal text with a must-be-absent control, and it DOES carry its breaking marker. Replaying the gate over v0.48.2..v0.49.0 exits 1 and names both breaking commits, so run at the time this check would have blocked that release until the page existed. The note says so rather than claiming the weaker 'it would not have fired', which is not what the range measures. The transcript in the note is captured output: every quoted line was matched back against the tool's emission, with a never-emitted line as the control. Refs #1578
Three defects found by review, each measured rather than reasoned about. A shallow checkout made the gate report the opposite of the truth. The tags still resolve at depth 1, so `git log` succeeds over a cut range: v0.48.2..v0.49.0 returned 1 commit instead of 173 and zero breaking changes instead of two, and the gate printed OK and exited 0. That is a green meaning "I could not see" on the one check whose green is supposed to mean somebody documented the break. It now exits 2 with the misuse wording it already had for an unresolvable ref, so the truncated case can never again be reported as a clean one. Three new cells grade it against a real depth-1 clone, paired with an accept control proving the guard discriminates rather than refusing everything, and a tenth mutation leg kills the guard and reds the cell that names it. A pass now reads "no breaking commits DETECTED", not "no breaking commits". The gate reads markers and cannot see an unmarked break, which the design note states plainly, so the old wording had the script contradicting its own note on the line an operator actually reads. The page said the check was proposed and promised to name it once it landed. It now names the command, documents the three accepted markers, and warns that a marker must survive the squash to be inside the graded range. Each documented string was executed against the real predicates with refuse controls, and the documented invocation was run as written. Refs #1578 Follow-up in the same commit, found by review and by mutation: The suite red under a shallow checkout for a reason that was not a defect. Two exit-code cells shell out to the real CLI over release tags, and the new guard correctly answers 2 there, so asserting 1 or 0 failed while the tool behaved exactly as designed. They now report UNGRADED, which is neither a pass nor a failure, and the summary line names the count so a green run can never quietly mean most of it did not run. Adding a skip introduced a worse hazard than it removed, and the mutation sweep caught it rather than a reader: forcing the truncation probe true on a healthy clone made the suite skip two real cells and still exit 0. Every other shallow cell reasons about temporary repositories it builds itself, so nothing looked at the repository the suite was actually in. A skip mechanism with no guard is a mute button. One cell now re-probes and asserts the skip decision matches this repository, and the mutant that forces the probe reds it by name.
The gate existed and CI never aimed it at a pull request. The suite registry ran the self-test alone, which grades two fixed historical ranges, so a breaking change landing tomorrow with no upgrade section was examined by nothing. A gate that is never aimed at the change is worse than absent, because a green check tells a reviewer somebody looked. This step grades base..HEAD on every pull request, using the same base derivation as the existing shard-stability step. Exit 2 fails the job rather than reading as a pass, and a missing verdict line fails it too, because empty output with a zero status would otherwise look green. Measured: the current range exits 0, and the historical range carrying two marked breaking changes with no new section exits 1. A second hunk giving the smoke job full history was written, measured, and deliberately dropped. It would have let two cells grade real release tags instead of reporting them ungraded, which is a small gain, and it cost an abort in the shard-stability detector: that detector pins the smoke job's first step to a byte-exact toolchain prelude, and adding a `with:` block to its checkout makes the topology unreadable. Weakening a pinned supply-chain contract to buy two cells is the wrong trade, and the suite already exits 0 under a shallow checkout by reporting them. Refs #1578
The page edit that named the gate used a filler word the project's own voice check rejects, and editing a page leaves the generated docs bundle stale until it is rebuilt. Both were caught by CI rather than by me: the unit job's "Docs bundle in sync" step runs check:docs-voice first and then diffs a fresh generation, and it failed on the first of those. The lesson is the one this PR is about. I validated the page by reading it and validated the workflow with a generic YAML parser, when the repository ships a checker for each. Running check:docs-voice takes under a second and names the file, the line and the word. Verified here: check:docs-voice exits 0 across 41 pages and exits 1 with the word restored, so the checker is proven able to fail. gen:docsbundle exits 0 and the regenerated bundle carries the new page text. Refs #1578
The step took its base from `github.event.pull_request.base.sha` and its head from the checked-out merge commit. Those are two different snapshots. The payload records main as it stood when the event was created; the merge commit's first parent is main as it stood when GitHub computed the merge. They drift apart the moment main moves, silently. Measured on this pull request: the event base was 0e2d464 13418c6c0801 while the merge's first parent was already 04f1b61 2a5582d49ac736ef48, so the step graded 26 changed paths over six commits where the contribution carries eight paths over five. That inverts the verdict rather than merely widening it. Reproduced end to end with the shipped script: an unrelated main-side commit adding any release section is swallowed into the range and reads as coverage, so a `feat(core)!` that documented nothing exits 0 and prints "covered by 1 new section(s)". Over HEAD^1..HEAD the identical commit exits 1 and REFUSES. A gate that someone else's unrelated commit can turn green is the reporter's own defect one level up, and this one shipped inside the gate written to catch that class. Both ends now come off the checked-out object, which cannot drift. A pull_request checkout that is not a two-parent merge snapshot is a misuse exit rather than a guess, because HEAD^1 on a single-parent checkout is the branch's previous commit and would read as a tiny clean range. Verified by running the step body extracted from the parsed workflow, not a paraphrase of it: the false-green scenario REFUSED at exit 1; a breaking change carrying its own section exited 0; a non-breaking change exited 0; a push-event break with no section exited 1; a single-parent pull_request checkout exited 2; a shallow checkout exited 2. Self-test 40 passed 0 failed on full history. Docs corrected in the same commit, since the prose contradicted the diff. The operator page said the PR-range step was "landing separately" while this workflow makes it live, and the design note said 36 cells and 9 mutants against an actual 39 and 11. Both now state plainly that `ci-ok` is not in the branch protection rule set, where `attribution` is the only required context today, so a red gate reports without blocking. Refs #1578
Two limitations the panel surfaced, written where a reader meets them rather than left implicit. The gate reports through `ci-ok`, and the only required context on main is `attribution`, so a red gate is advisory today. That is a deliberate position rather than an oversight: the org sits on a 20-concurrent-job cap and the board carries a known wrong-red rate, so requiring `ci-ok` would block every merge behind a flaky shard. The enforcement that exists is the operator gate, a maintainer reading a terminal-green rollup before merging. Whether to require `ci-ok` is a repository governance decision and is filed separately, so open question 2 is no longer carried as an open question here. A depth-1 checkout is a degrade, not a false pass, and the note now says so with the mechanism. The smoke suite runs `--self-test`, which reports the two cells needing real release history as UNGRADED and exits 0, printing the count and the reason. Measured in a real depth-1 clone: 38 passed, 0 failed, 2 UNGRADED. The grading path is the `unit` job at `fetch-depth: 0`, and pointing the gate at a range inside a shallow clone is a misuse exit rather than a pass, verified in the same clone at exit 2 for both `--range` and `--base`. Gates run before this push, each named because a claim without one is a prediction: check:docs-voice 41 pages passed, gen:docsbundle regenerated, check:shard-stability STABLE=0 over 0e2d464..7b6d7a6 with 0 of 436 frozen suites changing index and 0 of 585 changing shard. Refs #1578
…aller A reviewer found the guard was one layer too high. The workflow step checks that a pull_request checkout has two parents, but the script did not, so `--range HEAD^1..HEAD` on a single-parent branch graded the last commit alone. Reproduced: a branch carrying `feat(core)!` with a harmless commit on top returns "OK: no breaking commits DETECTED in range" and exits 0. The breaking change is one commit further back and invisible. The workflow already refused that shape, so CI was never exposed. The exposure is everyone else: a local hook, a future workflow, or a person running the script by hand inherits none of the caller's care. A tool that only refuses when its caller remembers to check is correct by convention rather than by construction. `--merge-snapshot` moves the decision into the tool. It derives HEAD^1..HEAD itself, refuses a checkout whose HEAD does not have exactly two parents, and refuses being combined with an explicit range, because two sources for one range is the defect this whole flag replaces. The workflow keeps its own parent check as a second line of defence rather than the only one. Measured, exit codes captured directly rather than through a pipe: script alone, --range HEAD^1..HEAD on the evasion rc 0 the false green script, --merge-snapshot on the same checkout rc 2 refused --merge-snapshot combined with --range rc 2 refused real range still graded rc 0 accept control Six legs on the step body extracted from the parsed workflow: merge snapshot with an undocumented break REFUSED 1, merge snapshot with its own section 0, single-parent checkout 2, the evasion 2, push-event break 1, push-event ordinary 0. Two accept legs and four refuse legs, on both event types. Self-test 42 passed 0 failed with two new cells and the sentinel moved 39 to 41. Mutation fixture grows to 13: one mutant disarms the parent check and one disarms the combination refusal, each naming its own cell. All 13 find strings verified to occur exactly once, with the applicability test calibrated on a known-absent and a known-present string first. Refs #1578
The mutation proof found the cell for the combination refusal green with the refusal deleted: the parent check fires first and also exits 2, so an exit-code reader cannot tell the two refusals apart. The cell was passing for a reason unrelated to the line it names, which is the wrong-green shape this gate exists to catch, one level up in its own suite. Both merge-snapshot cells now assert the refusal's own sentence beside its exit code. Refs #1578
39 cells and 11 mutants became 42 and 13 when the merge-snapshot guard landed, and the note still carried the old pair. Both numbers are read off runs rather than counted by hand: full history prints 42 passed 0 failed, a real depth-1 clone prints 40 passed 0 failed 2 UNGRADED, and the fixture reports 13. The script's own usage block now documents --merge-snapshot, including why it refuses a single-parent checkout. Refs #1578
… cannot read Two defects, both measured on a real two-parent checkout before anything changed. THE SUITE WAS READING THE WEATHER. Two cells ran `--merge-snapshot` against whatever repository the suite happened to be in, so the result was decided by that checkout's parent count rather than by the tool. On a branch head the suite read 42 passed; on a pull request's own merge ref it read 41 passed 1 failed, because the refusal a cell asserted cannot happen on a merge snapshot, where the flag correctly succeeds. CI checks that merge ref out at fetch-depth 0, so the suite went red on the one checkout that matters, and the mutation proof, which refuses to grade against a red baseline, left all 13 mutants ungraded. The cells now build the repositories they name, and the parent probe is shown to discriminate before any of them are believed. A GATE THAT GREENS ON A TYPO. The parser read an option's value by looking one slot to the right and never asked whether the slot held one, nor whether every argument had been understood. Measured: `--merge-snapshot --range` with no value, `--base` with no value, and `--merge-snapshot --typo` all graded and exited 0. A value-less `--range` read undefined, which is falsy, so the combination refusal never fired. It survived because the shapes anyone tested were the shapes that worked: the well-formed combination refuses correctly. Three mutants added for the new guarantees, each proven to apply exactly once. Refs #1578
Mutation found the gap I left in my own fix. Substituting a built repository for `process.cwd()` kept every cell GREEN, because this lane's checkout happens to have one parent and so happens to satisfy the refusal the cell asserts. The cell could not tell a repository it constructed from an ambient one that merely fit, which is the same defect one level up: a cell whose result is decided by the weather passes for exactly as long as the weather holds. The helper now refuses any directory outside the block's own temp root, and it refuses by returning a failing result rather than by throwing, so the cell that reached for the wrong repository reds on its own line instead of handing the reader a stack trace where a verdict belongs. A sentinel cell grades the guard itself, because an unchecked guard is the mute button this suite already caught once in its skip mechanism. Refs #1578
I pushed the sentinel to a number I predicted instead of the one the suite
reported, and committed a red suite for one minute. The failing line said
`{"pass":49}` the whole time. Read the row, not the count.
Refs #1578
…pe defect Counts read off runs rather than recomputed: 50 cells, 48 plus 2 UNGRADED in a shallow checkout, 16 mutants. The design note now carries the defect that took the suite red in CI and why a cell must build the condition it names. Refs #1578
…l blocks the merge The gate refused correctly and could not stop anything. `attribution` is the only required status context on main, measured directly from the branch ruleset, so a red in `unit` reports through the `ci-ok` rollup and the merge proceeds regardless. A refusal nobody has to answer is indistinguishable from a pass to the person clicking merge, which is the failure this gate exists to remove, one level up. The step moves into the `attribution` job. The range logic and the two-parent guard are unchanged. Hosting it there costs nothing, which is what makes this preferable to the alternative of making `ci-ok` required. That alternative would put every merge behind a flaky shard, on an org at a 20-concurrent-job cap with a known wrong-red rate. The script imports only node builtins, exactly like the two checks already in that job, and it was measured running to a correct verdict in a clone with no node_modules at all, so the job still installs no dependencies. Its existing checkout is already the right shape: fetch-depth 0 with the default pull request ref, which is the two-parent merge snapshot the gate requires and verifies for itself. THE PUSH BRANCH IS REMOVED RATHER THAN CARRIED. The step grades pull request merge snapshots only, because this workflow triggers on pull_request alone. The old HEAD~1..HEAD branch was inherited from a workflow that also ran on push, and in this job it is unreachable. Unreachable code that claims to grade something is worse than absent: it advertises coverage the triggers cannot exercise and leaves a second range path free to drift out of step with the one that runs. The reason it is not wanted is the one this workflow already gives for the attribution check, that a push to main is already merged and so a red there is a report rather than a gate. The event is now asserted rather than assumed, so adding a trigger later refuses to grade instead of silently taking a path nobody re-checked. Measured end to end by running the step body itself, in a dependency-free clone checked out at the real merge ref. On the snapshot as it stands, exit 0 with its range printed. With a planted `feat(core)!` carrying no section, REFUSED at exit 1. With the same commit carrying a section, exit 0 naming the section. Event legs: pull_request grades, push and schedule each refuse at exit 2 by name. On a single-parent checkout, misuse exit 2 rather than a silently shrunken range. Both directions and every refusal path, so the gate is proven able to fail and able to pass. The design note said the gate was advisory and that enforcement was a repository governance question filed separately. That framing assumed the step had to run where it was first put, and it was wrong. Those passages are rewritten, and the pull-request-only scope is recorded beside them. Refs #1578
Moving the gate into the required job made three shipped documents false, and the operator-facing page was the worst of them. docs/UPGRADING.md told a reader the check runs in the `unit` job, that it is not in the branch protection rule set, and that "a red gate reports the problem without blocking the merge". Every operative clause was wrong the moment the step moved. An operator reading that would not chase a red, because the page told them a red is advisory. The generated bundle carried the false enforcement sentence into the shipped product, where connector-core serves it to anyone asking about upgrading. That is the half that reaches users rather than reviewers, and it is regenerated here so the correction propagates. The design note is corrected in the two places that were stale, and deliberately NOT in a third. The paragraph describing the move states that an earlier revision ran in `unit` and was advisory; that is accurate history and it is the passage explaining why the placement matters, so a blanket sweep would have deleted the reasoning along with the defect. No existing check could have caught this. `check:docsbundle` regenerates and diffs, which proves the bundle matches its source and cannot prove the source is true: it propagates a faithful copy of a false claim at exit 0. This change's own gate reads whether a section exists, not whether prose agrees with the workflow. So a change whose subject is documentation going stale against a diff shipped exactly that, with a green check beside it. Refs #1578
The prose fix in the previous commit was a repair, not a defence. This change makes the class detectable, because nothing in the repository could see it. The class, measured on this pull request three times: the body claimed 36 cells and 9 mutants against 50 and 16; an open question said the gate "currently self-tests" after it had been wired into CI; and docs/UPGRADING.md told operators the check runs in the `unit` job and that a red "reports the problem without blocking the merge" after the step had moved into the required job and a red had started blocking. The last was generated into the shipped docs bundle, so the false promise reached users of the product. check:docsbundle regenerates and diffs. That proves the bundle MATCHES its source and cannot prove the source is TRUE: it propagates a faithful copy of a false claim at exit 0. This gate read whether a section EXISTS, not whether prose agrees with a workflow. So the tool written to stop documentation going stale against a change had no way to notice its own documentation going stale against its own change. The check is narrow on purpose. It does not grade prose. It asserts one fact that is mechanically derivable from the workflow files and is stated in the docs: which job runs the gate. The job is read from the YAML rather than hard-coded, so moving the step again reddens every document that still names the old job. THE FIRST VERSION OF THIS WAS DECORATIVE AND THE MUTATION PROOF SAID SO. Three mutants disabling the checks all SURVIVED, because the live documents were already correct: with nothing stale to find, deleting the detector changes nothing observable. That is the same defect the cells exist to catch, one level down. The detector is now a pure exported function driven by documents the suite builds itself, including a bundle-shaped escaped string, so the cells fail when the detector is broken rather than when the repository happens to be dirty. The bundle needs its own fixture because it stores each page as one escaped JSON string, and a reader written for markdown can pass on the source while missing the generated copy. Six fixture-driven cells and one live-file leg per artifact, with both directions asserted: a stale claim is named, a corrected page is silent, the host job is honoured rather than assumed, prose merely mentioning a job name is not a claim, and every stale claim in a page is reported rather than the first. Three mutations pinned, all killed by name. Full fixture: 19 mutations, 19 killed. Refs #1578
The check landed without being described in the note that exists to describe this gate, which is the same omission class one layer out: a document that no longer covers what the change does. Three paragraphs, and the middle one is the point. The check asserts ONE fact that is mechanically derivable from the workflow, which job runs the gate, and a future false claim that avoids naming a job passes every leg of it. Prose about what the gate proves, what it cannot see, or how an operator should respond to a red is graded by nothing and cannot be, because those are claims about meaning rather than about a value present in a file. Stating that here means the next reader meets the ceiling beside the check rather than discovering it by trusting a green. Also records why the cells are driven by documents the suite builds itself. The first version read only the real files and was decoration: with the live documents already correct, deleting the detector changed nothing observable and three mutations disabling it all SURVIVED. A detector driven only by real files also cannot be shown to track the workflow, because moving the workflow under it means moving the files too. The history paragraph describing the earlier `unit` placement is deliberately left intact. Measured: the detector reads a past-tense account of the old placement as silent and a live claim as stale, so recording the history does not trip the check that exists to catch stale placement claims. Refs #1578
…a fixed pair
The refuse leg could only notice a document claiming `unit` or `ci-ok`, the two
jobs that had actually gone stale. Review found the evasion by execution rather
than by reading: a page naming the CORRECT host AND ALSO a false third job passed
everything. The positive check was satisfied because the right job appeared, and
the refuse leg never looked at the third because it was not in the hard-coded
pair. That is a page simultaneously correct and false, shipping green, and it is
the precise shape this leg exists to catch.
Reproduced before changing anything: planting "and the grading path is the `live`
job" beside the correct sentence left the self-test at 64 passed, 0 failed, exit
0. After the fix the same plant reddens and names {"stale":["live"]}.
The job names are now read from the workflow files, at the same site that already
locates the host job, so any job the repository defines is a candidate and the
leg tracks the workflows the way the host lookup does. A literal list can only
ever notice yesterday's mistake.
The candidate list is required rather than defaulted. An absent or empty list
would report every page clean, which is the silent-pass shape this tool refuses,
so it throws instead. That guard is what the pinned mutation kills: restoring a
silent fallback reddens the refuse-control cell by name.
Two cells added for the evasion itself and one for the guard. The ceiling
paragraph in the design note is corrected as well, since it implied the leg
covered any wrong job when it covered two; it now says the recognised set is read
from the workflows and records how the evasion was found.
Refs #1578
davidfarah2003
force-pushed
the
fix/1578-upgrade-section-gate
branch
from
September 14, 2026 19:27
9e21173 to
a80dde6
Compare
…greement cells The two CHANGESETS AGREEMENT E2E cells for 'missing closing delimiter' and 'empty frontmatter block' read well and graded nothing: the fixture's only parser mutant changes major-value selection, which cannot move a cell whose subject is boundary rejection. M28 turns the no-boundary throw into an empty result (fail-open: malformed input the installed parser rejects would pass the gate as exit 0). M29 turns the parsed-null return into a throw (fail-closed: an empty mapping the parser accepts would be refused as misuse exit 2). Fixture 27 -> 29.
…n the claims to a head The design note said 90 cells, 88+2 UNGRADED shallow, 90 full. All three were stale and all three UNDERSTATED the suite. Measured at this head: 95 cells, 93 passed + 2 UNGRADED shallow, 95 passed full depth. BODY-v2.md carries the rewritten Verification section: it separates the three self-test figures (95 full / 93+2 shallow / 94 pre-sentinel expected) instead of quoting the largest, binds the 29/29 mutation claim to a head SHA and tree hash, records that the fixture reports 28/1 in a shallow clone because M11 is a no-op there, and withdraws the check:docsbundle exit-0 claim as unverified rather than restating it. Historical CI figures keep their own SHAs and are labelled as not re-taken.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #1578.
Split out of #1588 so the instrument can be reviewed on its own terms. #1588 keeps
docs/UPGRADING.md; this PR adds the check that keeps the page true, plus a one-page design note.What it does
Refuses a commit range that carries a breaking change and adds no new release section to
docs/UPGRADING.mdin that same range.Coverage is sections the range added, base against head. Deliberately not "did the range touch the page" and not "how many sections exist at head": the first version asked the weaker question and accepted on the page's total count, so a breaking change shipped beside a typo fix passed on sections written for earlier releases. A section must be a
##heading naming a release, because coverage is claimed by a heading and one that names no release claims everything while distinguishing nothing.Misuse exits 2, a genuine refusal exits 1. They used to share a code, so a mistyped ref reported a missing section for a verdict never computed.
What it cannot do
Stated in the note where a reader meets it, because a check trusted past its evidence is worse than none:
One correction to the framing this PR was requested under
I was asked to write that the credential-shape change behind #1578 carried no marker, so the gate would not have fired on it. That is not what the range measures, so the note says the opposite.
The change is
36d177951 feat(core)!: bind hosted runs to the caller's issued authority, located by searching the release range for the renewal refusal text operators actually hit (carries no issuance) with a must-be-absent control returning zero. It carries its!. Replaying the gate:So run at the time, this check would have blocked the release that produced the issue until someone wrote the page. That is a stronger claim than I was asked to make and it is the measured one. The unmarked case remains genuinely uncovered and no marker-keyed detector can close it, which is why the page states the credential-shape rule in prose for people rather than relying on the check.
Every line quoted above was matched back against the tool's emission, with a never-emitted line as the control.
Verification
Every figure below says which head it was measured at. Figures measured at an earlier head keep
that head's SHA and are labelled historical; they have not been re-taken here.
Measured at
12736bdbba57a05ae5fddd92b47442828d09aac8(this PR's heada80dde629afa8e6f413597fbb344a0ee87c3096cplus the two mutation-fixture entries below; the gatesource
scripts/upgrade-section-gate.mjsis byte-identical between the two, so the cell countsapply to both). Tree
04e1e150ebbc2f5029ff2577727b1159fc1aab96.--self-test, one invocation. THE COUNT DEPENDS ON THE CHECKOUT, and all three figures are real:UPGRADE SECTION GATE SELF-TEST OK (95 passed, 0 failed)need real release history, and the suite reports them UNGRADED rather than passing them, which
is the honest disposition. Verbatim:
UPGRADE SECTION GATE SELF-TEST OK (93 passed, 0 failed, 2 UNGRADED (shallow checkout: rungit fetch --unshallowto grade them))sentinel, printed as
every cell ran or was reported ungraded (94 before this sentinel).94 before the sentinel + the sentinel itself = 95.
A previous revision of this section said 90 cells. That figure was stale and it UNDERSTATED the
suite; it has been replaced by the three measured numbers above rather than by the largest one.
Three replay legs are included: breaking with no new section refuses, breaking with a section
passes, non-breaking with no section passes. The last two are what distinguish a working gate
from one that reds on everything.
12736bdbba57a05ae5fddd92b47442828d09aac8at full depth against a baseline of 95. Verbatimtally line:
All 29 mutation(s) killed. The suite discriminates.with
baseline green (95 progress marks). Command:node scripts/mutation-proof.mjs --config scripts/mutations/upgrade-section-gate.json(exit 0).Two entries are new in this revision, added because the two CHANGESETS AGREEMENT E2E cells for
missing closing delimiterandempty frontmatter blockread well and graded nothing: thefixture's only parser mutant changes major-VALUE selection, which cannot move a cell whose
subject is boundary REJECTION. M28 turns the no-boundary throw into an empty result (fail-open)
and M29 turns the parsed-null return into a throw (fail-closed), so the pair grades both
dispositions of the same branch. Each redded exactly its named cell at 94 marks against the 95
baseline.
the same fixture reports 28 killed, 1 survived and exits 1. The survivor is M11, which
replaces the
--is-shallow-repositoryprobe with a literaltrue; in a genuinely shallow clonethat is a no-op, so the cell cannot move (93 marks against a 93 baseline). It is an artefact of
the checkout, not a coverage gap. Run the fixture at full depth.
ci-suites.d, and confirmed to land in a shard rather than merely being presentin the file.
check:docsbundle: NOT VERIFIED at this head in this clone. The run exits 1 before it reachesthe docs comparison, because a workspace build artefact is absent
(
ERR_MODULE_NOT_FOUND: extensions/connector-core/node_modules/@cotal-ai/core/dist/index.js,imported from
extensions/connector-core/src/tool-specs.ts). Its first leg passes(
check:docs-voice: 41 pages passed). This is a missing local build, not observed docs drift,but it means the earlier "exits 0 with a clean tree" claim is UNSUPPORTED HERE and is withdrawn
rather than restated. It needs a run in an environment with the workspace built.
HISTORICAL, measured before the latest exact-head review fix and NOT re-taken at this head:
attribution, conclusion success). The wired step printed:grading the merge snapshot's own range: e700ce2a2eb499616e913942ceb0f2f45d5e35e4..8447744b953c4308fb809a0986ca358123db1887then
OK: no breaking commits DETECTED in range. The base it resolved is the branch's actualfork point, reached through the merge snapshot's first parent. This run predates this head; it is
kept with its own SHAs rather than re-labelled as current.
27 KILLED ... against the fixture's own declared count of 27. That claim carried no head SHA or run identifier, and the fixture isnow 29 entries, so it is superseded by the 29/29 measurement above rather than edited in place.
Open questions
The step grades the merge snapshot's own
HEAD^1..HEADrange rather than the event payload'sbase, so a main-side commit cannot supply coverage for a breaking commit on the branch.
Head note. The pushed head is
060c5e7c7b0649bd8303db01546eb4f469c6c8c7, which carries the fixture commit12736bdbba57a05ae5fddd92b47442828d09aac8and the design-note correction and nothing else;scripts/upgrade-section-gate.mjsis byte-identical toa80dde629afa8e6f413597fbb344a0ee87c3096c. Reproduced on a second machine at the pushed head: self-test 95 passed, 0 failed;node scripts/mutation-proof.mjs --config scripts/mutations/upgrade-section-gate.jsonprinted "All 29 mutation(s) killed. The suite discriminates." with exit 0. CI has not yet run on the two new commits.Head note, second push: the head is now
3ff1f17e7e0aeb6b2cd3e99489840c44c9722ae0. On top of the previous head it carries two one-line commits: the design note now says the fixture carries 29 mutants, and the gate script drops an import it never used. Reproduced at this sha on a second machine: self-test 95 passed, 0 failed; the mutation proof printed "All 29 mutation(s) killed. The suite discriminates." with exit 0; shard-stability STABLE; docs voice clean. CI has not yet run on the two new commits.