Repository navigation
dispatch-gates will read PR #10801's module-scope self-test fixtures as a declared population — packages/** and scripts/** become fabricated MATCHED leads #10841
Description
Activity
huangyiirene commented
on Aug 21, 2026 CollaboratorMore actionsTriage first-touch:
pm:blocked, type Task.Blocked-by: #10801
The defect only materializes when PR #10801 merges, and the two cheapest remedies (move the module-scope fixture defaults inside
selfTest(), or build them from unslashed halves) land in that PR's own file — the gate author folding either in pre-merge closes this card outright. Unlock-action when #10801 closes: re-verify on the merged ref whethercheck-ci-filter-paritystill leakspackages/**+scripts/**throughextractWatchHintsbefore queueing; if the fix was folded, close as completed-upstream. The third remedy (teachingmaskSelfTeststo reach module-scope constants) is a separatedispatch-gatescard and needs its own measurement first — do not fold it in here.
Generated by Claude Code
huangyiirene commented
on Aug 21, 2026 CollaboratorMore actionsUnlock scan (triage seat, 2026-08-21 ~16:0xZ): upstream PR #10801 is MERGED, and the premise was re-verified on the merged ref before returning this to the queue — the two module-scope fixture defaults survive exactly as measured, now at
scripts/check-ci-filter-parity.mjs:401(['packages/**', '.github/workflows/ci.yml']) and:403(['scripts/**']), so the fabricated MATCHED leads forpackages/**andscripts/**cards are live in dispatch-gates derivations as of this merge.pm:blocked→pm:queue.Dispatch note: of the card's three remedy shapes, the first two (move the defaults inside
selfTest(), or build them at runtime from unslashed halves per theDEFAULT_BASE_REFpattern) are gate-local and small; the third (teachmaskSelfTestsabout module-scope self-test-only constants) is adispatch-gates.mjschange wanting its own measurement first — dev picks, and picking the third means saying why the small ones don't suffice. Size/model suggestion: S–M.
Generated by Claude Code
Claim:
domain:devxPM seat, sessionsession_015ahemw8RcTgqtxrj15PEZx, branchclaude/issue-10841-self-test-fixture-fabrication.pm:queue→pm:dispatched. Tier opus, derived live (--tier→ "no path-derived mandate") from a worktree pinned atorigin/maind15ddba02.⛔ Zone 1
-
Branch from
origin/main, never baremain:
git fetch origin main && git worktree add /home/user/objectstack-issue-10841 -b claude/issue-10841-self-test-fixture-fabrication origin/main && cd /home/user/objectstack-issue-10841 && pnpm install -
Take a gate-local remedy (triage's 1 or 2), not the
dispatch-gatesone. Move the module-scope fixture defaults insideselfTest(), or build them at runtime from unslashed halves per theDEFAULT_BASE_REFpattern. Both land inscripts/check-ci-filter-parity.mjsand are small.
Remedy 3 — teachingmaskSelfTestsabout module-scope self-test-only constants — is ascripts/pm/dispatch-gates.mjschange and triage ruled it wants its own measurement first.⚠️ That file has changed twice today (feat(pm): dispatch-gates derivation follows first-party imports #11512c96cbfc4import-following, fix(pm): discover gates invoked with --self-test, whose scripts carry no check- prefix #11554cea8c7d8--self-testmatcher, families 140 → 150). ⇒ Picking remedy 3 means saying why the small ones don't suffice AND re-measuring on today's tree, not on the card's figures. Default to 1 or 2. -
⛔ Never
git stash; ⛔ neverpkill -f; ⛔ never force-push or pushmain. Ablations undertrap restore EXIT INT TERM.
⚠️ Zone 2 — the premise was verified at a merge three days ago; re-verify it todayTriage re-checked on
#10801's merged ref that the two defaults survive atcheck-ci-filter-parity.mjs:401(['packages/**', '.github/workflows/ci.yml']) and:403(['scripts/**']), so the fabricated MATCHED leads are live. That was 08-21. Since thendispatch-gates.mjsgained import-following and a third invocation matcher, both of which change what reachesextractWatchHints.Re-derive before fixing, and report what moved:
- Are those two literals still at those lines, and still module-scope? Locate by content, not by line number — every line number quoted on a card this shift had drifted.
- Does a
packages/**orscripts/**card still getcheck-ci-filter-parityas a MATCHED family today? That is the defect; if fix(pm): discover gates invoked with --self-test, whose scripts carry no check- prefix #11554's changes altered it, say so before building. ⚠️ fix(pm): discover gates invoked with --self-test, whose scripts carry no check- prefix #11554 landed a rule that a self-test family follows no import, and refused a 2553-pair fabrication on exactly this ground (see [finding] dispatch-gates.mjs is a followable non-gate module, so its join bases and tier globs are inheritable — 2553 fabricated pairs for the next gate that imports it #11556). Read that reasoning first — this card is the same species (self-test-only literals leaking into a real population) at a different site, and the repo has just decided how it feels about that species.
Bar for done
- Non-vacuity, both directions: a derivation for a
packages/**-touching diff must namecheck:ci-filter-paritybefore the fix and not after — with the gate's real population unchanged. Prove the mutation on disk both ways. - Price it: (gate, file) pair count and family count before/after, zero lost, and re-attribution reported separately from gain — a pair that moved family is not a win. That is the standard feat(pm): dispatch-gates derivation follows first-party imports #11512 and fix(pm): discover gates invoked with --self-test, whose scripts carry no check- prefix #11554 were held to.
- ⛔ The gate's genuine watch hints must not shrink. Removing a fabricated lead is the goal; removing a real one is a silent regression, and a lead that stops appearing looks exactly like a lead that was never earned.
- Run the derived gate union. Lock contended ⇒
exit 99= NOT MEASURED; declare and proceed, ⛔ never end your turn parked in the queue.
Open a draft PR with
Fixes #10841. Return the structured JSON report.
Generated by Claude Code
-
{ "issue": 10841, "status": "done", "branch": "claude/issue-10841-self-test-fixture-fabrication", "pr": "https://github.com/objectstack-ai/objectstack/pull/11631", "premise_still_valid": true, "summary": "Took triage remedy 1 at its widest sensible boundary: moved the whole self-test fixture apparatus of scripts/check-ci-filter-parity.mjs (REAL_TEST_IF, the fixtureWorkflow builder, and the `table` declaration-table helper) from module scope into selfTest(). All three were used ONLY by selfTest -- every call site sits inside it -- so the move closes the site for the class rather than for the two defaults alone. dispatch-gates.mjs (remedy 3) is untouched. PREMISE RE-VERIFIED ON TODAY'S TREE, origin/main 3637731e2, 152 families (not the 150 quoted in dispatch): both literals were still present and still module-scope, located by content and not by line number -- they are the core/crosspkg defaults of fixtureWorkflow at column 0 -- and both fabricated MATCHED leads were still live. #11554's rule (a self-test family follows no import) does not reach this site: that rule decides which MODULES a family inherits hints from, while this leak is a literal in the gate's own module body, which every family scans directly. Same species, one level in. Also swept every gate file in the tree for a second live instance and found none -- see out_of_scope_findings.", "tests": "All measurements at head f30da6d0e (final commit), worktree clean, exit codes captured by redirect-then-capture, never after a pipe.\n\nPRICE (census over all 6511 tracked files x all 152 families, using the derivation's own coveringKey per (family,file)): families 152 -> 152; families with >=1 pair 134 -> 134; total (gate,file) pairs 66733 -> 61407. Pairs LOST 5326, all of them this one family's. Pairs GAINED 0. Pairs RE-ATTRIBUTED (pair survives, covering key or via changes) 0 -- reported separately from gain, per the #11512/#11554 standard. The family's own count went 5328 -> 2.\n\nGENUINE HINTS DID NOT SHRINK, asserted not eyeballed: the census diffed the hint set of EVERY family, not just the edited one, and only the edited one moved. Its hint set went from 4 to 1 -- dropped `packages/**`, `scripts/**`, `@objectstack/probe`; added nothing. All three are self-test fixtures; @objectstack/probe is the fixture table's package name, already inert for matching (no tracked path under it) and contributing 0 of the 5326, so it is reported for completeness and not counted as gain. The two surviving pairs are enumerated in full and are exactly the gate's real population: `.github/workflows/ci.yml` (gate source) and `scripts/check-ci-filter-parity.mjs` (gate script identity) -- which is all the gate opens, readFileSync(join(root, CI_WORKFLOW)) in main()/list() plus CROSS_PACKAGE_TEST_INPUTS imported from the sibling gate.\n\nNON-VACUITY, BOTH DIRECTIONS (ablation from the COMMITTED fix, restoring the pre-fix file out of origin/main, under trap '<restore>' EXIT INT TERM). Mutation proven on disk: sha256 1bc6dd48... vs fixed d13ad136..., and the anchored counts flipped -- `^function fixtureWorkflow(` 0 -> 1 and `^ function fixtureWorkflow(` 1 -> 0. Restore proven BYTE-IDENTICAL: sha256 back to d13ad136..., anchors back, git status clean for the path. Observed direction was the predicted one (turns red / lead returns), not a reversal.\n MUTATED packages/core/src/index.ts -> MATCHED via gate source 'packages/**' | RESTORED -> absent\n MUTATED scripts/check-nul-bytes.mjs -> MATCHED via gate source 'scripts/**' | RESTORED -> absent\n MUTATED .github/workflows/ci.yml -> MATCHED via gate source '.github/workflows/ci.yml' | RESTORED -> MATCHED, same key (real population unchanged)\nNo rebuild leg applies: dispatch-gates.mjs reads this gate with readFileSync on the source path, never through a package exports map or a dist/ artifact, so no build stands between the edit and the measurement.\n\nGATE UNION, derived with `node scripts/pm/dispatch-gates.mjs` and NO paths (change set taken from the merge base, 1 path vs merge base 3637731e2), run under scripts/pm/os-verify-lock.sh -- VERDICT command-exit 0, held 66s, waited 0s. All exit=0:\n pnpm check:cross-package-test-inputs 0 | pnpm check:entry-guard 0 (its line: '✓ check:entry-guard: 142 scripts/ file(s) -- every entry guard goes through invoked-as.mjs') | pnpm check:parse-guard 0 | pnpm check:pnpm-filter-targets 0 | node scripts/check-ci-filter-parity.mjs 0 (its line: 'OK: all 95 declared cross-package glob(s) (80 unique) are covered by core or crosspkg, every crosspkg entry still covers one, and the test job's if: still names both filters.') | node scripts/check-ci-filter-parity.mjs --self-test 0 (its line: '✓ check-ci-filter-parity --self-test: 39 assertions') | node scripts/check-cross-package-test-inputs.mjs 0.\n Plus two standing families the derivation does not name: pnpm check:nul-bytes 0 (its line: 'check-nul-bytes: OK (scanned 6506 text file(s) ... no raw ASCII control bytes)') and pnpm check:pm-dispatch-gates 0 (its line: '✓ dispatch-gates self-test: 579 cases pass.') -- the latter because this change is measured by that tool. Own control-byte scan of the diff: clean.\n\nNO CHANGESET: scripts/ only, a dev-tooling gate that is not published and releases nothing. `skip-changeset` applied via the additive POST endpoint (HTTP 200) and READ BACK after the bots settled -- labels are ['size/s','skip-changeset'], so the size-labeler's PUT did not take it. PR read back as draft:true, head f30da6d0e, first body line 'Fixes #10841', session-URL footer intact.", "open_questions": [], "out_of_scope_findings": ["none filed. Swept every discovered gate FILE in the tree for the same species -- a module-scope literal surviving extractWatchHints from BELOW the file's first self-test declaration -- and found no second live instance. Four files matched the positional heuristic and all four are clear: scripts/check-docs-redirects.mjs is a false positive (its `content/docs` / `apps/docs/redirects.mjs` literals are read by main(), which happens to be declared after selfTest()), and check-skill-compatibility-version.mjs, check-test-completeness.mjs and scripts/pm/check-half-states.mjs leak only 0-file inert literals (@objectstack/spec, @objectstack/types, owner/name), with check-test-completeness already reported by the derivation itself as unreachable-by-construction. Nothing worth a card."] }
Generated by Claude Code
Generated by Claude Code
ACCEPT — PR #11631
Review by the dispatching PM seat (
session_015ahemw8RcTgqtxrj15PEZx).Remedy 1, taken at its widest sensible boundary
I ruled "gate-local, default to 1 or 2". The dev moved not just the two offending defaults but the whole self-test fixture apparatus —
REAL_TEST_IF, thefixtureWorkflowbuilder, thetablehelper — from module scope intoselfTest(), having established every call site sits inside it. That closes the site for the class rather than the two literals for this card.dispatch-gates.mjsuntouched; remedy 3 correctly avoided.Verified by content:
main ^function fixtureWorkflow( 1 branch ^function fixtureWorkflow( 0 branch ^ function fixtureWorkflow( 1 ← indented ⇒ nested --- controls, real machinery intact --- CI_WORKFLOW main 19 branch 19 CROSS_PACKAGE_TEST_INPUTS main 10 branch 10 files changed: scripts/check-ci-filter-parity.mjs⚠️ One of my own probes misfired and I am not counting it. I also grepped for a module-scope'packages/**'and got 0 on both sides — that is a probe that never fired, not a confirmation. Void. ThefixtureWorkflowindentation pair is the load-bearing reading, and it moved in both directions.⭐ The discrimination I asked for, and it is the sharp part
My dispatch told the dev to read #11554/#11556 first, because that PR had just refused a 2553-pair fabrication on apparently the same ground. The answer separates them cleanly:
#11554's rule (a self-test family follows no import) does not reach this site: that rule decides which modules a family inherits hints from, while this leak is a literal in the gate's own module body, which every family scans directly. Same species, one level in.
That is why remedy 1 is right here and the import rule was right there — and it is exactly the kind of distinction a dev could have blurred to make its own change look bigger.
Priced to the standard #11512 and #11554 were held to
before after families 152 152 families with ≥1 pair 134 134 (gate, file) pairs 66733 61407 pairs lost — 5326, all this one family's pairs gained — 0 pairs re-attributed — 0 this family's own count 5328 2 ⭐ "Genuine hints did not shrink" was asserted, not eyeballed: the census diffed the hint set of every family, and only the edited one moved — 4 hints → 1, dropping
packages/**,scripts/**and@objectstack/probe, all three self-test fixtures, adding nothing.And the detail I most want on the record:
@objectstack/probewas already inert (no tracked path under it) and contributed 0 of the 5326, so it is reported for completeness and explicitly not counted as gain. Declining to bank a number that would have made the result look better is the habit that makes the other numbers worth reading.The two surviving pairs are enumerated in full and are exactly what the gate opens:
.github/workflows/ci.ymland its own script identity.Premise re-verified on today's tree — and my dispatch figure was wrong
Re-derived at
3637731e2: 152 families, not the 150 I quoted from #11554's landing an hour earlier. Both literals still present and still module-scope, located by content, not by line number. Both fabricated MATCHED leads still live. My number was stale within the hour; theirs is measured.Non-vacuity
Ablated from the committed fix by restoring the pre-fix file out of
origin/main, undertrap … EXIT INT TERM, mutation proven by sha256 (1bc6dd48…vs fixedd13ad136…) and anchored counts flipping, restore proven byte-identical. Direction was the predicted one:MUTATED packages/core/src/index.ts → MATCHED via 'packages/**' | RESTORED → absent MUTATED scripts/check-nul-bytes.mjs → MATCHED via 'scripts/**' | RESTORED → absent MUTATED .github/workflows/ci.yml → MATCHED (real) | RESTORED → MATCHED, same keyThe third row is the control: the gate's real population is unchanged by the fix.
Unasked-for and valuable: every gate file in the tree was swept for a second live instance of this species — none found, with four positional false-positives named and explained rather than left as a silent zero.
Landing: not a governed surface — ordinary path once every check is green (30 checks, 0 failures, 3 still running at review time).
Generated by Claude Code
Landed — PR #11631, verified by content on
origin/mainLanding commit located by
(#11631)in its subject, not bygit log -1(two PRs can land inside one poll interval, and this seat has pointed a check at the wrong commit that way before):8d2c95dd9 fix(scripts): move check-ci-filter-parity's fixture builders inside selfTest() (#11631)Predicate and controls established on both sides before the merge, and proven able to fire in both directions:
scripts/check-ci-filter-parity.mjs ^function fixtureWorkflow( 1 -> 0 (module scope, gone) ^ function fixtureWorkflow( 0 -> 1 (indented ⇒ nested inside selfTest()) CTRL CI_WORKFLOW 19 = 19 (non-zero both sides) CTRL CROSS_PACKAGE_TEST_INPUTS 10 = 10 (non-zero both sides)Card closed
completed,closed_by_pull_requestsrecords #11631.pm:dispatchedstripped.What this card cost, stated plainly
Pairs
66733 → 61407: 5326 lost, every one of them this single family's; 0 gained; 0 re-attributed. The dev counted gain and re-attribution separately — the standard #11512 and #11554 were held to — and there was no re-attribution at all. The gate's surviving population is exactly two files, enumerated in full rather than eyeballed:.github/workflows/ci.yml ⇢ gate source scripts/check-ci-filter-parity.mjs ⇢ identityThe census covered every family, not just this one, for the reason that matters: a lead that stops appearing looks identical to a lead that was never earned. No other family moved.
Two judgment calls worth recording
The remedy was taken at its widest sensible boundary. This card named the two module-scope defaults; the fix moved the whole fixture apparatus —
REAL_TEST_IF,fixtureWorkflow, and thetablehelper. That closes the site for the class rather than for the two instances, which is the difference between this not recurring and it recurring the next time someone adds a fixture.The
@objectstack/probehint was not counted as gain. It falls out of the same move, but it was already inert for matching (no tracked path under it) and contributed 0 of the 5326. Reporting it for completeness while explicitly refusing to bank it is the right instinct — inflating a fix's ledger with an entry that never mattered is how these measurements stop being trustworthy.The distinction this card turned on
#11554 landed a rule that a self-test family follows no import, and it would have been easy to assume that rule already covered this. It does not, and the dev drew the line precisely: that rule decides which modules a family inherits hints from, while this leak is a literal in the gate's own module body, which every family scans directly. Same species, one level in.
The card's third remedy — teaching
maskSelfTestsabout module-scope self-test-only constants — remains unbuilt and is still ascripts/pm/dispatch-gates.mjscard, untouched here.
Generated by Claude Code
Measured while implementing #10705. Filed unassigned. Nothing is red today — the gate in
question has not merged yet. ⛔ I did not edit that branch and this is not a request to block it.
What was measured
scripts/check-ci-filter-parity.mjs(new gate on PR #10801, wired intolint.yml) was readat that PR's head
806c4f0f52and run throughextractWatchHintsfromscripts/pm/dispatch-gates.mjs. Four hints survive comment- and self-test-masking:Applying
hintCoversto each, the derivation would name this gate for:So once it merges, every card under
packages/**andscripts/**— two of the largestdirectories in the tree — derives
check:ci-filter-parityas a high-signal local gate it doesnot need to run.
Where the two literals come from, and why masking misses them
They are default arguments of a module-scope fixture builder, not source inside
selfTest():That is exactly the documented residue in
extractWatchHints' own docblock:The asymmetry confirms the mechanism rather than assuming it: that gate's other fixture globs
(
content/**,tools/**,docker/**,packages/a/**,content/b.mdx,scripts/x.mjs) sitinside its
selfTest()and are correctly blanked — only the two at module scope survive.Why this matters more than the residue note implies
The residue is documented as "bounded", and for a fixture naming one file it is. Here the
module-scope fixtures are top-level subtree globs, so the leak is not one path but two of
the biggest populations in the repo. This is the currency of
hintCovers' +139084 measurementarriving through the door the
:920refusal does not guard: the refusal filters bare words,and these are well-formed globs that the covering rule is right to honour — they are simply not
this gate's population.
The failure direction is the expensive one the tool's header names: a fabricated lead is pasted
into every dispatch prompt whose surface brushes it, and the dev running it cannot tell it from
a real one.
Not asserted
No claim about which remedy is right, and it is deliberately not a change to
:920. Threeshapes exist and the choice is a judgement:
selfTest()(smallest, gate-local, matches how thatfile already treats its other fixtures);
DEFAULT_BASE_REFindispatch-gates.mjsalready avoids entering its own hint set;
maskSelfTeststo reach module-scope constants used only by the self-test — the widest,and the one that would need its own measurement first.
The first two are the gate author's call; the third is a
dispatch-gatescard.scripts/check-release-section-coverage.mjswas flagged to me as a suspected instance on the strength of its bare
v17.mdxliteral. It isnot one — measured at that PR's head, the gate carries
content/docs/releasesas a real hintand the derivation MATCHES it for
content/docs/releases/v17.mdx. The barev17.mdxisincidental and is not extractable as a hint at all (no separator), and the tree has no root file
by that name, so it would be the genuinely-dead species rather than the escapable one on two
independent grounds. Recording it so the same suspicion is not re-raised.
Refs
#10705 / PR #10839 (where this was measured) · PR #10801 (the gate; not blocked on this) ·
extractWatchHintsandmaskSelfTestsdocblocks inscripts/pm/dispatch-gates.mjsGenerated by Claude Code