Skip to content

test(hygiene): declare the source-hygiene gate's scan surface once - #1343

Merged
os-trump merged 2 commits into
mainfrom
claude/issue-1314-gate-surface-mirrors
Aug 27, 2026
Merged

test(hygiene): declare the source-hygiene gate's scan surface once#1343
os-trump merged 2 commits into
mainfrom
claude/issue-1314-gate-surface-mirrors

Conversation

@os-trump

Copy link
Copy Markdown
Collaborator

Fixes #1314

The three sandbox suites that run scripts/check-source-hygiene.mjs each
declared their own hand-copied copy of the gate's three surface constants, and
nothing checked any copy against the gate. They now import all three from one
producer, scripts/lib/source-hygiene-surface.mjs.

Measured on 49bd297.

Route taken, decided against the current tree

Route 1, in its sibling-module form — the card's own "export the constants
from the gate, or a tiny sibling module the gate imports".

The cost that used to rule route 1 out is gone, as the dispatch note says:
#1321 landed allowJs repo-wide and test/source-token-ratchet.test.ts already
imports constants from a gate .mjs under strict with tsc --noEmit green.
So the choice was never TS7016 — it is top-level side effects, and that is
what decided it:

Routes 2 and 3 were rejected against this same tree: a source parser (2) is a
maintenance surface the card itself flags, and a --print-surface flag (3)
would add a test-only seam to production tooling and still need a parser on the
reading side. Neither buys anything over an import that already typechecks.

⛔ The gate is still runnable as bare node scripts/check-source-hygiene.mjs
with no build step, which is how both workflows invoke it. Verified directly.

No surface moved

The three lists were moved byte-for-byte, doc comments included; the only
textual change is the export keyword on three const lines. Proved by
diffing the extracted region against HEAD:

git show HEAD:scripts/check-source-hygiene.mjs | sed -n '510,638p'   vs
scripts/lib/source-hygiene-surface.mjs (export stripped)   ->  identical

The gate's banner reports the same surface before and after: 3 root .ts files,
16 root files, the same five text trees.

Acceptance: the load-bearing proof, not a green suite

All three suites are green today with hand-copied lists, so today's green proves
nothing. Both directions were mutated on origin/main (control, 08613ad) and on
this branch (treatment). Every mutation was proved on disk by blob hash before
any verdict was read, restored with git checkout HEAD -- PATH, and the restore
proved by an empty git diff HEAD; every anchor was match-counted first, so a
zero-match no-op could not be read as green.

