…ventory
`test/source-hygiene-scan-surface.test.ts` still asserted "The two lock files
are pinned the same way — they are text, they sit at the root, and they are
excluded on purpose". There is one lock file at the root now: `package-lock.json`
was retired with the StackBlitz demo it existed for. The gate's own surface
module was corrected in that same commit; only this suite lagged, and running it
could never have caught the drift — the case materialises both names in its own
sandbox and never reads the real tree.
The `package-lock.json` entry in `ROOT_EXCLUDED_FILES` is KEPT, on a measurement
rather than on the tidier-looking diff: adding the name back to the gate's
`ROOT_TEXT_FILES` turns that case red on the byte it plants, so the entry is a
live guard and deleting it would delete a guard. The defect was the prose, which
described an inventory of the tree instead of the property being pinned — that
the byte check's whitelist carries no lock-file name whether or not such a file
exists at the root. All three sites (the file docblock, the constant's comment,
and the case name plus its inline comment) now say that, and record that the two
entries no longer rest on the same reason.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Z7RPTnPnXsb8RUBr4yDjE
Fixes #1470
test/source-hygiene-scan-surface.test.tsstill argued about "The two lock files are pinned the same way — they are text, they sit at the root, and they are excluded on purpose." There is one lock file at the root now: #1469 retiredpackage-lock.jsonwith the StackBlitz demo it existed for, and corrected the gate's own surface module in the same commit. Only this suite lagged.The drift was invisible to CI by construction and stays invisible: the case materialises both names in its own sandbox and never reads the real tree, so deleting the file could not have turned it red. Reading was the tool here, not running.
The probe — the PM's premise 4, measured: RED
The dispatch ruled that the
package-lock.jsonentry inROOT_EXCLUDED_FILESmust not be dropped merely because the file is gone, and asked me to break the assumption underneath that ruling: that the entry is a live guard, i.e. that re-addingpackage-lock.jsonto the gate'sROOT_TEXT_FILESwould turn the case red.Method. In this worktree, temporarily insert
'package-lock.json'intoROOT_TEXT_FILESinscripts/lib/source-hygiene-surface.mjs, run the suite, revert. The mutation and the restore were each proved on disk rather than read off an exit code:before=0 after=1, anchor line still present, blob hash movedb9b89eab -> e2551783git diff HEADempty,git status --porcelainempty, blob hash back tob9b89eab=HEAD:scripts/lib/source-hygiene-surface.mjs, injected-line count back to0trap ... EXIT INT TERMwith absolute paths, and aborted rather than measuring if either hash check failedResult — red. Baseline 31 passed; under the mutation 32 tests ran (the
it.each(ROOT_TEXT_FILES)case gained a member and passed) and exactly one failed:⇒ The entry is a live guard. The PM's assumption holds and ruling 2 stands: the entry is kept. Deleting it would have deleted a guard, and the tidier-looking diff would have been the wrong one. The probe is fully reverted — this PR does not touch
scripts/lib/source-hygiene-surface.mjs.The counter-argument, and where it landed
The dispatch's item 5 is right that the two entries no longer rest on the same reason:
pnpm-lock.yamlis excluded by a live written decision (generated, so the check's remedy has no author to reach), whilepackage-lock.jsonis now merely absent from the whitelist. That difference is real, so the new prose states it rather than papering over it — but it does not argue for deletion, because the deletion of the file was a decision, not an impossibility, and the guard has to survive that decision being reversed.The actual defect, in other words, was never the entry: it was prose describing an inventory of the tree where the thing being pinned is a property of the gate's whitelist — that no lock-file name is on it, whether or not such a file sits at the root today. A second comment describing a tree would have fixed nothing (dispatch ruling 3).
What changed — one file, comments and one case name
ROOT_EXCLUDED_FILEScomment. Expanded from one line into the justification the entry now needs: it is an assertion aboutROOT_TEXT_FILES, the two entries' differing footing, and the measurement above recorded so the next reader does not have to re-run it to know the entry is live.does NOT read a lock file at the root, present or returning (#838, #1470). The old name and comment carried the same two-file assumption as the docblock (dispatch item 6).No change to what the gate scans or excludes;
ROOT_EXCLUDED_FILESstill reads['pnpm-lock.yaml', 'package-lock.json'].Verification
pnpm verify— the repo's full chain, all eight steps — green on this branch at2f51bc83, the tip of this PR, with a clean tree at that sha:The token ratchet is unmoved by design: it measures
src/**comment-stripped, and this change is comments intest/. Also self-scanned both touched files for raw control bytes beyond the gate — clean.One note for the reviewer, not a change in this PR
ROOT_EXCLUDED_FILESis hand-maintained whileROOT_TEXT_FILESbeside it is imported from the gate, because #1314 found hand-copied surface lists rot silently — and this card is that rot, in the one list nobody converted. Having worked in it: it is hand-maintained by necessity and converting it is not available. It names what the gate does not declare, so there is no producer to import from; the names have to be written down somewhere. What actually rotted was the prose around the list, not the list, so the fix is the one applied here. I have left a note to that effect at the declaration.Generated by Claude Code