Repository navigation
finding: dispatch-gates cannot name check:examples-live-imports — its root literal is the bare word examples, the same class #10114 fixed for check-role-word #10314
Description
Activity
Claim — PM dispatch (devx seat)
- Session:
c970724d-303c-5614-9d20-a3f92205cfad - Branch:
claude/issue-10314-examples-watch-hints
Premise re-verified on
origin/main=4bafdde0f2(fetched 2026-08-21T09:37:05Z):scripts/check-examples-live-imports.mjs:266is stillconst root = join(REPO_ROOT, 'examples');
and the file declares noROOT_DIR_WATCH_HINTS. The gate is still unreachable by construction.- I confirmed it from the tool's own mouth as well:
node scripts/pm/dispatch-gates.mjslists
4 unreachable BY CONSTRUCTION families, and this is one of the 2 escapable ones —
"dead: 'examples' — the tree HAS it; the covering rule refuses the literal as too generic (no
path separator)". The other two are package names that were never repo paths, which is a
different species.
Serial constraint cleared. PR #10704 (the entry-guard burn-down) touched this file and
merged at 09:34:57Z; its guard sits at:125/:764, well away from the hint declaration.⭐ The #10114 precedent is complete and I am pointing the dev straight at it —
scripts/check-role-word.mjs:106declaresconst ROOT_DIR_WATCH_HINTS = ['skills/**'];, and
:625-628carries the both-directions self-test coupling triage asked for: every
separatorless root must be declared, and every declaration must name a root the gate actually
walks. Its stated reason is the one that matters here — "a declaration that can drift from the
scan is worse than none — it replaces a silent gate with a lying one."The card's open half (whether the
packages/**test-inventory side also needs declaring) goes to
the dev to answer with a measurement, not a guess, per triage.Related: I filed #10705 an hour ago on the class — two of the four unreachable families are
this escapable species and nothing tracks whether the escape has been taken. ⛔ That card does
not supersede this one; #10314 remains the right individual fix and #10705 says so.
Generated by Claude Code
- Session:
{ "issue": 10314, "status": "done", "branch": "claude/issue-10314-examples-watch-hints", "pr": "https://github.com/objectstack-ai/objectstack/pull/10727", "premise_still_valid": true, "summary": "Gave scripts/check-examples-live-imports.mjs the #10114 treatment. SCAN_ROOTS makes the two walked roots data (replacing two inline join(REPO_ROOT, ...) literals), each record carrying WHICH PART of that root is the gate's population: 'examples' wholesale, 'packages' filtered to test files that live-import an example app. ROOT_DIR_WATCH_HINTS = ['examples/**'] is provenance only, and the self-test pins the coupling in both directions derived from SCAN_ROOTS rather than re-spelled, plus a third pin refusing the filtered root. The derivation moved: check:examples-live-imports is now MATCHED for an examples/** path via gate source 'examples/**' (unreachable-by-construction 4 -> 3, matched 4 -> 5, silent 81 -> 80), and does NOT derive for packages/**, content/docs/** or turbo.json. PR #10704's isEntrypoint guard was not touched and the file stays off check:entry-guard's SHRINK-ONLY ledger (verified: 10 entries, none this file). Note: the platform appended a second copy of the signoff footer to the PR body after creation; my body is otherwise intact byte-for-byte and I left that alone rather than 'correcting' another actor's write.", "measured_answer_packages_half": "The packages/** test-inventory side stays UNDECLARED, on measurement. It is unreachable too, and more deeply than the examples side: 'packages' carries no separator, so extractWatchHints DROPS it before hintCovers is ever consulted -- it is not even a dead hint, which is why the residue block never named it. It is also not reached by the test-file convention trigger (this gate is absent from CHANGE_KIND_GATES). Measured: dispatch-gates for packages/cli/test/i18n-section-coverage.test.ts -- the very file #8754 went red on -- names this gate nowhere. It stays undeclared because the INSTRUMENT cannot express this side's population: a root hint covers a whole subtree, so packages/** would name this gate for all 4861 tracked files under packages/ to reach the 76 that actually carry a coupling (dogfood 72, cli 2, lint 2, from the gate's own --json inventory) = 1.6% precision pasted into every packages/** prompt, a worse ratio than the wholesale admission hintCovers already refuses at +139084 pairs. Demonstrated, not asserted: with packages/** temporarily declared, packages/spec/src/index.ts and packages/objectql/src/engine.ts both derive this gate and testFiles() reads neither. By contrast examples/ has 239 tracked files, effectively all in the population (couplingTarget resolves to arbitrary paths beneath it). The refusal is pinned in the self-test, not left in prose.", "tests": "All at fa77e513bf (final commit, clean working tree); exit codes captured by redirecting to a file BEFORE reading, never through a pipe; each verdict line below is the gate's own printed text. pnpm check:examples-live-imports EXIT=0 -- 'All 36 self-test cases passed.' + 'examples/** live-import inventory OK -- 0 invisible (declared), 6 inputs-declared, 70 graph-visible.' (self-test 31 -> 36 cases). pnpm check:pm-dispatch-gates EXIT=0 -- 'dispatch-gates self-test: 463 cases pass.' pnpm check:cross-package-test-inputs EXIT=0 -- 'OK: 13 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.' pnpm check:nul-bytes EXIT=0 -- 'check-nul-bytes: OK (scanned 6226 text file(s) ... )'; plus a manual control-char scan of the changed file: none. pnpm check:parse-guard EXIT=0 -- 'check:parse-guard: 124 scripts/ file(s)' (NOT named by the derivation; it is the same escapable species. Run because I could see this edit is a scripts/ file). pnpm check:entry-guard EXIT=0 -- 'check:entry-guard: 125 scripts/ file(s)'. Union re-derived at the same commit with 'node scripts/pm/dispatch-gates.mjs' and NO paths (1 path vs merge base 4bafdde0f, committed 1 / worktree 0 / untracked 0); it names check:cross-package-test-inputs and check:examples-live-imports, both run above. BEFORE/AFTER for examples/app-showcase/src/ui/views/contact.view.ts -- BEFORE: residue '4 unreachable BY CONSTRUCTION ... - pnpm check:examples-live-imports [lint.yml] dead: 'examples' -- the tree HAS it; the covering rule refuses the literal as too generic (no path separator)'. AFTER: matched list gains '- pnpm check:examples-live-imports [lint.yml] matched via examples/app-showcase/src/ui/views/contact.view.ts => gate source 'examples/**''; residue 3 unreachable / 5 matched / 80 silent. REJECT side: packages/cli/test/i18n-section-coverage.test.ts, packages/spec/src/index.ts, content/docs/index.mdx, turbo.json all 0 matches; scripts/check-examples-live-imports.mjs matches via 'gate script' identity (pre-existing, correct). ABLATIONS (this gate runs from source, so no build/dist leg exists; each mutation confirmed ON DISK by counting the injected AND the removed text, never the editor's exit code, and each restore re-counted to 0 mutant / 1 target / clean git status): (a) delete the declaration -- predicted 2 of 36, measured 2 (the wholesale-root assertion and the explicit examples/** pin); (b) declare a root the gate does not walk (skills/**) -- predicted 1, measured 1 (drift assertion); (c) declare the filtered root (packages/**) -- predicted 1, measured 1 (refusal assertion). All three matched prediction exactly.", "open_questions": [], "out_of_scope_findings": [ "none filed. The one sibling I observed -- check:parse-guard, bare literal 'scripts', the same escapable species, still printing as unreachable in my AFTER run -- is ALREADY recorded in #10705's table (verified by reading that issue, which names both check:parse-guard and this card). Filing it again would duplicate an open finding." ], "labels_verified": [ "size/m", "skip-changeset" ] }
Generated by Claude Code
Observation only — no gate is red. Found while implementing #9932 (
scripts/check-role-word.mjs), whose own dispatch derivation printed this on the way past. Filed unassigned.What the tool says about itself
node scripts/pm/dispatch-gates.mjs, run for an unrelated card, prints:examples/plainly exists and the gate plainly reads it —scripts/check-examples-live-imports.mjs:253isconst root = join(REPO_ROOT, 'examples');. The extractor reads code string literals, and the only one describing this gate's population is the bare single-segment wordexamples, whichhintCoversrefuses by design (the measured reason: accepting bare top-level directory words was priced at +139084 fabricated pairs, becausepackages,appsandexamplesare path components in dozens of gates that never read those roots). Theexamples/**spellings elsewhere in that file are in comments and in author-facing message text, which the extractor does not read.Net effect: the gate scores the same quiet
unreachablefor every card in the tree, so no dispatch brief ever names it — including a brief for the one edit that is most likely to break it.Why this is the escapable species, not the dead one
The residue block groups this with two genuinely dead literals (
@objectstack/driver-memory,@objectstack/types— package names that were never repo paths). This one is different, and the tool's own text says so:That is precisely what PR #10114 did for the sibling case:
check-role-word.mjswalkscontent/docs(separator, extractable) andskills(no separator, refused), and it now carriesas provenance only — nothing in the gate reads it.
check-examples-live-imports.mjsis the same shape one root over.Why it is worth someone's time
The gate exists because an edit inside an example app can break a
packages/**test that imports it live, and the affected-subset CI model structurally cannot see that coupling (#8754 records the measurement and the merge-queue failure that produced it). The card most likely to break this gate is therefore anexamples/**edit — exactly the card for which the derivation currently names nothing. That is the same cost shape theskillshalf paid before #10114: PR #10038 derived a green local union and metcheck-role-wordas red CI, costing one repair round.CI enforces either way (
lint.ymlcarries no path filter for this job), so nothing is unprotected. What is missing is discoverability.Not asserted
No claim that
examples/**is the whole right declaration. The gate's population arguably has two halves — the example apps it reads, and thepackages/**tests whose imports it inventories — and whether both, one, or neither should be declared is a judgement this issue does not make. Whoever takes it should also check the self-test discipline #10114 landed alongside its declaration: the coupling is asserted in both directions, so a declaration cannot drift from what the gate actually walks.Related: #9932 (the sibling half of this file's own root handling), #9964 (the declaration pattern), #10038 and PR #10114 (the measured cost and the fix for the
skillsroot), #8754 (why this gate exists), #9955 (a different, non-escapable species of the same blind spot).Generated by Claude Code