mutation of the gate's real list origin/main — hand-copied this branch — derived
ADD a 4th root .ts all 3 suites RED, every failure reading scanned root file(s) missing: eslint.config.ts all 3 green, and the suite grows a case: 53 to 54 tests — the new file automatically gets its own control-byte case
REMOVE a root .ts scan-surface RED; size-advisory GREEN, header-position GREEN — both went on materialising a file the gate no longer reads mirrors follow: scan-surface 31 to 30 cases; the single red is DOES extend the marker and header checks to the root .ts files (#1236), which names vitest.config.ts literally — red naming the drift, which is the acceptance's second branch
ADD a 5th tree to SCANNED (same shape as row 1) all 3 green, 53 tests — including this PR's newly derived ${CODE_TREES.length} violation(s) assertion

⚠️ The card's premise needs one correction

The card says an added root .ts "leaves all three suites still green while
silently not exercising the new file", and that the gate's missing-file guard
"cannot fire on this". Measured, that is not what happens in the ADD
direction.
In a sandbox, a stale mirror means the file is genuinely absent
from disk
, which is exactly what that guard fires on — so all three suites went
red, loudly. What was wrong with it was the message: it names a missing
fixture, sending the reader to create a file, rather than saying a mirror is
stale.

The genuinely silent direction is removal, and it is two suites of three,
not three: dropping a root .ts from the gate's list left size-advisory and
header-position green while both kept materialising a file the gate no longer
reads. scan-surface caught it only by accident of asserting the banner's root
count.

The card is still worth doing, and the fix is unchanged — this only narrows the
claim it rests on.

Notes

  • The third suite's REQUIRED_TREES / REQUIRED_ROOT_FILES naming is kept —
    those names say what that file needs the lists for. Only the values derive
    now. REQUIRED_TREES is [...SCANNED, ...TEXT_SCANNED], which is what it
    always was by hand.
  • Each suite gains a one-element GATE_DEPENDENCIES, matching
    test/source-token-ratchet.test.ts:77 one file away. It is hand-maintained
    and safe to be: an import the sandbox does not carry makes the spawned gate
    die with ERR_MODULE_NOT_FOUND and takes every case in the file with it, so
    it cannot rot quietly.
  • ROOT_EXCLUDED_FILES (the two lock files) stays hand-written on purpose — it
    is an anti-list, pinning files deliberately absent from the gate's
    constants, so there is nothing to derive it from.
  • Changeset has empty frontmatter: tooling and tests only, nothing published.

Verification

pnpm verify green end to end on this tree, quoting each gate's own verdict line:

✓ i18n lint gate: 0 `i18n/missing-*` issues
Source hygiene — 340 files under src, test, e2e, scripts, plus 3 root .ts file(s)
  in the marker and header checks; the control-byte scan adds 348 under
  content, .changeset, docs, .github, .claude and 16 root file(s)
✓ source hygiene clean
✓ source token ratchet clean
✓ Build complete (1552ms)
Test Files  142 passed (142)
Tests  2997 passed | 1 skipped (2998)

Re-run on the final commit 49bd297: pnpm hygiene exit 0, pnpm hygiene:tokens
exit 0, pnpm typecheck exit 0, and the six directly adjacent suites
(three hygiene suites, script-main-guard, source-token-ratchet,
verify-log-decoy-pin) 86 passed. tsc --noEmit --listFiles confirms the new
.mjs is genuinely in the typecheck program rather than silently excluded.

Generated by Claude Code


Generated by Claude Code

claude added 2 commits August 27, 2026 01:51
The three sandbox suites that run scripts/check-source-hygiene.mjs each
declared their own hand-copied copy of the gate's SCANNED, TEXT_SCANNED and
ROOT_TEXT_FILES, and nothing checked any copy against the gate. #1236 made
those copies load-bearing: each suite's rootFixture() branches on its own
ROOT_TEXT_FILES to decide which fixtures carry a copyright header.

Move the three lists, with their reasoning, into
scripts/lib/source-hygiene-surface.mjs and import them from both the gate and
the suites. No constant value moves; the moved text is byte-identical.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ED4cusQ7pxZ7d7TCLKrZb
@vercel

vercel Bot commented Aug 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
hotcrm Ignored Ignored Aug 27, 2026 2:01am

Request Review

@github-actions github-actions Bot added the ci/cd CI plumbing and the verification pipeline label Aug 27, 2026
@os-trump
os-trump marked this pull request as ready for review August 27, 2026 02:06
@os-trump
os-trump added this pull request to the merge queue Aug 27, 2026
Merged via the queue into main with commit 16d5d3c Aug 27, 2026
10 checks passed
os-trump pushed a commit that referenced this pull request Aug 27, 2026
…s prose (#1346)

The comment above the "does not judge .d.ts" pin explains why the header check
reads `.ts` only, and propped that explanation on two hand-maintained integers:
"3 of 5 `.mjs` under scripts/, the `.sh`, the four `src/docs/*.md` pages".
Sixth instance of the defect the #1338 family collects — a counted fact in prose
with no producer and nothing checking it — handled the same way: the numerals go,
the claim they propped up stays, and no counting guard is added (ADR-0049 axis 3).

This instance came with its own demonstration that renumbering is not a repair.
The card was written against "2 of 7"; PR #1343 moved it to "2 of 8" two hours
later by adding `scripts/lib/source-hygiene-surface.mjs`; and measuring here with
the gate's own rule gives 3 of 8. The third reading differs from the second
because `scripts/publish-marketplace.mjs` opens

    // Copyright (c) 2026 ObjectStack contributors. …

while `COPYRIGHT_HEADER` is anchored `/^\/\/ Copyright \(c\) \d{4} ObjectStack\./`
— it wants the period directly after `ObjectStack`, so that file is headerless to
the check and headered to a reader. Three measurements, three answers.

Deleting the numerals alone would have left a false sentence, so the `.mjs` clause
gains a quantifier. "3 of 5" was not only arithmetic: it said *some, not all*, and
five of the eight `.mjs` under `scripts/` do carry the header. "Some of the `.mjs`
under scripts/" says that without a figure and holds under either reading of the
`publish-marketplace.mjs` spelling. The other two kinds stay unqualified because
they are complete — the one `.sh` and all four `src/docs/*.md` pages have no
header at all.

Prose only: no fixture, assertion or `describe` block is touched, and the gate
itself is untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ED4cusQ7pxZ7d7TCLKrZb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd CI plumbing and the verification pipeline

Projects

None yet

2 participants