Uh oh!
There was an error while loading. Please reload this page.
Move the cross-package glob predicate and declaration table out of the gate that owns them - #11871
Conversation
…ion table out of the gate `scripts/check-cross-package-test-inputs.mjs` was a gate AND a library. Two other gates imported out of it, and `scripts/pm/dispatch-gates.mjs` cannot follow a gate file, so `check:ci-filter-parity` -- whose population that gate's declaration table literally IS -- derived nothing for a card editing the table. Split the two shared halves into two plain modules that no workflow invokes: scripts/glob-match.mjs globToRegExp / matchesAny, no population scripts/cross-package-test-inputs.mjs CROSS_PACKAGE_TEST_INPUTS, the population Two modules rather than one, on measurement: the follow hands an importer the whole followed module's hints, so a single module would have handed check:examples-live-imports the packages-wide declaration table it never opens. Measured on 589758d, watch-hint (gate, file) pairs: scripts/check-ci-filter-parity.mjs 1 -> 3256 (+3255, honest) check:examples-live-imports 241 -> 241 ( 0, refused) check:cross-package-test-inputs 3253 -> 3255 ( +2, the two new files) Both new modules are inert on import, per check:entry-guard's second rule. glob-match.mjs pins "declares no path population" against its own bytes, and both importers fold that self-test into their own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
os-steve
commented
Aug 24, 2026
ACCEPT — PM review, This card's first dispatch was lost entirely — no branch, no report. The re-dispatch delivered, and improved the card twice. The point of the whole card, demonstrated live on both sidesI ran the derivation myself, on each tree: A card editing the declaration table now derives the gate whose population is that table. Before, it derived nothing. And no derivation rule changed — the table simply stopped living inside a gate, which is what the card said would be sufficient and is now shown to be. ⭐ Two modules, not one — the card's design would have failedThe card prescribes moving both halves into a plain module. You measured why that is wrong: the follow appends a followed module's hints to every importer WHOLE, regardless of which binding it named. So a single module hands Verified independently:
Zero population in the predicate module is the entire justification for the split, and the control makes that zero a reading rather than a grep that found nothing. Result: parity gate 1 → 3256, examples gate 241 → 241 (unchanged, empty
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#11511
scripts/check-cross-package-test-inputs.mjswas a gate and a library. Two other gate scripts imported out of it, andscripts/pm/dispatch-gates.mjsdeliberately never follows into a module that is itself a discovered gate file — socheck:ci-filter-parity, whose population that gate's declaration table literally is, derived nothing for a card editing the table.This moves the two shared halves into plain modules no workflow invokes, the shape
scripts/workspace-enumerator.mjs(#11797) andscripts/i18n-bundle-surface.mjsalready have. No change to the derivation's follow logic — the honest lead is now reachable through the existing follow.Two modules, not one — and that is the measurement, not a preference
The card describes one shared module. That does not work, and the reason is the follow's own semantics: it appends a followed module's watch hints to every importer whole, regardless of which binding the importer named. Pairing the pure predicate with the declaration table would hand
check:examples-live-imports— which wants one string helper and whose own subject isexamples/— the entirepackages/**declaration table, rebuilding the exact fabrication dispatch-gates refuses today, one file further out.So the split is by kind:
scripts/glob-match.mjsglobToRegExp,matchesAnyscripts/cross-package-test-inputs.mjsCROSS_PACKAGE_TEST_INPUTSA binding that is a predicate goes in the first; a binding that is a declaration goes in the second; neither file grows the other's kind. Both headers carry their half of the measurement.
Measured — watch-hint (gate, file) pairs, over 157 families × 6605 tracked files
scripts/check-ci-filter-parity.mjscheck:examples-live-importscheck:cross-package-test-inputsscripts/**it already declaredscripts/check-cross-package-test-inputs.mjsci.ymlThe gate loses nothing: it imports the table, so the follow returns all 103 hints to it, now labelled with the module they came from (
gate source via scripts/cross-package-test-inputs.mjs).The card's 595 figure does not reproduce — it is an undercount
Re-derived rather than restated. The pair definition used here is the one #11190 measured with, confirmed by reproducing its global result on today's tree: the same six families gain, at +907 where that docblock recorded +893 (drift in the two i18n families, +285 → +292 each).
Under that definition the lead
check:ci-filter-paritywas missing is worth 3255 pairs, not 595. The arithmetic is forced: both importers inherit the same 103-hint set from the same file, so their counterfactual gains cannot differ by 5×. Measured on the base tree, admitting gate-file modules to the follow would gain 3253 for the parity gate and 3105 for the examples gate — the latter matching the docblock's 3065 within drift, the former not matching 595 at all. The card's justification holds; it was understated.Non-vacuity
globToRegExp/matchesAnywere lifted verbatim out of the base commit into a standalone module and compared against the new one over every declared glob × every tracked file: 81 globs × 6605 files = 535,005 comparisons, 8,404 true matches, 0 mismatches, plus 10 adversarial cases (7 true, 0 mismatches). The true-match count is the positive control — agreement over an all-false corpus would prove nothing.coversDirectoryis the contrast: it closes overREPO_ROOTandreaddirSync, so it stayed in the gate.['packages/**', 'content/docs/**']inglob-match.mjsturns both importers'--self-testRED naming both literals, and takescheck:examples-live-importsfrom 241 to 5811 pairs — the fabrication the pin exists to stop, observed. Restored byte-identically (sha256 back toc1364e7e, marker count 1 → 0).--self-testfails 2 of 579 — it would have gone green over an empty hint list, the vacuous-pass shape those cases exist to refuse. Restored byte-identically (sha256 back to428a6e10). The comment above them asks for exactly this ("re-point the case at whatever gate then does"). No follow logic was touched.Inertness
Both new modules are declarations and functions only — no CLI, no top-level statement that runs anything, per
check:entry-guard's second rule (the rule that exists because importing a gate for its exports used to run the gate).check:entry-guardpasses with 107 exporting files, 97 inert, its shrink-only known-unsafe ledger untouched.glob-match.mjshas nocheck:name of its own by design, so folding itsselfTest()into both importers'--self-testis its CI coverage — the same arrangementworkspace-enumerator.mjsuses. Its fixtures are assembled from segments rather than spelled, so its own no-population pin (stricter than the real scanner, and therefore only ever able to refuse more) holds against its own bytes.Verification — all at
f9ed46290, on a clean treeGate union derived with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(no paths — from the merge base, working tree included), then every derived family run:Repo-wide lint run in full rather than narrowed:
eslint . --no-inline-config, 5030 files, 0 errors, 0 warnings, 52s.No changeset: this touches
scripts/tooling only and publishes nothing.Generated by Claude Code