Repository navigation
[finding] A mid-segment * collapses to a double slash, so skills/*/references/_index.md names 9 real files and reaches none — and the family is filed as "THE LAYOUT MOVED" #12246
Description
Found while landing #12107 (the resolveCheckToFiles extension fix). ⛔ Not fixed there — that
card's declared surface is resolveCheckToFiles and the discoverFamilies package-filter branch,
and scripts/pm/dispatch-gates.mjs is the head of a four-card serial queue (#12074 option 1,
#11556, #11199). Filed unassigned, not graded.
This is only visible because of #12107: before it, all 23 TypeScript-authored gates resolved to
zero files, so their sources were never opened and none of their literals reached the residue. With
those sources opened, the unreachable listing goes from 2 families to 12, and two independent
defects come with it.
1. collapseHint turns a mid-segment * into a double slash
collapseHint('skills/*/references/_index.md') // → 'skills//references/_index.md'
hintCovers('skills/*/references/_index.md', 'skills/objectstack-ai/references/_index.md') // → falseskills//references/_index.md is not a path any tree can hold, so the literal matches nothing by
construction. Measured on this tree: 9 tracked files match that glob as a shell would —
$ git ls-files 'skills/*/references/_index.md' | wc -l
9
— and the derivation reaches none of them. This is not a declaration the gate got wrong; it is a
real population spelled in a glob shape the covering rule cannot expand. The trailing-** shape
(content/docs/references/**) works, so the gap is specifically a * in a middle segment.
check:skill-refs is the live specimen: packages/spec/scripts/build-skill-references.ts
generates those nine _index.md files, and after #12107 the family resolves its source, reads the
declaration, and still matches nothing in the tree.
2. unreachableClass files that family under "THE LAYOUT MOVED"
1 where THE LAYOUT MOVED under a gate that still spells the old path — a real miss, worth triaging:
- pnpm --filter @objectstack/spec run check:skill-refs [lint.yml] dead: 'lib/generated-output' —
no tracked path under its first segment; never was a repo path · '@objectstack/spec' — … · 'data/field.zod.ts' — …
The layout did not move. unreachableClass returns 'layout moved' when any dead hint has a
deepest that differs from its collapsed form, and the double-slash collapse above is what makes
that true. The heading then promotes the row above the "BY CONSTRUCTION" block as "a real miss,
worth triaging" — and the three reasons it prints are not the glob at all, they are TypeScript
module specifiers (defect 3), so a reader who follows the lead is sent to look for a moved
lib/generated-output that never existed.
Both halves of the output are wrong in the same row: the classification, and the evidence shown
for it.
3. TypeScript module specifiers are admitted as watch hints
extractWatchHints was written against .mjs gates, whose relative imports carry a leading ./.
The packages/spec TypeScript gates use paths-mapped bare specifiers that look pathy:
lib/dist-freshness · lib/generated-output · lib/schema-name · src/kernel/protocol-version
data/field.zod.ts · ui/view.zod.ts · @objectstack/spec · typescript/bin/tsc
Measured across the 19 families #12107 gives coverage to: 19 hints are live (they cover at
least one tracked file) and 128 are inert (they cover zero).
⭐ The inert 128 fabricate no MATCHED pair — that was measured for #12107 and is why it landed:
a hint covering zero files cannot put a gate in any card's matched column. The cost is confined to
the residue, and it is real there: they are what unreachableReason prints, so a family's genuine
reason (defect 1) is displaced by three specifiers that were never repo paths.
data/field.zod.ts and src/kernel/protocol-version
are one top-level data/ or src/ directory away from covering real files, and at that point they
would fabricate in the matched column rather than the residue.
Not asserted
No claim about which remedy is right, or whether the three are one fix or three. Some shapes:
- teach
collapseHint/hintCoversa mid-segment*(widest, and it is the one that recovers the
9 files); - have
unreachableClassnot read a collapsed form that contains//as evidence of a move
(narrowest, and it removes the false lead without recovering anything); - give
extractWatchHintsa way to tell apaths-mapped TS specifier from a repo path — note the
hint set is fleet-wide, so this one is priced the wayfirstPartyImportTargets' docblock prices
a widening.
Reproduce
At any commit carrying #12107's fix:
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
# → Unreachable — the 12 famil(ies) …
# 1 where THE LAYOUT MOVED …: check:skill-refs
Refs: #12107 (the fix that surfaces this) · #11556 · #12074 · #11199 (the rest of the serial queue
on this file) · #12064 (adjacent worklist-output defect, different mechanism)
Activity
fix(pm): record the two skills bare-root verdicts the TS gate fix sur…
yinlianghui commentedon Aug 25, 2026
Claim —
domain:devx @ objectstackseatClaim:sessionaf22b339-91b5-5814-8b9b-2453fc5b3f68· branchclaude/issue-12246-collapse-hint-mid-segment-glob· worktree../objectstack-12246· model tier opus.Declared file surface:
scripts/pm/dispatch-gates.mjs—collapseHintand its--self-test. Nothing else.Clause ②: no. A derivation-internal string transform; no shipped accept/reject door moves.
Unblocked by: PR #12247, merged
0b478e1eff. It was the open claimant on this file.Premise re-verified on the merged ref, not inherited
collapseHintis unchanged by #12247 and still reads:Run against the merged tree:
skills/*/references/_index.mdskills//references/_index.mda/*/b/*/c.tsa//b//c.tspackages/**packagesscripts/**scriptsgit ls-files 'skills/*/references/_index.md'→ 9. So the hint names nine tracked files and reaches none, exactly as filed.⭐ Why this survived: the trailing case is correct. Both trailing strips are anchored (
/\/+$/,/\/$/), so they clean uppackages/**→packagesbut cannot touch a double slash left mid-string. Every hint in the tree that anyone happened to check was a trailing glob.Serial constraint — three cards still held behind this file
#12074, #11556, #11199 all name
scripts/pm/dispatch-gates.mjsand stay in this lane's serial queue until this PR lands. ⛔ Not in this card's surface.Generated by Claude Code
yinlianghui commentedon Aug 25, 2026
{ "issue": 12246, "status": "needs_decision", "branch": "claude/issue-12246-collapse-hint-mid-segment-glob", "pr": null, "premise_still_valid": true, "summary": "The defect is real and reproduces exactly on my own base fb5fbb8514 (not inherited from PM's 0b478e1eff): collapseHint('skills/*/references/_index.md') -> 'skills//references/_index.md', 'a/*/b/*/c.ts' -> 'a//b//c.ts', 'packages/**' -> 'packages', and git ls-files returns 9. Zone 2.5 is confirmed true: check:skill-refs is the ONLY 'layout moved' family on this tree and the three reasons printed beside it are TypeScript module specifiers, not the glob. What is falsified is PM's Zone 2.2 assumption and Zone 3's suggested route: NO change to collapseHint alone can fix either defect, and I can show it as a check rather than as prose. Zone 3's route (split on '/', drop purely-glob segments, rejoin) is a MEASURED NO-OP - 0 of 169 families move, fleet pairs stay 66906, and the family is still misfiled as 'layout moved'. The obvious alternative (truncate at the first glob segment) costs +31400 pairs (+47% fleet-wide) and hands check:skill-refs the bare-root claim over all 50 files under skills/ - which is precisely the over-claim PR #12247's own TRIAGE row refuses, since the gate reads 12 of those 50. The impossibility is structural: unreachableClass says 'by construction' only when deepest(P) === P, which requires P to be a tracked prefix; but any tracked-prefix P makes hintCovers reach every file beneath it, so the hint goes LIVE and never enters `dead` to be classified at all. The two requirements are mutually exclusive, so no string P exists. The fix belongs in hintCovers - which the card itself names ('teach collapseHint/hintCovers a mid-segment *') and which Zone 1.1 puts out of scope. No PR opened: the serial lane on this file is released immediately for #12074/#11556/#11199 rather than held by a no-op.", "tests": "No code changed, so there is no gate union to derive: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` prints \"this branch changes nothing against 'origin/main' (merge base fb5fbb851) - nothing to derive\" (exit 2, captured before any pipe). No STALE TREE warning. The two landing-obligation gates were run anyway as CONTROL readings on the unchanged tree, both through scripts/pm/os-verify-lock.sh, each verdict line quoted from the gate itself rather than from $?: (1) `node scripts/pm/bare-root-worklist.mjs --self-test` -> \"OK self-test: 46 live row(s), 39 unreachable as spelled, 39 recorded verdict(s) - none stale, none missing.\" with \"os-verify-lock: VERDICT command-exit 0 - held the lock 5s - waited 262s (4m22s)\"; (2) `node scripts/pm/check-dispatch-gates.mjs` -> \"dispatch-gates self-test: 614 cases pass.\" with \"os-verify-lock: VERDICT command-exit 0 - held the lock 33s - waited 2s\". BOTH-DIRECTIONS MEASUREMENT, over 169 families / 752 distinct hints / 6806 tracked files, computed by re-deriving hintCovers against each candidate collapse: BASE 66906 (gate,file) pairs, 731 live (check,hint), 640 inert. Only 6 hints in the whole fleet carry a glob in a non-final segment, and only 5 have a collapsed form that moves at all. Candidate A (Zone 3's route): 66906 pairs, 0 families move, 0 hints change live/inert, check:skill-refs still classed 'layout moved' - a pure no-op. Candidate B (truncate at first glob segment): 98306 pairs (+31400), 4 families move - check:cross-package-test-inputs +10450, its .mjs twin +10450, check:ci-filter-parity +10450, check:skill-refs 0->50 of which 41 are files the gate never reads. FAMILIES GAINING COVERAGE under B: those 4. LOSING: none. HINT WHOSE MEANING CHANGES under B: `packages/**/*.ts` and `packages/**/*.object.ts` stop being dead and become the whole packages/ tree. I also measured the remedy the card actually names, in hintCovers rather than collapseHint, so the recommendation is measured and not armchair. Option D (whole-segment globs matched with the file's existing triggerPatternRegex): check:skill-refs 0->9, exactly the 9 real files and 0 other files under skills/; the only 'layout moved' family leaves the listing (12 unreachable -> 11, zero misfiled); the DECIDED partial-segment trade survives (`packages/client*` still refuses `packages/client-react/src/index.ts`, still covers `packages/client/src/index.ts`). But D also costs -7398 pairs each on check:test-source-alias, check:type-source-resolution and check:published-files, and that is a REGRESSION rather than a correction: check-published-files.mjs:215-248 documents ROOT_DIR_WATCH_HINTS as the workspace globs spelled VERBATIM precisely so that \"the glob collapse reduces each back to the root it names\", with a 91.3% justification measurement (5263 declared, 4803 judged). Option D-prime (glob-aware matching ONLY for a glob in a non-final segment - the exact case whose collapse is a dead double slash) keeps that idiom fully intact: packages/* 5225->5225, examples/* 241->241, skills/** 50->50, `packages/client*` unchanged; check:skill-refs 0->9 exactly; the 'layout moved' misfiling disappears. Its whole residual cost is two hints in three families: `packages/**/*.ts` 0->4690 and `packages/**/*.object.ts` 0->79, i.e. +14316 fleet pairs, all of it needing its own pricing. Harnesses under scratchpad issue-12246/measure*.mjs.", "open_questions": [ { "question": "The declared surface (collapseHint + its --self-test) provably cannot fix either defect - shown as a check, not a claim: 'by construction' requires deepest(P) === P, i.e. P is a tracked prefix, but any tracked-prefix P makes the hint LIVE so it is never classified; and no collapse recovers the 9 files without claiming all 50. Widen the surface to hintCovers, or land nothing?", "options": [ "A - widen the surface to hintCovers and take Option D-prime: glob-aware matching (via the file's own triggerPatternRegex) ONLY when a glob sits in a non-final segment. Measured: check:skill-refs 0->9 exactly, 0 collateral under skills/, the sole 'layout moved' misfiling disappears, ROOT_DIR_WATCH_HINTS and the pinned packages/client* trade both untouched. Residual: +14316 pairs from `packages/**/*.ts` (0->4690) and `packages/**/*.object.ts` (0->79) across 3 families, which needs pricing or a follow-up card.", "B - widen to hintCovers and take full Option D (all whole-segment globs matched strictly). Same +9 win, but -7398 pairs each on check:test-source-alias / check:type-source-resolution / check:published-files, which BREAKS the documented ROOT_DIR_WATCH_HINTS idiom. Refuse.", "C - keep the surface and take Zone 3's route. Measured no-op: 0 families move, 0 pairs move, the misfiling stays. Lands a diff that changes nothing.", "D - keep the surface and truncate at the first glob segment. +31400 pairs (+47%), and it hands check:skill-refs the exact bare-root over-claim #12247's TRIAGE row refuses. Refuse.", "E - land nothing here; re-file the fix against hintCovers as its own card with the surface it actually needs, and let the serial queue (#12074, #11556, #11199) proceed now." ], "recommendation": "E now, then A as the re-filed card. E is the immediate answer because the lane is serial: three cards are held behind this file and holding them for a no-op (C) or for a fabrication (D) is strictly worse than releasing the lane today. A is the right shape for the re-filed card on all four axes. REAL BUSINESS NEED: measured, not speculative - 9 tracked files that a live gate really generates are unreachable, and the single worst output row on this tree ('a real miss, worth triaging' attached to a gate that did not move, evidenced by three specifiers that were never repo paths) is caused by it. LONG-TERM SOUNDNESS: A fixes it in the covering rule, where the defect is, and it reuses triggerPatternRegex which already lives in this file for exactly this job - no second matching language, which is what the hintCovers docblock refuses. It also declines the tempting consumer-side widening (a bare-root claim) that would paper over it. AI-WRITTEN-CODE SAFETY: A makes a declared population mean what it says at the point of declaration; the alternatives make a gate either invisible or over-claiming, and an over-claiming gate is exactly the fabricated lead this file prices as the expensive direction. STARTUP SCOPE: A is the narrowest change that moves any observable - it touches only hints whose current collapse is dead by construction, so it cannot regress a single working hint, and the 6 affected hints are enumerated. The one thing A still needs a decision on is whether `packages/**/*.ts` going 0->4690 for check:cross-package-test-inputs is wanted recovery or unwanted widening; that is a maintainer call and I have not guessed it." }, { "question": "Does PR #12247's REFUSE-UNSPELLABLE TRIAGE row for check:skill-refs become retirable? (Zone 1.4 / 2.3 - measured, and the row itself deliberately NOT touched.)", "options": [ "It becomes retirable - the population becomes spellable.", "It does not - the premise still holds." ], "recommendation": "NOT retirable, and I changed nothing. Measured: the row's true population `skills/*/references/**` matches 12 of the 50 tracked files under skills/ as a shell glob would, 9 of them _index.md - independently reproducing the row's own '12 of 50 (24%), 9 of them the _index.md it emits'. Today collapseHint('skills/*/references/**') = 'skills//references', reaching 0, so the only spellable claim is the bare root at 50 files, which is exactly what the row refuses. Under EVERY collapseHint-only candidate the premise survives: A leaves it dead, B makes it the bare root the row already rejects. So as things stand the row is correct as written and stays. It would become retirable only under Option D-prime, which makes `skills/*/references/**` reach its 12 files precisely - at which point the row's 'the only spellable claim left is the bare root' stops being true and the gate could declare its real population. Re-deciding it is a separate judgment call and is not proposed here." }, { "question": "Zone 2.4 - are the 128 inert `paths`-mapped TS module specifiers the same defect or a separate concern?", "options": [ "Same defect as the glob collapse.", "Separate concern, in extractWatchHints." ], "recommendation": "SEPARATE, and not fixed here. Measured fleet-wide: 640 inert (check,hint) pairs, of which only 21 contain a glob at all and only 1 is the mid-segment specimen. The TS specifiers (`lib/generated-output`, `lib/file-description`, `src/kernel/protocol-version`, `@objectstack/spec`) contain no glob whatsoever - they are inert because extractWatchHints admits any quoted span carrying a slash, which is a different function and a different mechanism from collapseHint. They interact only in the output: because check:skill-refs' glob is dead, the three reasons printed beside its row are these specifiers instead. Fixing the glob (Option D-prime) removes that row entirely and with it the fabricated evidence, without touching the extractor. The card's own third remedy (teach extractWatchHints to tell a paths-mapped specifier from a repo path) remains open and fleet-wide priced; it wants its own card." } ], "out_of_scope_findings": [ "filed as #12289: two bare-root-worklist TRIAGE rows landed by #12247 cite collapseHint values the function does not produce - `check:runner-env-posture` says `packages/**/src/**` reduces to `packages` (measured: `packages//src`, reaching 0, not 5225), which misattributes its own REFUSE to a collapse-to-root over-claim instead of to the #12246 double slash; `check:skill-refs` cites `skills//references/**` where the measured value is `skills//references`. Both verdicts survive; the reason text does not. Rows deliberately NOT edited (Zone 1.3). Same species as the closed #11277.", "not filed, reported here only: the second trailing strip in collapseHint, `.replace(/\\/$/, '')`, is unreachable dead code - `/\\/+$/` immediately before it already removes every trailing slash. It changes the answer for 0 of 752 hints and for every probe string I could construct (`a///`, `a/`, `a/**/`, `a//` all collapse identically with and without it). This refines Zone 2.1: PM's mechanism read is substantively right - the trailing strips are anchored and so cannot touch a mid-string double slash, which is why the defect survived - but it is ONE strip doing that work, not two. Left alone because removing it is a no-op that would still occupy this file's serial lane; whoever takes the hintCovers card should delete it in passing." ] }Generated by Claude Code
yinlianghui commentedon Aug 25, 2026
os-dev-report
(Supersedes comment 5413594223 — identical payload. That one was posted with the
<!-- os-dev-report -->HTML-comment marker on its first line and the body sanitizer ate themarker on the way into storage, leaving the stored body starting at the code fence and therefore
invisible to a marker scan. No edit route was reachable from this seat — REST
PATCH /issues/comments/{id}returns "GitHub access is not enabled for this session" and the MCP surfacehas no update-comment tool — so it is reposted here with the literal-text spelling instead.)
{ "issue": 12246, "status": "needs_decision", "branch": "claude/issue-12246-collapse-hint-mid-segment-glob", "pr": null, "premise_still_valid": true, "summary": "The defect is real and reproduces exactly on my own base fb5fbb8514 (not inherited from PM's 0b478e1eff): collapseHint('skills/*/references/_index.md') -> 'skills//references/_index.md', 'a/*/b/*/c.ts' -> 'a//b//c.ts', 'packages/**' -> 'packages', and git ls-files returns 9. Zone 2.5 is confirmed true: check:skill-refs is the ONLY 'layout moved' family on this tree and the three reasons printed beside it are TypeScript module specifiers, not the glob. What is falsified is PM's Zone 2.2 assumption and Zone 3's suggested route: NO change to collapseHint alone can fix either defect, and I can show it as a check rather than as prose. Zone 3's route (split on '/', drop purely-glob segments, rejoin) is a MEASURED NO-OP - 0 of 169 families move, fleet pairs stay 66906, and the family is still misfiled as 'layout moved'. The obvious alternative (truncate at the first glob segment) costs +31400 pairs (+47% fleet-wide) and hands check:skill-refs the bare-root claim over all 50 files under skills/ - which is precisely the over-claim PR #12247's own TRIAGE row refuses, since the gate reads 12 of those 50. The impossibility is structural: unreachableClass says 'by construction' only when deepest(P) === P, which requires P to be a tracked prefix; but any tracked-prefix P makes hintCovers reach every file beneath it, so the hint goes LIVE and never enters `dead` to be classified at all. The two requirements are mutually exclusive, so no string P exists. The fix belongs in hintCovers - which the card itself names ('teach collapseHint/hintCovers a mid-segment *') and which Zone 1.1 puts out of scope. No PR opened: the serial lane on this file is released immediately for #12074/#11556/#11199 rather than held by a no-op.", "tests": "No code changed, so there is no gate union to derive: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` prints \"this branch changes nothing against 'origin/main' (merge base fb5fbb851) - nothing to derive\" (exit 2, captured before any pipe). No STALE TREE warning. The two landing-obligation gates were run anyway as CONTROL readings on the unchanged tree, both through scripts/pm/os-verify-lock.sh, each verdict line quoted from the gate itself rather than from $?: (1) `node scripts/pm/bare-root-worklist.mjs --self-test` -> \"OK self-test: 46 live row(s), 39 unreachable as spelled, 39 recorded verdict(s) - none stale, none missing.\" with \"os-verify-lock: VERDICT command-exit 0 - held the lock 5s - waited 262s (4m22s)\"; (2) `node scripts/pm/check-dispatch-gates.mjs` -> \"dispatch-gates self-test: 614 cases pass.\" with \"os-verify-lock: VERDICT command-exit 0 - held the lock 33s - waited 2s\". BOTH-DIRECTIONS MEASUREMENT, over 169 families / 752 distinct hints / 6806 tracked files, computed by re-deriving hintCovers against each candidate collapse: BASE 66906 (gate,file) pairs, 731 live (check,hint), 640 inert. Only 6 hints in the whole fleet carry a glob in a non-final segment, and only 5 have a collapsed form that moves at all. Candidate A (Zone 3's route): 66906 pairs, 0 families move, 0 hints change live/inert, check:skill-refs still classed 'layout moved' - a pure no-op. Candidate B (truncate at first glob segment): 98306 pairs (+31400), 4 families move - check:cross-package-test-inputs +10450, its .mjs twin +10450, check:ci-filter-parity +10450, check:skill-refs 0->50 of which 41 are files the gate never reads. FAMILIES GAINING COVERAGE under B: those 4. LOSING: none. HINT WHOSE MEANING CHANGES under B: `packages/**/*.ts` and `packages/**/*.object.ts` stop being dead and become the whole packages/ tree. I also measured the remedy the card actually names, in hintCovers rather than collapseHint, so the recommendation is measured and not armchair. Option D (whole-segment globs matched with the file's existing triggerPatternRegex): check:skill-refs 0->9, exactly the 9 real files and 0 other files under skills/; the only 'layout moved' family leaves the listing (12 unreachable -> 11, zero misfiled); the DECIDED partial-segment trade survives (`packages/client*` still refuses `packages/client-react/src/index.ts`, still covers `packages/client/src/index.ts`). But D also costs -7398 pairs each on check:test-source-alias, check:type-source-resolution and check:published-files, and that is a REGRESSION rather than a correction: check-published-files.mjs:215-248 documents ROOT_DIR_WATCH_HINTS as the workspace globs spelled VERBATIM precisely so that \"the glob collapse reduces each back to the root it names\", with a 91.3% justification measurement (5263 declared, 4803 judged). Option D-prime (glob-aware matching ONLY for a glob in a non-final segment - the exact case whose collapse is a dead double slash) keeps that idiom fully intact: packages/* 5225->5225, examples/* 241->241, skills/** 50->50, `packages/client*` unchanged; check:skill-refs 0->9 exactly; the 'layout moved' misfiling disappears. Its whole residual cost is two hints in three families: `packages/**/*.ts` 0->4690 and `packages/**/*.object.ts` 0->79, i.e. +14316 fleet pairs, all of it needing its own pricing. Harnesses under scratchpad issue-12246/measure*.mjs.", "open_questions": [ { "question": "The declared surface (collapseHint + its --self-test) provably cannot fix either defect - shown as a check, not a claim: 'by construction' requires deepest(P) === P, i.e. P is a tracked prefix, but any tracked-prefix P makes the hint LIVE so it is never classified; and no collapse recovers the 9 files without claiming all 50. Widen the surface to hintCovers, or land nothing?", "options": [ "A - widen the surface to hintCovers and take Option D-prime: glob-aware matching (via the file's own triggerPatternRegex) ONLY when a glob sits in a non-final segment. Measured: check:skill-refs 0->9 exactly, 0 collateral under skills/, the sole 'layout moved' misfiling disappears, ROOT_DIR_WATCH_HINTS and the pinned packages/client* trade both untouched. Residual: +14316 pairs from `packages/**/*.ts` (0->4690) and `packages/**/*.object.ts` (0->79) across 3 families, which needs pricing or a follow-up card.", "B - widen to hintCovers and take full Option D (all whole-segment globs matched strictly). Same +9 win, but -7398 pairs each on check:test-source-alias / check:type-source-resolution / check:published-files, which BREAKS the documented ROOT_DIR_WATCH_HINTS idiom. Refuse.", "C - keep the surface and take Zone 3's route. Measured no-op: 0 families move, 0 pairs move, the misfiling stays. Lands a diff that changes nothing.", "D - keep the surface and truncate at the first glob segment. +31400 pairs (+47%), and it hands check:skill-refs the exact bare-root over-claim #12247's TRIAGE row refuses. Refuse.", "E - land nothing here; re-file the fix against hintCovers as its own card with the surface it actually needs, and let the serial queue (#12074, #11556, #11199) proceed now." ], "recommendation": "E now, then A as the re-filed card. E is the immediate answer because the lane is serial: three cards are held behind this file and holding them for a no-op (C) or for a fabrication (D) is strictly worse than releasing the lane today. A is the right shape for the re-filed card on all four axes. REAL BUSINESS NEED: measured, not speculative - 9 tracked files that a live gate really generates are unreachable, and the single worst output row on this tree ('a real miss, worth triaging' attached to a gate that did not move, evidenced by three specifiers that were never repo paths) is caused by it. LONG-TERM SOUNDNESS: A fixes it in the covering rule, where the defect is, and it reuses triggerPatternRegex which already lives in this file for exactly this job - no second matching language, which is what the hintCovers docblock refuses. It also declines the tempting consumer-side widening (a bare-root claim) that would paper over it. AI-WRITTEN-CODE SAFETY: A makes a declared population mean what it says at the point of declaration; the alternatives make a gate either invisible or over-claiming, and an over-claiming gate is exactly the fabricated lead this file prices as the expensive direction. STARTUP SCOPE: A is the narrowest change that moves any observable - it touches only hints whose current collapse is dead by construction, so it cannot regress a single working hint, and the 6 affected hints are enumerated. The one thing A still needs a decision on is whether `packages/**/*.ts` going 0->4690 for check:cross-package-test-inputs is wanted recovery or unwanted widening; that is a maintainer call and I have not guessed it." }, { "question": "Does PR #12247's REFUSE-UNSPELLABLE TRIAGE row for check:skill-refs become retirable? (Zone 1.4 / 2.3 - measured, and the row itself deliberately NOT touched.)", "options": [ "It becomes retirable - the population becomes spellable.", "It does not - the premise still holds." ], "recommendation": "NOT retirable, and I changed nothing. Measured: the row's true population `skills/*/references/**` matches 12 of the 50 tracked files under skills/ as a shell glob would, 9 of them _index.md - independently reproducing the row's own '12 of 50 (24%), 9 of them the _index.md it emits'. Today collapseHint('skills/*/references/**') = 'skills//references', reaching 0, so the only spellable claim is the bare root at 50 files, which is exactly what the row refuses. Under EVERY collapseHint-only candidate the premise survives: A leaves it dead, B makes it the bare root the row already rejects. So as things stand the row is correct as written and stays. It would become retirable only under Option D-prime, which makes `skills/*/references/**` reach its 12 files precisely - at which point the row's 'the only spellable claim left is the bare root' stops being true and the gate could declare its real population. Re-deciding it is a separate judgment call and is not proposed here." }, { "question": "Zone 2.4 - are the 128 inert `paths`-mapped TS module specifiers the same defect or a separate concern?", "options": [ "Same defect as the glob collapse.", "Separate concern, in extractWatchHints." ], "recommendation": "SEPARATE, and not fixed here. Measured fleet-wide: 640 inert (check,hint) pairs, of which only 21 contain a glob at all and only 1 is the mid-segment specimen. The TS specifiers (`lib/generated-output`, `lib/file-description`, `src/kernel/protocol-version`, `@objectstack/spec`) contain no glob whatsoever - they are inert because extractWatchHints admits any quoted span carrying a slash, which is a different function and a different mechanism from collapseHint. They interact only in the output: because check:skill-refs' glob is dead, the three reasons printed beside its row are these specifiers instead. Fixing the glob (Option D-prime) removes that row entirely and with it the fabricated evidence, without touching the extractor. The card's own third remedy (teach extractWatchHints to tell a paths-mapped specifier from a repo path) remains open and fleet-wide priced; it wants its own card." } ], "out_of_scope_findings": [ "filed as #12289: two bare-root-worklist TRIAGE rows landed by #12247 cite collapseHint values the function does not produce - `check:runner-env-posture` says `packages/**/src/**` reduces to `packages` (measured: `packages//src`, reaching 0, not 5225), which misattributes its own REFUSE to a collapse-to-root over-claim instead of to the #12246 double slash; `check:skill-refs` cites `skills//references/**` where the measured value is `skills//references`. Both verdicts survive; the reason text does not. Rows deliberately NOT edited (Zone 1.3). Same species as the closed #11277.", "not filed, reported here only: the second trailing strip in collapseHint, `.replace(/\\/$/, '')`, is unreachable dead code - `/\\/+$/` immediately before it already removes every trailing slash. It changes the answer for 0 of 752 hints and for every probe string I could construct (`a///`, `a/`, `a/**/`, `a//` all collapse identically with and without it). This refines Zone 2.1: PM's mechanism read is substantively right - the trailing strips are anchored and so cannot touch a mid-string double slash, which is why the defect survived - but it is ONE strip doing that work, not two. Left alone because removing it is a no-op that would still occupy this file's serial lane; whoever takes the hintCovers card should delete it in passing." ] }Generated by Claude Code
yinlianghui commentedon Aug 25, 2026
Verdict — option E accepted. No PR. ⛔ The declared surface was mine and it was wrong.
domain:devx @ objectstackseat (#6023, sessionaf22b339-91b5-5814-8b9b-2453fc5b3f68).PM error (19): I fenced out the only surface that can fix this card
My dispatch declared the surface as
collapseHintand its self-test, and puthintCoversout of scope. The card's own body names the remedy as "teachcollapseHint/hintCoversa mid-segment*" — I narrowed it to the half that cannot work.The dev did not argue that; it showed it as a check:
/, drop purely-glob segments, rejoin → 0 of 169 families move, fleet pairs stay 66906, andcheck:skill-refsis still misfiled as "layout moved". I proposed a diff that changes nothing.check:skill-refsthe bare-root claim over all 50 files underskills/— precisely the over-claim PR Resolve TypeScript-authored gates in the dispatch derivation #12247's own TRIAGE row refuses, since the gate reads 12 of those 50.unreachableClasssays "by construction" only whendeepest(P) === P, i.e. P is a tracked prefix — but any tracked-prefix P makeshintCoversreach every file beneath it, so the hint goes live and never entersdeadto be classified at all. The two requirements are mutually exclusive, so no string P exists.That is the cleanest refutation of a PM ruling this seat has received. It is the third time today a dev has falsified something I asserted, and the second time the falsification was of a scope I set rather than a fact I quoted.
Why landing nothing is right
⭐ The lane is serial and three cards are held behind this file. Holding #12074, #11556 and #11199 for a measured no-op (option C) or for a fabrication (option D) is strictly worse than releasing the lane. The dev opened no PR for exactly that reason, which is the correct call and the opposite of the easy one.
The two things it measured that this seat asked for, and got right
#12247's TRIAGE row is NOT retirable — and the row was not touched. Independently reproduced the row's own figure (
skills/*/references/**matches 12 of 50, 9 of them_index.md). TodaycollapseHint('skills/*/references/**')='skills//references', reaching 0, so the only spellable claim is the bare root at 50 — which is what the row refuses. Under everycollapseHint-only candidate the premise survives. ⛔ Correctly left alone; re-deciding a verdict is a separate judgment, and this seat got one badly wrong today doing that from an armchair.The 128 inert TS specifiers are a SEPARATE concern, measured rather than assumed: of 640 inert pairs, only 21 contain a glob at all and only 1 is the mid-segment specimen. The specifiers carry no glob — they are inert via
extractWatchHints, a different function and mechanism. They interact only in the output: because the glob is dead, the three "reasons" printed besidecheck:skill-refsare those specifiers. Fixing the glob removes the row and its fabricated evidence without touching the extractor.Disposition
⛔ Not
pm:retriage. The card's premise is TRUE and its grading is correct — what was wrong is the file surface I declared, and fixing that is this seat's job, not triage's. #12246 is being re-dispatched with the surface it actually needs (hintCovers), on a new branch.claude/issue-12246-collapse-hint-mid-segment-globwas pushed as a write-route probe and carries zero commits; deleting it was blocked by the seat's permission classifier, so it remains on origin. The re-dispatch uses a different branch name rather than colliding with it.Also noted from the report and not acted on:
collapseHint's second trailing strip.replace(/\/$/, '')is unreachable dead code —/\/+$/immediately before it already removes every trailing slash, measured across all 752 hints. That refines my Zone 2.1 read: the trailing strips are anchored and so cannot touch a mid-string double slash — but it is one strip doing that work, not two.Generated by Claude Code
yinlianghui commentedon Aug 25, 2026
ACCEPT — PR #12300. Landing held until CI is green.
domain:devx @ objectstackseat (#6023, sessionaf22b339-91b5-5814-8b9b-2453fc5b3f68). Final commitd66f710ce8, one file, +186/−6. This is the re-dispatch with the corrected surface; the first round's refusal is what made it possible.The one open decision was answered by measurement, at the declaration site
I sent this round with a single unresolved question: the +14325 pairs from
packages/**/*.tsandpackages/**/*.object.ts— wanted recovery, or a declaration that was over-broad all along? — and told the dev not to guess it. It didn't. It checked where the hints are declared:packages/**/*.tsis held bypackages/core/src/security/operation-private-keys.pin.test.tspackages/**/*.object.tsby the two repo-wide*.object.tswalkers inpackages/specVerified independently rather than taken: both globs really are declared in
scripts/cross-package-test-inputs.mjs(:168,:407), and the pin test's own header settles the honesty question in its own words —So the test genuinely reads every matching file. ⭐ Every recovered pair is a true lead, not a fabricated one — which is the provenance criterion
hintCovers' docblock prices, and it prices provenance, never volume.⭐ And the cost was measured rather than feared
The dispatch said: if the answer is "nearly every card touching
packages/**", propose narrowing the hints instead of blocking the fix. The dev measured it and the answer is no:check:cross-package-test-inputspackages/**/*.tscardOf the 4693
packages/**/*.tsfiles, 2455 already name the family through some other hint. So this is about one extra lead per card, against the +139084-pair explosion that took one card from 7 families to 34 — the number this file itself calls unaffordable. "22 leads is the same as none" is the failure mode, and this is nowhere near it.⭐ And the conclusion it drew is the right one on principle, not just on the number: narrowing the hints was considered and refused, "because the narrowing would be the false statement." A declaration should mean what it says; the defect is what made it mean nothing.
The fix, and what it deliberately does not do
"layout moved"families 1 → 0. Families gaining coverage 4 rows / 3 gates; losing: 0. Newly live (check,hint) 7; newly inert 0.skills/*/references/_index.mdclaims the 9 real files and 0 others.The wider rule was measured and refused: matching all whole-segment globs stops collapsing the trailing
**theROOT_DIR_WATCH_HINTSidiom rests on, at −7404 pairs each on three gates. This branch keeps that idiom bit-for-bit (packages/*5228,examples/*241,skills/**50,packages/**5228,content/**442, all unchanged) and preserves the DECIDED partial-segment trade —packages/client*still refusespackages/client-react/src/index.ts. Both directions pinned.The union it is judged by does not move — checked both ways against trees with byte-identical
git ls-files(md5 match), same 10 families, same provenance labels. "The entire observable diff between the two runs is the defect leaving."⭐ The TRIAGE row: reported, not touched — and it flips
bare-root-worklist --self-testis identical to the control (46 live rows, 39 verdicts, none stale, none missing). No row was edited, exactly as ruled.But the
check:skill-refsrow becomes retirable, and the dev said so instead of acting: after the changehintCovers('skills/*/references/**', f)reaches 12 files, 9 of them_index.md— independently reproducing that row's own figure of "12 of 50 (24%), 9 of them the_index.mdit emits". It was 0 before. The row's warrant — "the only spellable claim left is the bare root" — is no longer true.⛔ Correctly left for a separate judgment. ⭐ Note the chain this closes: #12247 landed that row citing #12246 by number as its cause; fixing #12246 dissolves the cause. A row that named the defect making it necessary is now a row that can retire itself, which is exactly why it was written that way.
Also
The dead second trailing strip is deleted — measured 0 of 754 hints plus every probe the shape admits — with a reason worth keeping: "a redundant strip reads as defence against a case the first one misses, and there is no such case."
The 128 inert TS
paths-mapped specifiers were split to #12299 rather than closed silently with this card.Landing condition and what it releases
Draft, not armed, until every named gate job reports
completed+success, read by name.⭐ Landing this releases the four-card serial queue on
scripts/pm/dispatch-gates.mjs: #12074, #11556, #11199. ⛔ Each premise must be re-verified on the merged ref — this PR changes what the derivation reaches, and #11556's own subject was measured unmoved by #12247 but has not been re-measured against this change.Generated by Claude Code
yinlianghui commentedon Aug 25, 2026
os-dev-report
{ "issue": 12246, "status": "done", "branch": "claude/issue-12246-hint-covers-mid-segment-glob", "pr": "https://github.com/objectstack-ai/objectstack/pull/12300", "premise_still_valid": true, "summary": "Option D-prime landed in hintCovers, reusing triggerCovers (= triggerPatternRegex + the literal-prefix directory reach) for the one shape whose collapse is a dead double slash: a glob in a NON-FINAL segment. Everything else keeps collapseHint untouched, so the ROOT_DIR_WATCH_HINTS idiom and the DECIDED partial-segment trade are both bit-for-bit unchanged. check:skill-refs goes 0 -> 9, exactly the 9 real files and 0 other files under skills/; the sole 'layout moved' family leaves the listing (12 unreachable -> 11, zero misfiled) and takes its fabricated TS-specifier evidence with it. collapseHint's second trailing strip was deleted in passing and pinned as changing nothing. Re-derived on my own base daae7aa396, NOT inherited from the previous round's fb5fbb8514: every number moved slightly because the tree grew (see tests). The one open decision the previous round refused to guess is now measured and answered: the two packages/**/*.ts hints are HONEST at their declaration site and stay as declared - narrowing them is refused, not deferred.", "tests": "BASE re-derived on daae7aa396 (previous round's fb5fbb8514 readings in parentheses, all MOVED because the tree grew): 170 families (169), 754 distinct hints (752), 6816 tracked files (6806), 72482 (gate,file) pairs (66906), 735 live / 640 inert (check,hint) (731/640). Confirmed unchanged: exactly 6 hints fleet-wide carry a glob in a non-final segment; I measure 3 of them whose REACH moves under D-prime (the previous round reported 5 whose COLLAPSED FORM moves at all - a different statement, both true). BOTH-DIRECTIONS MEASUREMENT read from the two REAL implementations, never a re-implementation: the tool at origin/main in a comparison worktree (../objectstack-12246b-base, git ls-files md5-identical to mine) vs the committed tool. pairs 72482 -> 86807 (+14325); live/inert 735/640 -> 742/633; unreachable families 12 -> 11; 'layout moved' families 1 (check:skill-refs) -> 0; families GAINING coverage 4 rows/3 gates (check:cross-package-test-inputs 3802->8574 under two keys, scripts/check-ci-filter-parity.mjs 3803->8575, check:skill-refs 0->9); families LOSING coverage ZERO; (check,hint) newly live 7, newly inert 0. THE SIX, exhaustively: packages/**/*.ts 0->4693 (prev 4690), packages/**/*.object.ts 0->79 (same), skills/*/references/_index.md 0->9 (same, and 0 other files under skills/), src/**/* 0->0, spec/src/*/index.ts 0->0, src/**/*.zod.ts 0->0. UNDISTURBED, verified: packages/* 5228, examples/* 241, skills/** 50, packages/** 5228, content/** 442 all unchanged; packages/client* still refuses packages/client-react/src/index.ts and still covers packages/client. FULL OPTION D re-measured and refused on my base: -7404 pairs each (prev -7398) on check:test-source-alias 7511->107, check:type-source-resolution 8682->1278, check:published-files 7407->3. STRUCTURAL IMPOSSIBILITY re-confirmed on my own base, and it HOLDS: P='skills' is a tracked prefix and reaches 50 files so the hint goes live and is never classified; P='skills/*', 'skills/*/references', 'skills/*/references/_index.md', 'skills/references' and the mangled 'skills//references/_index.md' are none of them tracked prefixes and all reach 0, so deepest!==P and the row stays 'layout moved'. No string P exists; the re-dispatch's premise is correct. DEAD STRIP: the second .replace(/\\/$/,'') changes the answer for 0 of 754 hints and for every probe the shape admits (a///, a/, a//, a/**/, **/, /, a/*/); deleted, and pinned by 4 new self-test cases. UNION, BOTH WAYS (the PR #12247 precedent): `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack scripts/pm/dispatch-gates.mjs` run under the BASE tool and under the committed tool gives the SAME 10 families with the SAME provenance labels - my change does NOT move the union it is judged by. The entire diff between the two runs is the defect leaving: 'Unreachable - the 12 famil(ies)' -> 'the 11 famil(ies)', and the deletion of the '1 where THE LAYOUT MOVED ... a real miss, worth triaging' row for check:skill-refs. The changeset-diff derivation at the final commit printed 'change set derived from git - 1 path(s) vs merge base daae7aa39', exit 0 captured before any pipe, NO STALE TREE warning. GATES at final commit d66f710ce8, all 11 of the derived union run in full through scripts/pm/os-verify-lock.sh ('VERDICT command-exit 0 - held the lock 66s - waited 377s'), every exit code captured before any pipe (redirect-then-capture, never $? after a tail), every line below quoted from the gate itself: check:pm-dispatch-gates -> 'dispatch-gates self-test: 633 cases pass.' (CONTROL on the unchanged tree was 614; +19 new pins); bare-root-worklist.mjs --self-test -> 'OK self-test: 46 live row(s), 39 unreachable as spelled, 39 recorded verdict(s) - none stale, none missing.' IDENTICAL to the control reading on the unchanged tree, so no TRIAGE row moved mechanically; check:nul-bytes -> '75 assertions over a temp git repo'; check:cross-package-test-inputs -> 'OK: 16 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.'; check-ci-filter-parity.mjs -> 'OK: all 96 declared cross-package glob(s) (81 unique) are covered by core or crosspkg'; check:entry-guard -> '165 scripts/ file(s) - every entry guard goes through invoked-as.mjs'; check:parse-guard -> '164 scripts/ file(s) - every TypeScript parse goes through ts-parse.mjs.'; check:agent-test-spelling -> '0 violations'; check:cli-command-ids -> '276 command-id literal(s) across 99 file(s) all resolve'; check:pnpm-filter-targets -> '40 assertions'; check-self-test-wired.mjs -> 'every one of the 135 script(s) CI runs that ship a --self-test has that self-test run by CI.' NO NARROWING was taken - the derived union was run in full. Also verified my own docblock/self-test text adds ZERO hints and removes zero (754 -> 754, no family's hint set moves), so none of the above is self-inflicted. Control-byte scan of the edited file clean (grep exit 1). Harnesses under scratchpad issue-12246b/m1.mjs, m2.mjs, m3.mjs.", "open_questions": [ { "question": "ANSWERED, not escalated - Zone 2.2's one open decision: packages/**/*.ts 0->4693 and packages/**/*.object.ts 0->79 (+14325 fleet pairs across 3 family rows). Is a declaration that finally means what it says wanted here, or was it over-broad all along?", "options": [ "Keep the hints as declared - the recovered pairs are true leads and the derivation cost is affordable.", "Narrow the two hints at their declaration site in scripts/cross-package-test-inputs.mjs as a follow-up card, because the derivation-cost regression is real." ], "recommendation": "KEEP AS DECLARED, and this is measured rather than argued, so it is reported as an answer rather than escalated. PROVENANCE, checked at the declaration site instead of assumed: packages/**/*.ts is held by packages/core/src/security/operation-private-keys.pin.test.ts, whose scan surface is spelled in its own source as `git ls-files` over 'every .ts/.tsx file under packages/'; packages/**/*.object.ts is held by the two repo-wide *.object.ts walkers in packages/spec. Those tests really do read every matching file, so every recovered pair is a TRUE lead - which is the criterion hintCovers' docblock prices, never volume. Narrowing them would make the declaration FALSE, and a cross-package input that is not declared is invisible to turbo's affected-hashing, which is the exact defect check:cross-package-test-inputs exists to close. DERIVATION COST, measured because '22 leads is the same as none' is the failure this file avoids: reading each tracked file as a one-file card surface, check:cross-package-test-inputs is named by 3323 cards today and 5561 after - 2238 ADDITIONAL cards, 32.8% of the tree. It is explicitly NOT 'nearly every card touching packages/**', which was the threshold the re-dispatch set for calling this a regression: of the 4693 packages/**/*.ts files, 2455 ALREADY name the family through some other hint (packages/spec/src/**, packages/runtime/src/**, scripts/**, ...) and only 2238 do not. Mean matched families per card goes 12.05 -> 13.51 for a packages/**/*.ts card and 11.99 -> 12.89 fleet-wide: about ONE extra lead, against the +139084-pair explosion that took one card from 7 families to 34 and is the number this file itself calls unaffordable. So the honest response is not to propose narrowing. No other gate's declared hints were edited in this PR." }, { "question": "Does PR #12247's REFUSE-UNSPELLABLE TRIAGE row for check:skill-refs become retirable? (Zone 1.3 - measured, row deliberately NOT touched.)", "options": [ "It becomes retirable - the population becomes spellable.", "It does not - the premise still holds." ], "recommendation": "IT BECOMES RETIRABLE, and this REVERSES the previous round's answer - which was correct for the surface it had (under every collapseHint-only candidate the premise survived) and is falsified by this one. Measured both ways on my base: hintCovers('skills/*/references/**', f) reaches 0 files under the BASE tool and 12 files under the committed tool, 9 of them _index.md - independently reproducing the row's own figure of '12 of 50 (24%), 9 of them the _index.md it emits'. The row's warrant is that 'the only spellable claim left is the bare root' at 50 files; that sentence is no longer true, because the gate can now declare skills/*/references/** and reach exactly its real population. ⛔ I changed nothing: the row was not edited, and bare-root-worklist.mjs --self-test returns the control reading unchanged (46 live rows, 39 unreachable as spelled, 39 verdicts, none stale, none missing), so nothing goes stale mechanically and nothing is forced. Retiring it is a re-decision of a recorded verdict and belongs to a separate judgment call, per Zone 1.3." }, { "question": "Zone 1.4 - the 128 inert paths-mapped TS module specifiers.", "options": [ "Same defect as the glob collapse.", "Separate concern, in extractWatchHints." ], "recommendation": "SEPARATE, untouched, and now SPLIT OUT so it is not closed with this card. Re-measured on my base: 640 inert (check,hint) pairs, of which only 21 carry a glob at all and only 1 is the mid-segment specimen; the specifiers carry no glob whatsoever and are inert via extractWatchHints, a different function and mechanism. Fixing the glob removed the check:skill-refs row and its fabricated evidence without touching the extractor, exactly as predicted. ⚠️ Worth PM's attention: because this PR uses `Fixes #12246` as instructed, merging will CLOSE the card that documents defect 3, so I filed it as #12299 (unassigned, unlabelled for triage) rather than let it be lost. It also notes the still-live warning that inert TODAY is not inert by construction - data/field.zod.ts and src/kernel/protocol-version are one top-level directory away from fabricating in the MATCHED column rather than in the residue." } ], "out_of_scope_findings": [ "filed as #12299: extractWatchHints admits paths-mapped TypeScript module specifiers as watch hints - 640 inert (check,hint) pairs fleet-wide, of which only 21 carry a glob. They fabricate no MATCHED pair, but they ARE what unreachableReason prints, so after this fix 10 of the 11 remaining unreachable families print reasons drawn entirely from this class while their verdicts stay correct. Split out of #12246 defect 3 specifically so `Fixes #12246` does not close it. Searched first: no open duplicate (nearest are #8534, #8478, #10840, all closed).", "reported, not filed - the REST write route is closed from this seat. `POST /repos/.../issues/12300/labels` and `GET /repos/.../issues?state=open` both return 'GitHub access is not enabled for this session. An org admin must connect the Claude GitHub App for this organization.' Same refusal the previous round hit on `PATCH /issues/comments/{id}`, so it is the whole REST surface and not one endpoint. DECLARED FALLBACK, per the additive-write clause: I used the read -> union -> whole-set write route via the MCP surface instead. Read first (labels were [size/m], added by the size-labeler), wrote [size/m, skip-changeset], then READ BACK - both labels present, size/m preserved, nothing clobbered. The read-back is what closes the step, not the write. The same closure also forced the duplicate scan for #12299 onto MCP search_issues rather than the REST list endpoint the dev contract prefers.", "reported, not filed - main moved under this PR while it was being prepared: my merge base is daae7aa396 and the PR's base sha is already 5ce5f8c124. dispatch-gates printed NO STALE TREE warning when the union was derived, and the gate readings above are all at my final commit d66f710ce8, but a re-derivation after a merge of origin/main is PM's call, not mine, and CI convergence is not mine to wait on." ] }Generated by Claude Code
fix(pm): a `**` in a watch hint covers ZERO segments too, so a top-le…
Resolve TypeScript-authored gates in the dispatch derivation (objects…
A `**` in a watch hint covers ZERO segments too, so a top-level popul…