fix(gate): stamp the installed gate with the install time, and record a receipt (BACKLOG #1247) - #682
Conversation
…ops lying (BACKLOG #1247)
THREE THINGS WERE MISSING, all measured at zero on origin/main against a positive control of three
Copy-Item calls: the gate script was overwritten with NO BACKUP, nothing recorded that an install
had happened, and the installed copy's timestamp was INHERITED rather than set.
THE TIMESTAMP IS THE LOAD-BEARING ONE AND IT IS WHY THIS ROW EXISTS. Copy-Item carries the SOURCE
file's LastWriteTime, so the installed gate reported a time from whichever checkout it came from.
MEASURED: a source back-dated six days produced an installed copy reporting the same six-day-old
time, seconds after the copy ran. A correct stale-gate report was once RETRACTED on the strength of
that timestamp -- "nothing wrote it today" -- and the retraction propagated.
AN INHERITED MTIME IS WORSE THAN A MISSING ONE BECAUSE IT READS AS EVIDENCE. A file with no
timestamp gets questioned; a file with a confident wrong one gets believed.
The mtime now answers the question people actually ask it: WHEN WAS THIS INSTALLED. It deliberately
does not answer "is this current", which is a CONTENT question that -Status already answers by hash.
THE NEAR-MISS THE ROW PREDICTED CAUGHT ME FIRST, and it is recorded so the next reader does not
repeat it: Write-Settings has long backed up settings.json, and the allowlist gained a backup under
#1375. Reading "Copy-Item ... .bak" in this file therefore makes the absence around the GATE SCRIPT
read as presence. I claimed this row was partly discharged by my own #1375 work; it was not. Three
different files.
THE RECEIPT SAYS WHAT IT IS WORTH, because shipping it silently would replace one confident wrong
answer with another. Gate rule 1a protects ~/.claude/hooks/ by EXACT FILENAME and refuses to key on
the parent directory, so the receipt sibling is NOT protected and a session can write it. It carries
a checkable hash and an explicit note that it is a convenience record, not attestation.
THE TESTS EXECUTE THE REAL REGION RATHER THAN ASSERT ITS SHAPE. The install path cannot be run from
a session -- the script refuses inside Claude Code by design, keyed on the session not the target --
and the escape hatch that invites is a static test pinning call-site shape, which on the sibling item
was escaped every time. So these cut the region out of the real file, run it against a fixture, and
read the consequence off the filesystem.
MUTATION-PROVED AT THE CALL SITE, against an unmutated control of 4 passed:
drop the mtime stamp RED
drop the gate backup RED
drop the receipt write RED
receipt hashes the SOURCE GREEN -- THE SUITE MISSES IT
THE FOURTH IS RECORDED IN THE TEST ITSELF RATHER THAN HIDDEN. No fixture can catch it: immediately
after Copy-Item the two files are byte-identical, so hashing either gives the same digest. The rule
is untestable at that point BY CONSTRUCTION, not by oversight. It is harmless today and stops being
harmless the moment the copy becomes conditional or the installed file is post-processed.
NOT DONE HERE: the zero-root exit in worktree_gate.ps1 is the DOCUMENTED kill switch and is
untouched. The row's headline misjoins it to this path anyway -- a bare install writes ONE root,
never zero, so that branch never fires here.
Scope: scripts/worktree/install-gate.ps1 + tests/test_install_gate_records_the_install.py.
Correction to this PR's description: it mischaracterises #613's mtime choiceThe table above says #613 has Its position, quoted:
The real choice, and it is genuinely openDocument-the-trap (#613) versus repair-the-trap (this PR). Both defensible:
What that means for this PRDo not land this and #613 independently. They conflict on This author is not contesting the call. Their position, relayed: if a single authority is wanted, #613 alone is right and this branch should be dropped rather than merged. Their preference is both — #613's richer receipt plus the one-line stamp repair on top. The lander's read: the strongest form is #613 landing, then this one-line stamp repair applied on top with the second-authority objection answered in a comment — not this branch as it stands. #613 is currently Correction found by the Dispatcher seat; the quotations are my own re-verification. |
test_every_non_engine_test_is_classified was failing on all three test legs: tests/test_install_gate_records_the_install.py was in neither the manifest nor the explicit stay-list. Classified against the manifest's own rule -- a test that reads real engine source stays on the engine leg -- rather than by where it felt like it belonged: references to messagefoundry/ 0 <- the stay-list criterion pwsh / subprocess / scripts-worktree 5 CONTROL, a known tooling test 1 messagefoundry ref, 9 pwsh CONTROL, a known engine test 2 messagefoundry refs Zero engine references and five harness-child references. It exercises scripts/worktree/install-gate.ps1, which is development harness. Unambiguous, so the manifest's "when ambiguous, LEAVE IT OFF" does not apply. Mutation-tested both arms: entry removed -> test_every_non_engine_test_is_classified FAILS, 1 of 9 entry present -> 9 passed Inserted at position 70, inside the existing alphabetical run, rather than appended -- the file has no global ordering guarantee but its body is sorted and a stray tail entry reads as an accident.
tests/test_gate_ci_mirror_parity.py imports no engine module, so test_every_non_engine_test_is_classified would have reddened all three test legs the moment this branch reached CI. Caught before the run rather than after. Classified by the assertion's own criterion -- imports no engine module -- and the file imports only re, pathlib and pytest. Mutation-tested: entry removed -> the classification test FAILS; restored -> 22 passed across the partition test and the new parity test together. THIRD TIME TODAY. #682, #687 and now this one: every branch adding a new non-engine test file hit the same gap, because nothing prompts an author to classify a test they just created and the failure surfaces only in CI, on three legs at once, naming a file the author did not think of as needing a home. Worth a row of its own; not filing one here because this commit is a one-line fix on someone else's branch.
…reen, and the ungated sequencer (#685) * docs(backlog): file #1393 -- the REBUILD bar every dispatch screen misses Four open rows say their work already shipped and must not be rebuilt -- #1107, #1130, #1183, #1242 -- and every dispatch screen in this repo passes all four as buildable. THE MECHANISM IS ONE WORD. Blocker screens match a verb list (BUILD, START, DISPATCH, IMPLEMENT, LAND) and none of them matches REBUILD. Two independently written screens were measured against this population today and both missed all four, which makes it a property of the vocabulary rather than of one tool. THE STRUCTURAL HALF IS WHY THE SET GROWS: each row is open ONLY because closing it is a judgement the building seat cannot make. The build landed; the banner cannot move without an authority the builder lacks. So the ledger accumulates done-but-open rows by construction and nothing prunes them. THE MIRROR DEFECT, found in the same pass: a bar that has already EXPIRED. #1020 contains "DO NOT BUILD THE GATE YET" and also contains "THE STANDING 'DO NOT BUILD THE GATE YET' PARK HAS EXPIRED BY ITS OWN TERMS" and "UNCHANGED: build now". A screen matching the string blocks a row that explicitly says to build it. REBUILD a real bar the screen MISSES -> a lane-window rebuilding shipped work EXPIRED a dead bar the screen HONOURS -> the row is lost while reading as handled Neither is reachable by a verb list; both turn on whether a sentence is still in force, which only reading answers. So the proposed fix is not a longer verb list -- it is making the screen say MUST BE READ instead of returning a verdict. EVIDENCE: all 115 open rows containing "DO NOT" were read across two passes today -- the 79 no verb list could classify, then the 36 they could. Working and per-row verdicts anchored at refs/builder2/dup-hunt-2026-08-29, read back through the ref against a control phrase. Ref rather than mail because mail's default TTL is 72h and the gap to the next session is 108h. Ledger controls: 388 -> 389 items, added [1393], lost [], #1393 parses open. * docs(backlog): file #1394 -- the blocker-screen kills the best-written rows A dispatcher screened open rows for blockers by matching the token "DO NOT". 115 of 251 open rows contain it -- 46 percent of the live ledger -- and about 24 actually bar work. An overnight "nothing is dispatchable" finding was published off that screen and has since been withdrawn. Found by the DISPATCHER in its own instrument and disclosed unprompted. THE PERVERSE SELECTION IS THE DEFECT, NOT THE FALSE-POSITIVE RATE. A row saying "do not invert it silently" is a row whose author thought carefully about how the work could go wrong. The screen kills those first; a thin, uncautioned row sails through. The instrument is anti-correlated with row quality. THREE SHAPES, EACH MEASURED, NONE REACHABLE BY A TOKEN MATCH: first-hit a row carries several DO NOTs of different classes; #352 reads as prose on hit one and fences on hit two, #1242 has three harmless hits and a fourth that is a real bar REBUILD four rows say the work already shipped; no BUILD-verb list matches REBUILD (filed as its own row alongside this one) expired #1020 carries "DO NOT BUILD THE GATE YET" and, later, "THE STANDING ... PARK HAS EXPIRED BY ITS OWN TERMS" and "build now" AND THE BUCKET MEANT TO STOP DISPATCH IS THE LEAST RELIABLE: of 19 rows a verb-list screen called BAR, 8 do not bar the row's work -- 42 percent false. Five of those contain the literal words "DO NOT BUILD", meaning not that way, not there, not yet-and-yet-is-over, or quoting. #1334's are quoted definitions of demand-gate and owner-ruling, so screening on the words blocks the row that would fix the screening problem. THE FIX IS NOT A LONGER VERB LIST. Every shape turns on whether a sentence is still in force and whom it addresses. Make the screen return MUST BE READ rather than a verdict, and never let "the screen passed it" stand in for "somebody read it". Evidence anchored at refs/builder2/dup-hunt-2026-08-29 (both classification reports), read back through the ref against a control phrase. Ref rather than mail: mail's TTL is 72h, the gap to the next session is 108h. Does NOT claim ~86 dispatchable rows exist. An unbarred row is a candidate. Ledger controls: 389 -> 390 items, added [1394], lost []. * docs(backlog): file #1395 -- a rebase bypasses all eleven pre-commit gates git does not run the pre-commit hook for a commit created by the sequencer, so a rebase or cherry-pick lands a commit with NONE of the eleven gates having run, and nothing reports it. MEASURED, four arms, throwaway worktree owning no allocation, same content (the commit adding row #1393, allocated to a different worktree): arm 1 ordinary commit 11 gates ran REFUSED control ordinary commit, unrelated edit 11 gates ran (hooks do run here) arm 2 cherry-pick 0 gates ran ALLOWED, commit e1fac8556 arm 3 rebase --onto + conflict + continue 0 gates ran ALLOWED, commit bec1188bd ARM 3 WAS RUN TWICE AND THE FIRST RUN IS RECORDED AS INCONCLUSIVE RATHER THAN DROPPED: the base already contained #1393, the replay went empty, no commit was created, so "0 gates ran" proved nothing. Re-run against a base without #1393 the replay was non-empty, a real commit was created, and the gates still did not run. THE LEDGER GATE IS THE LEAST OF IT. All eleven are skipped -- the config declares 11 hook ids and an ordinary commit prints exactly 11 result lines. The set includes the customer/PHI leak guard, hardcoded-secret detection, the control-character check, SPDX and bandit. A rebase is an ordinary daily operation here; every branch behind main needs one. And the resulting commit is indistinguishable from a gated one afterwards -- git log cannot show which commits were gated, so the gap repairs itself into invisibility. Does NOT claim anything has actually bypassed a gate this way; the finding is reachability, not an incident. CI is a real second line, which is why this is P2 -- confirming which of the eleven have a CI twin is the first task of whoever takes it. Preferred fix is a CI-side `pre-commit run --from-ref --to-ref` over the PR diff, because a local hook is advisory by construction and this row is the proof. Found while settling why an allocation-keyed refusal did not reproduce across worktrees. The allocation answer is replay-versus-fresh-authorship; the reason is that no gate runs at all, which is the larger finding. Ledger controls: 390 -> 391 items, added [1395], lost []. * docs(backlog): file #1396 -- anchor-guided remediation leaves the earlier error An ASVS cell cites specific lines as evidence. When a defect is repaired the repair lands where the anchors point, so anything wrong EARLIER in the same document -- where no anchor looked -- survives, and the reader meets it first. MEASURED IN docs/SECURITY.md. The ASVS-tracker's words: "Every correction to this document landed at a line the cell's evidence anchors cited. Both surviving errors sit 370 lines earlier, where no anchor looked." :806 "[auth].require_mfa on ... the Administrator role must satisfy MFA before any step-up operation ... other users may opt in" :811 the opt-out sentence -- ANCHORED, and fixed under #1388 :1178 the same document, in the row itself: "An earlier revision of this row said Administrator-only and step-up-boundary-only; both were wrong" So the document carried its own refutation 370 lines below the claim. #1388 fixed :811 and left :806, and :806 comes FIRST. Both are repaired now; this row is the CLASS, not those two lines. NOT A CARELESSNESS FINDING, which is why it needs a row. #1388's pass sorted every line INSTRUCTION versus DESCRIBE and fixed the instructions; :806 was classed DESCRIBE on the sound ground that a description says what the system reads, not what to write. The method was right, applied honestly, and still left a wrong description four lines from its own correction. THE MECHANISM: the anchor set is the search space. Anchors record where the evidence was found, never where the claim is repeated, and remediation inherits that boundary silently. It worsens over time -- each pass adds anchors at the lines it just fixed, growing the anchored region around what is already correct. The repair here added anchors at :806, :807 and :811. THE READING-ORDER HALF IS THE DANGEROUS ONE: a document right late and wrong early is worse than one wrong throughout. The early reader is confidently misinformed; the thorough reader finds a contradiction and cannot tell which half is current. Here the early text also named a key refused at load, so following it produced a serve that exits 2. Does NOT claim any other cell has this today -- one instance is measured, the mechanism is general, no census was run. That census is the first task and it is cheap: per cell, grep the claim across the whole file, compare to its anchor lines. Anchors are not wrong to exist; the defect is treating an evidence list as a work list. Found by the ASVS-tracker, relayed by the Liaison. I am the worked example. Ledger controls: 391 -> 392 items, added [1396], lost []. * docs(backlog): #1395 measured its own open question, and it was wrong twice #1395 left one question open in its own body -- which of the eleven pre-commit gates have a CI twin -- and rated itself P2 on the strength of "CI is a real second line". Both halves are now measured, and the row was wrong twice. FIRST ERROR. The row said "bandit, the leak guard and the SPDX check are pre-commit-only as far as this measurement went". That is false. ALL ELEVEN gates have a CI mirror, those three included: bandit -r . in security.yml, scan_forbidden.py --path . in security.yml and branch-leak-scan.yml, and licence_header_check.py at ci.yml:360. SECOND ERROR, and it is the one that matters. "CI is a real second line" holds for TEN of eleven, not all eleven. scripts/hooks/ledger_check.py guards its ownership check as `elif not self.ci and not self.owns(...)` at BOTH :296 and :385, and CI invokes it as `--ci`. So CI never checks allocation ownership. The duplicate-number half still runs; only ownership is skipped, and that is unavoidable rather than an oversight -- the registry lives in .git/mefor-coord/ and never reaches a runner. Put that with this row's own finding and the ownership check has NO enforcement path at all on a replayed commit. Not weakened -- none. The hook is skipped by the rebase and CI is told to skip it too. P2 IS KEPT, AGAINST THE FINDING: a duplicate number IS still caught by CI, and ownership is a coordination aid rather than a security control. The rating was right; the sentence justifying it was not. Also recorded: parity between hook and CI is itself enforced for only 3 of 11, by tests/test_lint_scope_parity.py -- ruff-format, ruff-check and bandit. The other eight are hand-maintained with nothing comparing them. All eight MATCH today, checked individually. A parity test for those eight is the durable fix and is not built. CONTROLS: parse_items 392 -> 392 items, added [] and lost [] -- an edit inside one row must move nothing. #1395 still parses open. The live assertion is gone (the "a P2 and not a P1** -- but" wording returns 0). One control tripped and was a false alarm OF MY OWN MAKING: grep for the stale phrase still returns 1, because the amendment QUOTES the sentence it retracts, two lines before saying "THAT WAS WRONG". A string scan cannot tell making a claim from quoting the claim it withdraws -- the same shape the ASVS tracker recorded for docs/SECURITY.md:186 and :1181, where the document warns against a key rather than using it. Checked rather than accepted. * test(gates): pin the eight CI mirrors nothing was comparing (BACKLOG #1395) CI does not run pre-commit. Measured across all 24 workflows: zero `pre-commit run` invocations. Every CI check that mirrors a hook is a separate hand-maintained re-implementation, and security.yml says so in its own comment -- that test_lint_scope_parity.py fails if the two drift apart AGAIN. That word is the evidence: the drift has already happened once. test_lint_scope_parity.py pins ruff-format, ruff-check and bandit. THIS FILE COVERS THE OTHER EIGHT, which had nothing comparing them: ledger-gate, backlog-parses, forbidden-content, licence-header, control-char, username-access-key, gitleaks, actionlint. It matters because the local hook is skippable. #1395 established that git never invokes pre-commit for a sequencer commit, so a rebase or cherry-pick lands a commit with none of the eleven gates having run. The CI mirror is the only enforcement left on a replayed commit -- and eight of them could drift away from the rule they mirror with nothing failing. RED-FIRST, MUTATING WHAT THE TEST OBSERVES RATHER THAN THE TEST: drop `--ci` from the ledger invocation -> 1 failed rename MEFOR_MIN_DETECTORS in both workflows -> 1 failed (see below) point the licence-header mirror elsewhere -> 1 failed restore all three -> 13 passed THE SECOND MUTATION FIRST PASSED, AND FIXING THAT IS THE POINT. The original assertion was `"MEFOR_MIN_DETECTORS" in body`, and renaming the variable to MEFOR_MIN_DETECTORS_TYPO STILL SATISFIED IT -- the old name survives as a substring of the new one. So the check could not see a rename, which is the likeliest way that detector floor actually gets lost. It now anchors on the ASSIGNMENT and the mutation reds. A test whose own control cannot fail is the defect this file exists to catch, and it was in the first draft of the file. CONTROLS BUILT IN: a fixtures-not-empty arm (every other assertion has the shape "X appears in the workflows", which an empty or misresolved corpus makes vacuous -- and pytest.importorskip("yaml") fails SILENTLY GREEN if PyYAML is ever dropped); a negative control proving the search can return NO; and an exhaustiveness arm asserting this file plus its sibling account for every declared hook, so a NEW unmirrored hook reds rather than landing unnoticed. Comment lines are excluded from the corpus deliberately: a mirror mentioned only in a comment is not a mirror. #1389 was filed against a guard whose evidence cited two COMMENT lines as though they were the run line. WHAT IT DOES NOT DO, stated in the module docstring rather than discovered later: it pins agreement, not truth -- hook and mirror can be wrong together and this stays green; it does not check that each mirror's workflow actually runs on a PR; and it does not close the ledger-gate ownership divergence, which is deliberate and cannot be closed because the allocation registry never reaches a runner. ruff format, ruff check and mypy --strict all clean; 13 passed. * test(tooling): classify the new CI-mirror parity test tests/test_gate_ci_mirror_parity.py imports no engine module, so test_every_non_engine_test_is_classified would have reddened all three test legs the moment this branch reached CI. Caught before the run rather than after. Classified by the assertion's own criterion -- imports no engine module -- and the file imports only re, pathlib and pytest. Mutation-tested: entry removed -> the classification test FAILS; restored -> 22 passed across the partition test and the new parity test together. THIRD TIME TODAY. #682, #687 and now this one: every branch adding a new non-engine test file hit the same gap, because nothing prompts an author to classify a test they just created and the failure surfaces only in CI, on three legs at once, naming a file the author did not think of as needing a home. Worth a row of its own; not filing one here because this commit is a one-line fix on someone else's branch. * docs(backlog): file #1398 -- a built row that says nothing about being built A dispatcher screened #1300 as open, unclaimed, in no PR title or body, and carrying no bar: "P1, quick win, not started". IT WAS ALREADY BUILT AND SHIPPED ON MAIN. I found out only because I started it. tests/test_required_contexts.py:235 -- the test's docstring opens "BACKLOG #1300", and .github/required-contexts.txt names all eight legs and says the second group "WAS ABSENT FROM THIS FILE UNTIL BACKLOG #1300". The row still reads OPEN. THIS IS NOT #1393's CLASS AND THE DIFFERENCE IS THE POINT. #1393 covers rows that SAY their work shipped, where a screen misses the word REBUILD. #1300 says nothing at all -- no bar, no DO NOT, no shipped sentence. There is nothing in the text to miss. No amount of reading the ledger finds it, because a banner is a hand-maintained claim ABOUT the code and it goes stale in the direction nothing detects. THE CHECK, four minutes for nine rows: git grep -l -E "BACKLOG #<N>" origin/main -- tests/ scripts/ messagefoundry/ .github/ Run it with BOTH controls or it proves nothing: a number known unbuilt must return 0, one known built must return nonzero. Without both, a broken pattern returns zero everywhere and reads as "nothing is built". The dispatcher's first run of it was broken in the other direction -- a bare #N alternative matched issue numbers, PR numbers and anchors, returning 53 files for #3 -- which is why the two-way control is part of the check rather than advice beside it. MEASURED across nine rows a dispatcher had screened as startable: 7 of 9 have code on main citing the row number -- #1300 #1337 #1348 #1291 #1254 #1255 #1290 2 clean -- #1384 #1385 controls: #1396 (filed that night) 0; #1027 (landed) 4 The dispatcher then ran it across its whole 59-row bench: 18 of 59 tree-cited. DOES NOT CLAIM SEVEN ROWS ARE DONE, nor eighteen. A citation is not completion; a builder may cite a row while doing part of it. Exactly one was verified end to end. What it establishes is that those rows warrant a read nobody was doing, at a cost of minutes against a lane-window. THE FIX IS NOT A BETTER SCREEN. The dispatcher's screen was good and had just been improved to read banners BY SENTENCE rather than by token -- an improvement that correctly caught three other rows. It still passed #1300, because the ledger is not where the answer lives. The asymmetry that keeps producing this: gates force a builder's commit to cite the row number in code and in the message. The ROW is the one artefact nothing forces anyone to update, and closing it is often a judgement the building seat cannot make (#1393). Silently-stale banners are the normal output of that structure, not an error in it. Ledger controls: 392 -> 393 items, added [1398], lost [], #1398 parses open. The control-char gate caught a literal BACKSPACE (0x08) in the first attempt at this commit, where the grep example's word-boundary escape collapsed to the byte. Repaired by rebuilding the two characters from codepoints; the file now carries zero control bytes. Second time today that escape has collapsed in a generator script, and the gate caught it both times. * test(gates): anchor the mirror check on the INVOCATION, not the tool name REVIEW OF PR 685 FOUND THIS AND PROVED IT BY MUTATION. My matcher used bare tool names for gitleaks and actionlint, and the corpus already carries 10 non-invocation gitleaks lines and 8 for actionlint -- job keys, name: labels, a release URL, checksums, tar, sudo install, --version. SO THE CI MIRROR COULD BE DELETED ENTIRELY, LEAVING ONLY THE INSTALL BLOCK, AND THIS TEST STAYED GREEN -- on exactly the rebase-created commit #1395 describes, where the CI mirror is the only enforcement left. My two findings composed into a live hole in the test written to catch them. The docstring said "not a mention in a comment -- an executable line". That was necessary and NOT SUFFICIENT: `sudo install -m 0755 gitleaks /usr/local/bin/ gitleaks` is an executable line and is not a scan. FIXED by anchoring on the invocation, which is the pattern this same file already used for MEFOR_MIN_DETECTORS after an identical mutation caught that arm. I solved this once here and did not generalise it. gitleaks -> \bgitleaks\s+detect\b (detect is the scan subcommand) actionlint -> \bactionlint\s+-shellcheck= (the flag the hook passes) the six script paths are regexes too, so the whole map is one mechanism MUTATION-PROVEN AGAINST THE REVIEWER'S EXACT CASE: delete the real gitleaks invocation -> 11 mentions remain -> 1 failed delete the real actionlint invocation -> 8 mentions remain -> 2 failed restore both -> 13 passed Before this change both of those mutations PASSED. The six path patterns were confirmed safe by measurement -- 1 to 3 hits each, all run lines -- but safe BY THE CURRENT CORPUS, not by construction, and the comment now says so. STILL OPEN, and named in the review rather than fixed here: nothing links this file to test_lint_scope_parity.py, so _COVERED_ELSEWHERE is a hand-maintained claim that the sibling still pins ruff and bandit. If the sibling drops one, the exhaustiveness arm still passes and that hook is pinned by neither. That is the hand-maintained-mirror defect this file exists to catch, one level up. A THIRD BACKSPACE TODAY: the first attempt at this patch collapsed \b into 0x08 inside the generator script, and the two patterns carried an invisible control byte that made the tests fail for a reason unrelated to the fix. Repaired by building the backslash from its codepoint. The file now carries zero control bytes. Same mechanism all three times: an escape sequence written through a shell heredoc into a Python string. * test(gates): close the CI-mirror parity review findings Addresses the Reviewer's open findings on the CI-mirror parity test. The row number is deliberately not cited: it is allocated but not yet on main -- it rides this branch -- and citing it reds the PR's own citation check. Every pre-commit hook passes on the bad version; only the ubuntu harness leg catches it. WHAT CHANGES, and both fixes are NARROWED forms. The obvious repair for each was designed, attacked by two adversarial lenses, and KILLED: 1. THE --baseline ARM WAS A BARE SUBSTRING OVER EVERY WORKFLOW LINE -- the exact shape _MIRRORS was fixed to abandon a hundred lines above it. Two mutations left it green: renaming the flag on the real run line, and deleting the flag while the path survives in a paths: filter. The screen makes that silent by design -- username_access_key_screen.py returns 0 when no --baseline is given, so a mirror that loses the flag becomes a step that CANNOT FAIL. A per-line regex was REJECTED, not overlooked. The real invocation is a folded block scalar: the script is on one line and --baseline on the next, so the only line carrying the flag carries no tool name. That form also reds on four ordinary spellings already used in this corpus -- --flag=value, a quoted value, a re-wrap, a hoisted shell var. IT WOULD HAVE TRADED A LATENT HOLE FOR AN ARMED FALSE DENY ON A REQUIRED CHECK, which is the direction that gets acted on. Now anchored on the step's RESOLVED run command via a new _run_blocks() corpus: this script, this flag, this value, in ONE command. Tolerates all four spellings; still refuses a decoy in a different step. `all`, not `any`, because a second baseline-less invocation reads as coverage and produces none. 2. _COVERED_ELSEWHERE ASSERTED SOMETHING ABOUT A SIBLING FILE THAT NOTHING CHECKED. Move tests/test_lint_scope_parity.py away entirely and this file stayed green with three hooks still reported covered by a file the repo no longer had. A decorator registry in the sibling was REJECTED: its own proposed code decorated a test that does not bind the CI mirror, so it would have over-claimed on the day it landed -- a second hand-maintained definition, already wrong. The surviving form is existence plus name-reference, with its own limit stated in the docstring: it cannot tell an assertion from a mention. Also: the actionlint arm's `if hook sets it` guard is gone. It could see CI drop the flag but not the hook dropping it while CI kept it -- the same divergence, in the direction nothing watched -- and its CI half was a substring copy of _MIRRORS["actionlint"], which already requires the flag. One rule, one definition. The #1389 attribution in _workflow_lines() was wrong and is replaced with a live in-repo instance, plus the measured fact that zero comment lines match any _MIRRORS pattern today. VERIFIED BY ME, NOT BY THE AGENT THAT DESIGNED IT. 10-row mutation battery, 0 wrong verdicts, both halves: MUST-TRIP rename the flag on the invocation line RED delete the invocation's flag line RED keep the flag, change its value RED _run_blocks returns empty (corpus control) RED move the sibling away RED sibling no longer names bandit RED drop -shellcheck= from the hook args RED MUST-NOT --baseline=value form PASS quoted baseline path PASS rename a sibling test, bandit still named PASS baseline before and after PASS THE MUST-NOT-TRIP HALF IS NOT DECORATION: without it the battery shows only that the assertions can fire, not that they fire on the right things. THREE HARNESS BUGS WERE FOUND AND FIXED WHILE RUNNING THAT BATTERY, and the first run's result was INVALID because of them. Recorded because the third one is the defect this file exists to catch: - the harness ran `git checkout` on the subject file, reverting the uncommitted patch mid-run, so later rows tested shipped code and "passed" - two mutations matched the FIRST textual occurrence of a token, which was a COMMENT, not the invocation -- once for --baseline, once for -shellcheck= - a restore ordering bug collided a moved file with the one git had just put back The mutations now locate their targets structurally. ruff check, ruff format --check and mypy --strict all clean. 13 tests become 14. * docs(tests): state the four residual limits the mirror parity test does not cover Closes the last of the Reviewer's three findings. Its investigation was the one that never returned from the design pass, so none of the earlier hunks touched this section and it was still the three bullets the review objected to. THE REVIEW'S OWN POINT WAS THAT A READER FINISHING THAT LIST BELIEVES THE RESIDUAL RISK IS "the mirror could be wrong" AND "the workflow might not trigger", when the real third risk was that a mirror could be ABSENT and the file would not notice. That specific gap is now closed in code, so the honest bullet is NOT the one the review asked for. These are the limits that actually remain, measured rather than assumed: 1. THE HOOK-TO-PATTERN MAP IS ITSELF HAND-WRITTEN -- the defect this file exists to catch, one level up. Each arm asserts that SOME non-comment line matches, never that it is the BLOCKING step. Measured: ledger-gate matches exactly one line today, but that is a property of this corpus, not of the assertion. 2. THE SIBLING ARM CANNOT TELL AN ASSERTION FROM A MENTION. Gutting that file's bandit arms while leaving the word in place stays green here. Closing it needs the sibling to publish what its arms exercise. 3. The six script-path patterns are safe BY THE CURRENT CORPUS, not by construction -- which is exactly how gitleaks and actionlint failed before they were anchored on their invocations. TWO OF THOSE THREE ARE LIMITS THIS BRANCH'S OWN CHANGES INTRODUCE OR PRESERVE, not inherited ones, and saying so is the point of the section. An unstated limit cannot be acted on. ruff check, ruff format --check and mypy --strict clean. 14 tests pass. No decorative glyphs. --------- Co-authored-by: wshallwshall <mefordev@messagefoundry.org>
Owner-approved shrink. The PR was 117 behind and carried two limbs that landed on main independently while it sat. Dropping them leaves the payload -- the sandbox work, the store backends, the leak gate and the mail drain -- merging clean. WHY A MERGE AND NOT A REBASE: a rebase runs no pre-commit hooks, so the ledger, leak and secret gates would not see any of this. The limbs are dropped by taking main's version of the files in the merge resolution, forward-only. THREE CONFLICTS, AND ONLY ONE NEEDED JUDGMENT. tests/test_ci_engine_step_excludes_webconsole.py take main's scripts/worktree/install-gate.ps1 take main's docs/adr/README.md keep BOTH sides LIMB 1, the CI guard. Main's e9fa3b4 (PR #733) locates the engine pytest step by name rather than by a one-line spelling, which is strictly broader than what this branch did. Taking main's file is safe because the branch does not touch .github/workflows/ci.yml at all, so the merged workflow IS main's workflow, which is the file main's guard was written against. LIMB 2, the BACKLOG #1247 install receipt. Main's 31b0171 (PR #682) covers the backup, the install-time mtime and the receipt. Removed with it: scripts/worktree/_gate_receipt.ps1 and tests/test_gate_install_receipt.py, both new on this branch and absent from main. A COUPLING THAT WOULD HAVE REDDED ALL THREE REQUIRED TEST LEGS. Commit 459fb95 added tests/test_gate_install_receipt.py to tests/tooling_manifest.txt, and main carries test_every_manifest_entry_exists. Deleting the file while leaving the manifest line would fail every test leg. The line is removed in the same commit. Verified after: 137 manifest entries, zero missing, with a negative control confirming the detector sees a name that is absent. THE INDEX ROW, which is the conflict that needed judgment. The branch adds ADR 0176 and main added 0177 through 0180. Both sides are kept, 0176 first, which is both the numeric order and the correct resolution. Resolved against ledger_check.py's own INDEX_ROW regex rather than a hand-rolled scan: each of 0176 through 0180 occurs exactly once, no duplicate row anywhere in the file, zero conflict markers left. An insert-after on the 0173 row was considered and rejected -- that row is 1471 characters, so a substring anchor splices INTO it, and INDEX_ROW anchors at line start, so the spliced row would be invisible to the gate while the 0173 row was silently corrupted. TWO THINGS DELIBERATELY NOT DONE THE WAY THEY WERE ASKED. 1. The BACKLOG #1247 note is REWRITTEN, not deleted. Deleting it would have been wrong. Measured on origin/main before writing the replacement: the copy at install-gate.ps1:452 is unconditional and nothing compares content before it, so one named limb of #1247 -- refusing to overwrite a gate whose content does not match its own receipt -- is still unbuilt. This branch carried that refusal and is now dropping it, so the note is the only place that fact survives. The three refuse matches in that file are an environment check and two comments, not a guard. #1247 stays OPEN. PR #607, the fail-open alternative, is closed unmerged. 2. A citation this branch itself introduced was wrong and is fixed here. scripts/hooks/mail-drain.ps1 cited install-coordination.ps1:238-239 for the SessionStart and Stop rows. They are at 278-279. Verified both directions: the new lines do name mail-drain.ps1, the old lines do not. VERIFICATION link_check.py 4959 relative links in 318 files, every one resolves backlog_status_check 420 items, each declaring exactly one status ADR index 0176 is added by this PR and has exactly one row tooling manifest 137 entries, 0 missing, negative control fires conflicts 0 remaining, 0 markers in any file The full suite runs in CI; this worktree has no virtualenv, so no local pytest run is claimed here.
One commit, two files. Merges clean into
main.The load-bearing half
Copy-Itemcarries the source file'sLastWriteTime, so the installed gate inherited a timestamp from whichever checkout it was copied from — routinely days old, older still on a fresh clone. Measured by the author: a source back-dated six days produced an installed copy reporting the same, seconds after the copy ran.That is the mechanism that once carried a true stale-gate report into retraction. The fix stamps it:
There is a second, independent fix for #1247, and this is why this one was chosen
PR #613 also addresses #1247, inside a five-item bundle. Diffed both:
LastWriteTimeLastWriteTimeUtc = $installedAtUtcDIRTY, and failingCI gate)merge-tree rc=0test_gate_install_receipt.pytest_install_gate_records_the_install.pyDocumenting a trap leaves every other consumer of that mtime still walking into it; stamping makes the timestamp true. The two branches conflict on
install-gate.ps1(merge-tree rc=1), so they cannot both land as they stand.What this means for #613 — for its author, not decided here
#613 still carries #321, #328, #343 and #1215. Its #1247 portion is now redundant and its
install-gate.ps1changes will conflict. The receipt test it adds under a second name would otherwise be a silent duplicate — two tests for one behaviour, neither broken, no dangling citation, nothing reporting it.How this happened is not carelessness: #1247 is one line in a five-item PR title, and Builder 1 built off main deliberately so it would land on its own merits. A multi-item PR hides its contents from everyone who does not read its diff. Found by the Dispatcher seat; the diffs above are the lander's own.
Lander checks
The branch was unpushed when handed over — the fourth today whose tip existed only as a local object. Pushed here.
Neither the author nor the lander has reviewed the 18 tests on their merits.