Skip to content

fix(gate): stamp the installed gate with the install time, and record a receipt (BACKLOG #1247) - #682

Merged
wshallwshall merged 2 commits into
mainfrom
b1-1247-install-receipt
Aug 29, 2026
Merged

fix(gate): stamp the installed gate with the install time, and record a receipt (BACKLOG #1247)#682
wshallwshall merged 2 commits into
mainfrom
b1-1247-install-receipt

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

One commit, two files. Merges clean into main.

The load-bearing half

Copy-Item carries the source file's LastWriteTime, 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:

$installedAtUtc = [DateTime]::UtcNow
(Get-Item -LiteralPath $GateDst).LastWriteTimeUtc = $installedAtUtc

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:

#613 this branch
LastWriteTime appears only in a warning string"DO NOT trust the file's timestamp" stamps it: LastWriteTimeUtc = $installedAtUtc
merges into main conflicts (DIRTY, and failing CI gate) clean, merge-tree rc=0
test files touched 11 1
receipt test name test_gate_install_receipt.py test_install_gate_records_the_install.py

Documenting 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.ps1 changes 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

merge against current main   merge-tree rc=0, clean
files a merge would change   scripts/worktree/install-gate.ps1 (M), tests/test_install_gate_records_the_install.py (A)
branch state                 1 ahead of main
push verified                remote 548b1e628 == the tip handed over

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.

…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.
@wshallwshall

Copy link
Copy Markdown
Collaborator Author

Correction to this PR's description: it mischaracterises #613's mtime choice

The table above says #613 has LastWriteTime "only in a warning string", implying an omission. That is wrong. #613 addresses the mtime deliberately, with its reason stated in scripts/worktree/_gate_receipt.ps1 — a file I never opened, because I diffed the two branches on the one file they share and concluded about the whole PR.

Its position, quoted:

THE MTIME IS THE TRAP, NOT THE GAP. ... An absent record makes a reader say "unknown"; a WRONG record makes them say something false with confidence. So nothing here repairs or writes mtime — a corrected timestamp is still one mutable field asserting a fact nothing corroborates.

The real choice, and it is genuinely open

Document-the-trap (#613) versus repair-the-trap (this PR). Both defensible:

  • A repaired stamp is a second authority — two records of the install time that can disagree, where the receipt is the one also carrying hash and actor.
  • But the person who was misled was reading the mtime, from a file listing. A comment in the installer never reaches them.

What that means for this PR

Do not land this and #613 independently. They conflict on install-gate.ps1 (merge-tree rc=1), and both landing yields two receipt tests for one behaviour under two names.

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 DIRTY and failing CI gate, so nothing is landing in that order today.

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.
@wshallwshall
wshallwshall added this pull request to the merge queue Aug 29, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 29, 2026
@wshallwshall
wshallwshall added this pull request to the merge queue Aug 29, 2026
wshallwshall pushed a commit that referenced this pull request Aug 29, 2026
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.
Merged via the queue into main with commit 31b0171 Aug 29, 2026
38 of 39 checks passed
@wshallwshall
wshallwshall deleted the b1-1247-install-receipt branch August 29, 2026 18:49
wshallwshall added a commit that referenced this pull request Aug 30, 2026
…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>
wshallwshall pushed a commit that referenced this pull request Sep 2, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant