Skip to content

fix(pm): stop dispatch-gates reading non-path literals as populations, and print the unreachable set by default - #10191

Merged
qq9340100 merged 1 commit into
mainfrom
claude/issue-10097-unreachable-families
Aug 20, 2026
Merged

fix(pm): stop dispatch-gates reading non-path literals as populations, and print the unreachable set by default#10191
qq9340100 merged 1 commit into
mainfrom
claude/issue-10097-unreachable-families

Conversation

@qq9340100

Copy link
Copy Markdown
Collaborator

Fixes#10097

Ruled scope from the grading comment: C + A. Both landed.

Premise re-check first — the card's counts had shifted

The card measured 6 unreachable, 2 misparsed. Re-run on fresh origin/main (da891e0), the set is 4, and the shrink is real repo churn, not a measurement artifact: the two objectui-pin-fresh families (pnpm check:objectui-pin-fresh, node scripts/check-objectui-pin-fresh.mjs, both declaring the cross-repo slug objectstack-ai/objectui) have been retired from the repo since the card was written — the only surviving mention is a past-tense note in prerelease-pin-watch.yml. Family count 114 → 115; swept corpus 6336 → 6074.

Both families the grading actually named were still present, so C + A remained fully valid. Self-test baseline re-measured on the spot: 351, not the 344 the dispatch assumed.

C — the misparse

Both literals confirmed as incidental operational constants, not population declarations:

familyliteralwhere it really comes from
check:release-bodyapplication/jsonscripts/release-github-releases.mjs:491 — a content-type request header
check-skill-frame-freshness.mjsrefs/remotes/origin/mainscripts/check-skill-frame-freshness.mjs:193 — a git rev-parse argument

extractWatchHints decides "looks pathy" by contains a slash, and a slash separates several namespaces of which only one is the filesystem.

Measured per family, as the grading asked: each of these two had the phantom as its only hint. So neither derives a real population once repaired — both land in undetermined ("source names no path at all — NOT known irrelevant"), which is the honest bucket and strictly more honest than the unreachable verdict a phantom population was earning them.

Why the class, not the two instances

isNonPathNamespace refuses two closed grammars — IANA top-level media types (2 segments, undotted subtype) and the git revision namespaces refs/… and origin/…. The same literals are scraped out of a dozen other gate sources where a real hint sits beside them and hides the effect; those survivors are the fabrication risk extractWatchHints' own header calls the expensive direction.

This file already knew: DEFAULT_BASE_REF is assembled from two unslashed halves specifically so the joined origin/main never enters its own hint set, and its comment says so. That workaround is the single-file version of this rule.

Blast radius, measured over every tracked JS/TS source: the refusal removes 21 distinct literals (20 media types, 1 ref). None names a tracked path or path prefix, and the tree has no top-level directory called refs, origin, or any of the ten media types. So it moves no verdict for any card — every literal removed was already dead for matching. Verified against the whole tree, not assumed.

A — the unreachable set in the default output

unreachableLines now prints on every run, not only under --residue. The disclosure already existed; what did not exist was any reason for a reader to look — every dispatch brief says "run every family dispatch-gates names", and the flag that would have shown the limit is one nobody is told to pass.

The heading carries the correction the card put first, because it is the one wrong reading available: ⛔ NOT a skip list — CI runs these on every pull request.

Entries are grouped by unreachableClass, derived mechanically from the existing prefix sweep, never declared (intent is not in the tree, and unreachableFamilies' docblock is explicit that this must not be faked):

  • by construction — the literal was never a repo path, or the tree has the population and the covering rule refuses the literal as too generic. No change to gate or tree reaches it.
  • layout moved — the tree stops at a shorter prefix. Usually a real miss, so it sorts first rather than being buried among the standing facts.

After the C repair the live set is 2, both by construction:

Unreachable — the 2 famil(ies) whose declared population matches NOTHING in this tree, swept over 6074 tracked file(s).
⛔ NOT a skip list: CI runs these on every pull request. This says only that no path derivation can name them,
so they score the same quiet green for every card in the tree — yours included — whether they still work or not.
2 unreachable BY CONSTRUCTION — the literal is not a path this derivation can reach, and no change to the gate or the tree makes it one:
- pnpm check:driver-memory-census [lint.yml] dead: '@objectstack/driver-memory' — never was a repo path
- pnpm check:examples-live-imports [lint.yml] dead: 'examples' — the tree HAS it; refused as too generic

check:driver-memory-census — the gate whose CI failure surfaced this card — is now named on every default run.

Tests

Self-test 351 → 388 (+37), all green, run on b1833c8.

Both repaired parses and the new default section carry positive and negative shapes: the negatives pin that a 3-segment literal headed by a media type, a media-type head with a dotted second segment (image/logo.png), and a directory merely starting with a refused word (origins/, refspec/) are all still paths.

Reverse verification, both legs, mutation confirmed on disk by grep count before each run (no build step — the script is executed directly, nothing resolves through dist/):

  • Ablation C (disable the refusal at its call site): 8 red, exactly the C cases. Restored, marker count back to 0.
  • Ablation A (move the listing back behind showResidue): 2 red — and only the two end-to-end spawn cases. The 30 isolation cases stayed green, which is precisely why the end-to-end case exists: a regression putting the section back behind the flag is invisible to every isolation test of unreachableLines. Restored.

Gate union re-derived on the actual diff via node scripts/pm/dispatch-gates.mjs (no paths — the script takes the change set from the merge base itself); it named the same 3 families as the dispatch clue, no additions. Run under scripts/pm/os-verify-lock.sh:

All 60 self-test cases passed.
OK: 12 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.
✓ check-nul-bytes --self-test: 75 assertions over a temp git repo (real scan() path)
check-nul-bytes: OK (scanned 6069 text file(s) ...; no raw ASCII control bytes).
os-verify-lock: VERDICT command-exit 0 · held the lock 8s · waited 0s

skip-changeset: internal PM tooling only, publishes nothing.

Generated by Claude Code


Generated by Claude Code

…, and print the unreachable set by default
Two gate families declared a population that was never a population: a MIME
type and a git ref, scraped out of their own operational constants by a
"contains a slash" heuristic. A slash separates several namespaces and only
one of them is the filesystem.
`isNonPathNamespace` closes the class (IANA media types; the `refs/` and
`origin/` git revision namespaces) rather than the two instances — the same
literals survive in a dozen other gate sources where a real hint sits beside
them and hides the effect. This file already knew: DEFAULT_BASE_REF is
assembled from two unslashed halves precisely so `origin/main` never enters
its own hint set.
The unreachable listing now prints on every run instead of only under
`--residue`, grouped by whether the family is unreachable by construction or
its layout moved, and headed by the correction that these gates are NOT
skipped — CI runs them on every pull request.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019T1sSZbQTnLhrK9HhNdNiB
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@qq9340100@claude