Uh oh!
There was an error while loading. Please reload this page.
fix(scripts): ask the per-GLOB holder question, and stop counting the seeds in prose - #10690
Conversation
… seeds in prose `check:cross-package-test-inputs` had two staleness limbs and both were PACKAGE-scoped: a package with an escaping test and no entry, and an entry whose package has no escaping test any more. Nothing asked whether each declared GLOB was still held by something, so a package that keeps ONE escaping test could carry a glob held by nothing indefinitely — the declaration silently stops being checked against the code, which is the property a narrow radius rests on (#7802's blind spot, one level up). Reproduced on this tip rather than trusted from the card: reseed `managed-extension-fields.test.ts` from `process.cwd()` and the gate exits 0 while `packages/**/*.object.ts` is held by nothing. `globHolderVerdict()` asks the inverse of the roster-coverage limb. Measured first: a roster miss alone CANNOT decide it. The roster is a lower bound — an argument the scan cannot fold costs the NAME and keeps the depth — and 6 of the 60 declared globs on this tree are held by exactly such reads (a loop variable, a `git ls-files` result, a computed target), `create-objectstack`'s `scripts/invoked-as.mjs` among them, a glob whose own rationale already says it "appears in NO quoted string the flat literal collector can see". So a glob is held either mechanically by a roster path or by DECLARATION: `heldBy` names the escaping test that reads it, and the witness is checked, not prose — the named test must still be one of that package's escaping tests. That is what makes the ablation fail by name while the tree stays green. Also #10565: the detector's own "the two seeds" comments, the source the three copies #10163 swept came from. Four of the five sites misdescribed the set — four directory-naming seeds resolve there, not two — including #10452's resolver half, which was not in that card's table. The fifth (`import.meta.dirname` is "the modern spelling of the two seeds above") is positionally true — exactly two cases precede it — and is left byte-identical. Closes#10566Closes#10565
PM ruling + review — verified against the diff and the PR head, not the report. Approving. Q1 — A, and the predicate you could not ship is one I wroteDirection A as my brief spelled it ("a declared glob that matches nothing in its package's roster fails as unheld") is unsound, and you measured it rather than arguing it: it reddens 6 of 60 globs on a healthy tree. Reading the diff, the reason is sharper than "prose-named paths", which is what my brief asserted. Those six are held by reads whose paths are loop variables, So both approaches I offered were individually wrong, in opposite directions:
That the escape hatch existed and you didn't take it is the right call here: the acceptance criteria were all met, so this was a judgment call to surface, not a blocker to stop on. Surfacing it as an open question rather than burying it in the diff is exactly the handling I want. Q2 — A, allow a witness to coexist with roster coverageYour reasoning is better than the alternative and I'm adopting it verbatim: reporting a redundant witness as stale creates a prose-driven ping-pong — an incidental quoted path in a comment flips a glob to roster-held, the gate demands the witness be deleted, a later reword drops the literal and the gate demands it back. That is #9763's "prose was holding the radius" applied to the witness itself. Reporting only What makes this reviewable rather than merely plausible
#10565, verified on the PR head
Notes
Arming. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Closes#10566
Closes#10565
Both land in
scripts/check-cross-package-test-inputs.mjsand nothing else.1. The defect, reproduced on this tip (not trusted from the card)
verify()had three limbs, and the two staleness limbs were both package-scoped:a package with an escaping test and no entry, and an entry whose package has no escaping
test any more. The roster loop runs the other direction — it fails when a test names a
path no glob covers. Nothing asked whether each declared glob was still held by
anything.
Reproduced by reseeding
packages/plugins/plugin-auth/src/managed-extension-fields.test.tsfrom
process.cwd()(a root walk the detector deliberately does not resolve), onorigin/mainbefore this change:packages/**/*.object.tsstays declared inCROSS_PACKAGE_TEST_INPUTSand inturbo.json, and nothing holds it. Exit 0.2.⚠️ Where this departs from direction A as written, and why — measured
Direction A says "a declared glob that matches nothing in its package's roster fails as
unheld". Measured on this tree, that predicate fails 6 of the 60 declared globs on an
unmodified tree. The card anticipated one caveat (globs declared for a path only named
in prose — those ARE on the roster, held by the flat literal collector). The larger one is
different: the roster is a lower bound. An argument the scan cannot fold costs the
NAME and keeps the depth, so a real read whose path is a loop variable, a
git ls-filesresult or a computed target holds a live radius while naming nothing.
Every one of the six is a live read, verified individually — none is a genuinely dead
glob on
main:@objectstack/specpackages/**/*.object.tsapi-methods-batch-conformance.test.ts+platform-object-names.test.tswalkPACKAGES_DIR, descending withreaddirSync(dir)on a loop variable@objectstack/plugin-authpackages/**/*.object.tsmanaged-extension-fields.test.ts, same walk shape@objectstack/lintpackages/cli/src/commands/**authoring-rule-wiring.test.tsresolvescommandsDir, then readsreadFileSync(join(commandsDir, file))withfilea variable — the roster gets a directory in file position, which it drops@objectstack/downstream-contractpackages/spec/src/**source-resolution.pin.test.tsreaches files asexistsSync(target)wheretargetwas computed out of theexportsmapcreate-objectstackcontent/**template-consistency.test.tsreads throughgit grep -- content/docs; the paths are process outputcreate-objectstackscripts/invoked-as.mjstemplate-version-stamps.test.tsderives the stamper's import closure. This glob's own rationale in the table already says it "appears in NO quoted string the flat literal collector can see"The last row is the decisive one: the file already documented, in its own declaration
prose, that a live glob can be roster-invisible. A limb that reddened every roster-invisible
glob would be red on a healthy tree, and asking the filesystem instead answers a different
question — it would redden the prose-named globs (
serve.ts,check-nul-bytes.mjs,realtime-protocol.mdx) that the flat collector legitimately holds.So a glob is held either MECHANICALLY, by a roster path, or by DECLARATION.
heldBynames the escaping test that reads it, and the witness is checked rather than prose: the
named test must still be one of that package's escaping tests. That is precisely what makes
the required ablation fail — the reseeded test stops being an escaping test at all, so the
witness dies with it — while a healthy tree stays green. Six witnesses are declared here,
one per row above; no glob was widened and none was deleted.
Stated rather than discovered later, and written into the function's docblock: a witness
that stays escaping through some other read while dropping the one that held the glob
still reads as held. The witness is a weaker claim than a roster path, and it is the
strongest one available for a read this detector cannot name.
3. Controls, all four run on
7b8b444474Required ablation — same reseed, after the limb: exit 1, naming the glob and the
dead witness.
It reddens two globs, one more than predicted, and the extra one is correct rather than
noise:
packages/core/src/security/**was on the roster only through a path quoted in thatsame test's prose, so it drops out with the file. Both of that test's radii lose their
holders together.
Negative control — the same reseed on
packages/platform-objects/src/managed-api-method-affordance-sweep.test.ts(that package'sonly escaping test): exit 1 via limb 2, and its output is byte-identical to the
pre-change run (
diffclean). The new limb is skipped there by itsif (!info) continue;guard, so limb 2 was not rebuilt.
Full-tree control — unmodified tree, exit 0:
OK: 13 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.Reverse-verification, both legs from the committed state, each with before/after anchor
counts printed and the tree restored clean afterwards:
globHolderVerdicttounheld: globs.filter(() => false)→ 3/90 self-test casesfail, exactly the three that assert a glob is unheld; the held-direction cases stay
green, and full-tree
verify()stays green — which is why the self-test, not the tree, iswhat pins this limb;
@objectstack/plugin-auth'sheldByentry →verify()exits 1 namingpackages/**/*.object.ts, so the six new declarations are load-bearing rather thandecorative.
Nine
--self-testcases were added in the file's existing style (81 → 90), pinning thewitness in both directions: a case asserting only "a witness makes it green" would pass
just as happily on a witness nothing checks.
4. #10565 — the seed-count prose, per site
The card's table was measured on
ceb33a9f12and is stale and short by one. On this tipthe phrase occurs five times. Each was checked against the seed set that actually
resolves above it — four directory-naming seeds resolve in
pathExpression(
dirname(fileURLToPath(import.meta.url)),__dirname,import.meta.dirname,dirname(import.meta.filename))::919:932:938:954:1104Confirming the brief's reading:
:932is positionally true and survives, and I found nosixth site (
grep -c "two seeds"= 5 before, 1 after — the survivor is:932). No blanketreplace was used; each site is an independently anchored substitution with its before/after
count printed. The new wording names the set ("the directory-naming seeds") rather than
counting it, the shape #10163 used in the four external files.
5. Gates
Run on
7b8b444474, the final commit, after the last edit:check:cross-package-test-inputsAll 90 self-test cases passed.+OK: 13 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.check:examples-live-importsAll 31 self-test cases passed.+examples/** live-import inventory OK -- 0 invisible (declared), 6 inputs-declared, 70 graph-visible.check:ratchet-remedy-authority109 scripts swept … 99 hand out no ratchet-expanding remedy. Control corpus: 19 hand-classified scripts— unchanged from the pre-change baselinecheck:nul-bytesOK (scanned 6215 text file(s) … no raw ASCII control bytes)check:entry-guard123 scripts/ file(s) — every entry guard goes through invoked-as.mjspartition-test-shards --self-testself-test OK (71 measured packages, 6 shards …)dispatch-gates --self-test451 cases pass.node scripts/pm/dispatch-gates.mjs(no paths) named onlycheck:cross-package-test-inputs.The other five were run because of import and control edges the tool cannot see:
check-examples-live-imports.mjsimportsglobToRegExpfrom this file;check-ratchet-remedy-authority.mjscarries a hand-classified control entry for thisfile (
expect: 'excluded'), and this change adds a new author-facing remedy plus a seconddeclaration registry, either of which could have moved that classification — it did not;
partition-test-shards.mjsandscripts/pm/dispatch-gates.mjsboth read this file's--union-intocontract and its source text.pnpm lintwas not run locally (it needs a full workspace install). Declared narrowing:the only eslint rule in scope for
scripts/**/*.mjsisno-restricted-importson@objectstack/specsubpaths, and this diff adds no import.No changeset: gate tooling only, nothing published —
skip-changeset.Generated by Claude Code
Generated by Claude Code