fix(pm): read a package-root-anchored literal against the root its writer binds, and stop the residue calling it a non-path - #14450

Merged
baozhoutao merged 4 commits into
mainfrom
claude/issue-14208-dispatch-gates-package-root-anchor
Sep 2, 2026
Merged

fix(pm): read a package-root-anchored literal against the root its writer binds, and stop the residue calling it a non-path#14450
baozhoutao merged 4 commits into
mainfrom
claude/issue-14208-dispatch-gates-package-root-anchor

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#14208
Fixes#14289

Two deliverables in one PR on one file, scripts/pm/dispatch-gates.mjs, as three commits: the package-root anchor (#14208), the residue reason text it is coupled to (#14208), and the read-edge prose repair (#14289, no behaviour change).

#14208 — measure first, as the card required

Premise re-confirmed on this PR's base (ad54eb342), with the card's own command:

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --residue packages/spec/src/index.ts

check:generated printed 5 of 10 declared literal(s) reach nothing tracked, the three named literals among them, each under the reason no tracked path under its first segment; never was a repo path — while all three sit on disk under packages/spec/.

The population sweep

Over the 201 discovered families' 195 scannable gate scripts, resolving every import.meta-seeded binding through the tool's own resolvePathExpression:

measurementvalue
discovered gate scripts binding a package root8 (all packages/spec)
...of those, scripts carrying a hint dead at the repo root2
dead-at-root hints in that population6
...that re-anchor onto something the tree HAS5
...refused because the re-anchored form reaches nothing1 ('zod', a package specifier)

⚠️The card's description of the shape does not survive the sweep, and this changed the rule. The card described the class as literals passed to resolve(PKG_DIR, …). That shape is real — 6 such literals across 6 of the 8 scripts, every one a tracked file — but it is not where the confirmed instance comes from. All five of check:generated's dead literals are artifact: values in its GATED ledger (artifact: 'api-surface/', artifact: 'src/meta-spelling/meta-url-data.generated.ts', …), describing what each gate verifies; nothing resolves them. A rule restricted to the syntactic shape would have repaired none of the three dead leads the card names. The restriction was measured and dropped; the reachability test is what keeps the wider reading honest.

The rule that landed

A hint is re-read against the package root its writer binds only when deepestTrackedPrefix is empty — no tracked path begins with even its first segment, which is exactly the branch whose reason text was false — and only if the re-anchored form reaches the tree through hintReachesTree. It replaces rather than adds. Neither priced refusal is relaxed and neither is reachable from here: admission is untouched (a literal that is not already a hint never arrives), and a re-anchoring that names nothing is dropped, so the rule cannot invent a path. The layout-moved class (a shorter surviving prefix) is deliberately left alone — its first segment is a real repo directory, so re-anchoring it would replace a triage lead with a fabricated one.

The price, before and after, on this base

Over 201 discovered families and 7900 tracked files:

beforeafter
watch-hint (gate, file) pairs142089142139 (+50, 0 lost)
families whose hint set moves1 (check:generated, no other)
pairs re-attributed (same pair, new via)0 — every replaced spelling was dead, so it covered nothing to re-attribute
(check, hint) live / inert1108 / 1521113 / 147
distinct hints in the fleet725722 — three of the five re-anchored forms are already spelled from the root by other gates
families carrying dead literals4645
dead literals152147
...in the empty-deepest class128123

Provenance, which is the criterion this file prices and never volume: 5 of 5 admitted hints are TRUE leads, and 50 of 50 pairs with them, each verified at its declaration site — they are check:generated's own artifact ledger and verifying those artifacts is the gate's whole job:

re-anchored hintfilesledger entry
packages/spec/api-surface17check:api-surface
packages/spec/export-origins17check:export-origins
packages/spec/declaration-map14check:declaration-map
packages/spec/src/meta-spelling/meta-url-data.generated.ts1check:meta-url-spelling
packages/spec/liveness/state-counts.md1the liveness counts

0 fabricated. For scale, hintCovers' docblock prices the bare-top-level-word admission it refuses at +139084 pairs on this corpus; this is 0.04% of it, and moduleRelativeDirectoryHint's naive-resolved-form widening was +53 hints for 1 true lead against this rule's 5 for 5.

The whole --residue output moves by exactly one row before and after (diffed line by line).

The reason text

The by-construction sentence ended never was a repo path — a claim about EVERY base made from evidence about ONE, and a dead-lead label printed over three live files. It now reads:

'HINT' — no tracked path under its first segment; a specifier, or a path anchored at a base this scan did not resolve

Checkable by the reader (git ls-files on the first segment is empty) and no longer asserting a base it did not test. The three docblocks stating the same universal as current fact are corrected with it; the two that quote it as history keep the quote and say it is retired, so a grep still lands on the incident. The pins move with the text: pinned to a retired phrase, the dist-freshness case would pass on any tree at all — a green over the exact regression it exists to catch.

Ablation

Anchor call replaced with return [...hints] on the committed tree, mutation proven on disk before the run (anchor occurrences 1 to 0, injected marker 1, blob 9743275c to e51f9d24), restore leg trapped and then proven by byte comparison (git diff HEAD empty, blob back to the HEAD blob 9743275c). Ablated, check:generated returns to 5 of 10 declared literal(s) reach nothing tracked with the three named literals marked. The ablated run also shows the corrected reason text in place, so the two halves are independent — the reason-text repair is the deliverable the card specified for the no-repair branch, and it stands on its own.

#14289 — prose only, no behaviour change

The fourth-edge docblock above packageManifestTargets opened "The three follows above all end at a PROGRAM … and inherit that program's declared population". The middle one does not: readProgramTargetsInSource fills entry.reads / entry.readOrigin and nothing feeds those into hintsOfModule. The read edge reaches a card only through the last key in coveringKey, an identity claim about a single FILE (#13000).

Re-derived at this PR's base, not copied from the card:

EDGE import: 27 families, 38 (family,target) pairs, 15 distinct targets
EDGE run: 1 family, 1 pair, 1 distinct target
EDGE manifest: 10 families, 10 pairs, 2 distinct targets
EDGE read: 12 families, 16 pairs, 15 distinct targets
INHERITED HINTS BY EDGE LABEL:
import: 373 hints across 13 families
manifest: 7 hints across 7 families
run: 1 hint across 1 family
read: 0

Read targets that carried a hint into their family: 0. Of the 16 read-edge pairs, 8 are answered by the READ identity key and 8 by an earlier key — consistent with coveringKey's "additive by construction" note.

The two numberings are reconciled rather than flattened: the docblock numbers all four scans in source order (so the manifest edge is the fourth), discoverFamilies numbers only the population follows (so the same edge is its third), and each now says which. The claim that there are three population follows all ending at a program is gone, and packageManifestTargets' "the other three follows take" is corrected to two.

Gates

Union re-run after the final commit, at git rev-parse --short HEAD = 70c1aedff (merge of origin/maind16df7418). Derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands — 15 families, all named by path, 0 by kind; the change set is exactly the one file. Exit codes captured to a file before any pipe.

gateexitverdict
pnpm check:pm-dispatch-gates (the tool's --self-test)0RAN-PASS — dispatch-gates self-test: 1240 cases pass
node scripts/check-ci-filter-parity.mjs0RAN-PASS
node scripts/check-cross-package-test-inputs.mjs / pnpm check:cross-package-test-inputs0 / 0RAN-PASS (both spellings)
node scripts/check-self-test-wired.mjs0RAN-PASS
node scripts/check-shard-attestation.mjs0RAN-PASS
node scripts/check-test-completeness.mjs3NOT MEASURED — no test log to hand it; the gate says so itself and calls exit 3 distinct from a finding's 1
pnpm check:agent-test-spelling0RAN-PASS
pnpm check:bash32-floor0RAN-PASS
pnpm check:cli-command-ids0RAN-PASS
pnpm check:declared-population-live0RAN-PASS
pnpm check:entry-guard0RAN-PASS
pnpm check:parse-guard0RAN-PASS
pnpm check:pnpm-filter-targets0RAN-PASS
pnpm check:watch-hint-literal0RAN-PASS
pnpm check:ratchet-remedy-authority0RAN-PASS (beyond the derived family)
node scripts/check-self-test-workflow-commands.mjs0RAN-PASS (beyond the derived family)
pnpm check:nul-bytes0RAN-PASS

Lint, narrowed and declared.pnpm lint is eslint . --no-inline-config over the whole repo; the run here is targeted at the one changed file: 1 file linted, 0 errors, 0 warnings, exit 0, the count read from --format json. The narrowing is a measurement rather than a gap because this repo runs one eslint.config.mjs which never enables type-aware linting for any file (no parserOptions.project, no typed rules — stated and positive-control-measured in that config's own header), so a one-file diff cannot move the verdict on any untouched file, and the file is a config match rather than an ignore (it produced a result object, not an ignore warning).

Changeset: none. This is a scripts/**-only diff that publishes nothing from any package, which is the repo convention on recent scripts-only PRs; the skip-changeset label carries it.

Generated by Claude Code


Generated by Claude Code

…iter binds
extractWatchHints resolves a path literal against exactly two anchors: the
repo root, and the writer's own directory when the author spelled a ./ or ../
prefix. A gate that lives inside a package binds a third at module top --
join(dirname(fileURLToPath(import.meta.url)), '..') -- and every literal it
writes against that binding was read from the REPO root, where nothing of the
sort exists. check:generated's five artifact-ledger literals are the confirmed
instance: all five alive under packages/spec/ and all five printed by the
residue as dead leads, under a reason text asserting they were never repo
paths.
Measured first, as the card required. Over the 201 discovered families' 195
scannable gate scripts, 8 bind a package root this way, 2 of them carry a hint
dead at the repo root, 6 such hints exist, 5 re-anchor onto something the tree
has and 1 is refused because it reaches nothing. Price: 142089 -> 142139
watch-hint (gate, file) pairs (+50, 0 lost, 0 re-attributed), one family's hint
set moves, and 5 of 5 admitted hints are true leads verified at their
declaration site.
The admitted class is exactly the one whose residue sentence was false: a hint
is re-anchored only when no tracked path begins with even its first segment.
The layout-moved class is left to triage, admission is untouched, and a
re-anchoring that reaches nothing is dropped, so the rule cannot invent a path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
unreachableReason's by-construction branch ended "never was a repo path" -- a
claim about EVERY base, made from evidence about ONE. check:generated's
api-surface, export-origins and src/meta-spelling/... all printed under it
while sitting on disk under packages/spec/: a dead-lead label on a live path,
telling the reader there is nothing to chase about three files they could
open.
The sentence now names what the sweep actually established (no tracked path
under the literal's first segment) and the two ways that happens, without
asserting the literal never existed anywhere. The three docblocks that stated
the same universal as current fact are corrected with it; the two that quote it
as history keep the quote and say it is retired, so a grep for it still lands
on the incident.
The pins move with the text rather than staying on the retired phrase: pinned
to a string nothing renders, the dist-freshness case would pass on any tree at
all -- a green over the exact regression it exists to catch -- so it now
asserts against the sentence that branch prints today, and the sweep case
asserts both halves.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
The fourth-edge docblock above packageManifestTargets opened "The three
follows above all end at a PROGRAM ... and inherit that program's declared
population". The middle one does not: readProgramTargetsInSource fills
entry.reads / entry.readOrigin and nothing feeds those into hintsOfModule.
The read edge reaches a card only through the LAST key in coveringKey, which
is an identity claim about a single FILE and never a population.
Re-derived at this PR's base: the read edge is 12 families, 16 (family,target)
pairs over 15 distinct targets, and it carries 0 inherited hints -- against 373
for the import edge, 7 for the manifest edge and 1 for the run edge. Reading a
program's TEXT is not performing its work, so identity-only is the correct
behaviour and the sentence was the defect. No behaviour change.
The two numberings inside the file also disagreed: discoverFamilies called the
spawn follow the SECOND edge and the manifest follow the THIRD, while the
docblock called the manifest one the FOURTH. They count different things --
source-order edges, and population follows -- so each now says which, and the
claim that there are three population follows all ending at a program is gone.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 2, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 2, 2026 08:12
@baozhoutao
baozhoutao added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 9e286e2Sep 2, 2026
36 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14208-dispatch-gates-package-root-anchor branch September 2, 2026 08:39
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

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(pm): read a package-root-anchored literal against the root its writer binds, and stop the residue calling it a non-path - #14450

Merged
baozhoutao merged 4 commits into
mainfrom
claude/issue-14208-dispatch-gates-package-root-anchor
Sep 2, 2026
Merged

fix(pm): read a package-root-anchored literal against the root its writer binds, and stop the residue calling it a non-path#14450
baozhoutao merged 4 commits into
mainfrom
claude/issue-14208-dispatch-gates-package-root-anchor

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#14208
Fixes#14289

Two deliverables in one PR on one file, scripts/pm/dispatch-gates.mjs, as three commits: the package-root anchor (#14208), the residue reason text it is coupled to (#14208), and the read-edge prose repair (#14289, no behaviour change).

#14208 — measure first, as the card required

Premise re-confirmed on this PR's base (ad54eb342), with the card's own command:

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --residue packages/spec/src/index.ts

check:generated printed 5 of 10 declared literal(s) reach nothing tracked, the three named literals among them, each under the reason no tracked path under its first segment; never was a repo path — while all three sit on disk under packages/spec/.

The population sweep

Over the 201 discovered families' 195 scannable gate scripts, resolving every import.meta-seeded binding through the tool's own resolvePathExpression:

measurementvalue
discovered gate scripts binding a package root8 (all packages/spec)
...of those, scripts carrying a hint dead at the repo root2
dead-at-root hints in that population6
...that re-anchor onto something the tree HAS5
...refused because the re-anchored form reaches nothing1 ('zod', a package specifier)

⚠️The card's description of the shape does not survive the sweep, and this changed the rule. The card described the class as literals passed to resolve(PKG_DIR, …). That shape is real — 6 such literals across 6 of the 8 scripts, every one a tracked file — but it is not where the confirmed instance comes from. All five of check:generated's dead literals are artifact: values in its GATED ledger (artifact: 'api-surface/', artifact: 'src/meta-spelling/meta-url-data.generated.ts', …), describing what each gate verifies; nothing resolves them. A rule restricted to the syntactic shape would have repaired none of the three dead leads the card names. The restriction was measured and dropped; the reachability test is what keeps the wider reading honest.

The rule that landed

A hint is re-read against the package root its writer binds only when deepestTrackedPrefix is empty — no tracked path begins with even its first segment, which is exactly the branch whose reason text was false — and only if the re-anchored form reaches the tree through hintReachesTree. It replaces rather than adds. Neither priced refusal is relaxed and neither is reachable from here: admission is untouched (a literal that is not already a hint never arrives), and a re-anchoring that names nothing is dropped, so the rule cannot invent a path. The layout-moved class (a shorter surviving prefix) is deliberately left alone — its first segment is a real repo directory, so re-anchoring it would replace a triage lead with a fabricated one.

The price, before and after, on this base

Over 201 discovered families and 7900 tracked files:

beforeafter
watch-hint (gate, file) pairs142089142139 (+50, 0 lost)
families whose hint set moves1 (check:generated, no other)
pairs re-attributed (same pair, new via)0 — every replaced spelling was dead, so it covered nothing to re-attribute
(check, hint) live / inert1108 / 1521113 / 147
distinct hints in the fleet725722 — three of the five re-anchored forms are already spelled from the root by other gates
families carrying dead literals4645
dead literals152147
...in the empty-deepest class128123

Provenance, which is the criterion this file prices and never volume: 5 of 5 admitted hints are TRUE leads, and 50 of 50 pairs with them, each verified at its declaration site — they are check:generated's own artifact ledger and verifying those artifacts is the gate's whole job:

re-anchored hintfilesledger entry
packages/spec/api-surface17check:api-surface
packages/spec/export-origins17check:export-origins
packages/spec/declaration-map14check:declaration-map
packages/spec/src/meta-spelling/meta-url-data.generated.ts1check:meta-url-spelling
packages/spec/liveness/state-counts.md1the liveness counts

0 fabricated. For scale, hintCovers' docblock prices the bare-top-level-word admission it refuses at +139084 pairs on this corpus; this is 0.04% of it, and moduleRelativeDirectoryHint's naive-resolved-form widening was +53 hints for 1 true lead against this rule's 5 for 5.

The whole --residue output moves by exactly one row before and after (diffed line by line).

The reason text

The by-construction sentence ended never was a repo path — a claim about EVERY base made from evidence about ONE, and a dead-lead label printed over three live files. It now reads:

'HINT' — no tracked path under its first segment; a specifier, or a path anchored at a base this scan did not resolve

Checkable by the reader (git ls-files on the first segment is empty) and no longer asserting a base it did not test. The three docblocks stating the same universal as current fact are corrected with it; the two that quote it as history keep the quote and say it is retired, so a grep still lands on the incident. The pins move with the text: pinned to a retired phrase, the dist-freshness case would pass on any tree at all — a green over the exact regression it exists to catch.

Ablation

Anchor call replaced with return [...hints] on the committed tree, mutation proven on disk before the run (anchor occurrences 1 to 0, injected marker 1, blob 9743275c to e51f9d24), restore leg trapped and then proven by byte comparison (git diff HEAD empty, blob back to the HEAD blob 9743275c). Ablated, check:generated returns to 5 of 10 declared literal(s) reach nothing tracked with the three named literals marked. The ablated run also shows the corrected reason text in place, so the two halves are independent — the reason-text repair is the deliverable the card specified for the no-repair branch, and it stands on its own.

#14289 — prose only, no behaviour change

The fourth-edge docblock above packageManifestTargets opened "The three follows above all end at a PROGRAM … and inherit that program's declared population". The middle one does not: readProgramTargetsInSource fills entry.reads / entry.readOrigin and nothing feeds those into hintsOfModule. The read edge reaches a card only through the last key in coveringKey, an identity claim about a single FILE (#13000).

Re-derived at this PR's base, not copied from the card:

EDGE import: 27 families, 38 (family,target) pairs, 15 distinct targets
EDGE run: 1 family, 1 pair, 1 distinct target
EDGE manifest: 10 families, 10 pairs, 2 distinct targets
EDGE read: 12 families, 16 pairs, 15 distinct targets
INHERITED HINTS BY EDGE LABEL:
import: 373 hints across 13 families
manifest: 7 hints across 7 families
run: 1 hint across 1 family
read: 0

Read targets that carried a hint into their family: 0. Of the 16 read-edge pairs, 8 are answered by the READ identity key and 8 by an earlier key — consistent with coveringKey's "additive by construction" note.

The two numberings are reconciled rather than flattened: the docblock numbers all four scans in source order (so the manifest edge is the fourth), discoverFamilies numbers only the population follows (so the same edge is its third), and each now says which. The claim that there are three population follows all ending at a program is gone, and packageManifestTargets' "the other three follows take" is corrected to two.

Gates

Union re-run after the final commit, at git rev-parse --short HEAD = 70c1aedff (merge of origin/maind16df7418). Derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands — 15 families, all named by path, 0 by kind; the change set is exactly the one file. Exit codes captured to a file before any pipe.

gateexitverdict
pnpm check:pm-dispatch-gates (the tool's --self-test)0RAN-PASS — dispatch-gates self-test: 1240 cases pass
node scripts/check-ci-filter-parity.mjs0RAN-PASS
node scripts/check-cross-package-test-inputs.mjs / pnpm check:cross-package-test-inputs0 / 0RAN-PASS (both spellings)
node scripts/check-self-test-wired.mjs0RAN-PASS
node scripts/check-shard-attestation.mjs0RAN-PASS
node scripts/check-test-completeness.mjs3NOT MEASURED — no test log to hand it; the gate says so itself and calls exit 3 distinct from a finding's 1
pnpm check:agent-test-spelling0RAN-PASS
pnpm check:bash32-floor0RAN-PASS
pnpm check:cli-command-ids0RAN-PASS
pnpm check:declared-population-live0RAN-PASS
pnpm check:entry-guard0RAN-PASS
pnpm check:parse-guard0RAN-PASS
pnpm check:pnpm-filter-targets0RAN-PASS
pnpm check:watch-hint-literal0RAN-PASS
pnpm check:ratchet-remedy-authority0RAN-PASS (beyond the derived family)
node scripts/check-self-test-workflow-commands.mjs0RAN-PASS (beyond the derived family)
pnpm check:nul-bytes0RAN-PASS

Lint, narrowed and declared.pnpm lint is eslint . --no-inline-config over the whole repo; the run here is targeted at the one changed file: 1 file linted, 0 errors, 0 warnings, exit 0, the count read from --format json. The narrowing is a measurement rather than a gap because this repo runs one eslint.config.mjs which never enables type-aware linting for any file (no parserOptions.project, no typed rules — stated and positive-control-measured in that config's own header), so a one-file diff cannot move the verdict on any untouched file, and the file is a config match rather than an ignore (it produced a result object, not an ignore warning).

Changeset: none. This is a scripts/**-only diff that publishes nothing from any package, which is the repo convention on recent scripts-only PRs; the skip-changeset label carries it.

Generated by Claude Code


Generated by Claude Code

…iter binds
extractWatchHints resolves a path literal against exactly two anchors: the
repo root, and the writer's own directory when the author spelled a ./ or ../
prefix. A gate that lives inside a package binds a third at module top --
join(dirname(fileURLToPath(import.meta.url)), '..') -- and every literal it
writes against that binding was read from the REPO root, where nothing of the
sort exists. check:generated's five artifact-ledger literals are the confirmed
instance: all five alive under packages/spec/ and all five printed by the
residue as dead leads, under a reason text asserting they were never repo
paths.
Measured first, as the card required. Over the 201 discovered families' 195
scannable gate scripts, 8 bind a package root this way, 2 of them carry a hint
dead at the repo root, 6 such hints exist, 5 re-anchor onto something the tree
has and 1 is refused because it reaches nothing. Price: 142089 -> 142139
watch-hint (gate, file) pairs (+50, 0 lost, 0 re-attributed), one family's hint
set moves, and 5 of 5 admitted hints are true leads verified at their
declaration site.
The admitted class is exactly the one whose residue sentence was false: a hint
is re-anchored only when no tracked path begins with even its first segment.
The layout-moved class is left to triage, admission is untouched, and a
re-anchoring that reaches nothing is dropped, so the rule cannot invent a path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
unreachableReason's by-construction branch ended "never was a repo path" -- a
claim about EVERY base, made from evidence about ONE. check:generated's
api-surface, export-origins and src/meta-spelling/... all printed under it
while sitting on disk under packages/spec/: a dead-lead label on a live path,
telling the reader there is nothing to chase about three files they could
open.
The sentence now names what the sweep actually established (no tracked path
under the literal's first segment) and the two ways that happens, without
asserting the literal never existed anywhere. The three docblocks that stated
the same universal as current fact are corrected with it; the two that quote it
as history keep the quote and say it is retired, so a grep for it still lands
on the incident.
The pins move with the text rather than staying on the retired phrase: pinned
to a string nothing renders, the dist-freshness case would pass on any tree at
all -- a green over the exact regression it exists to catch -- so it now
asserts against the sentence that branch prints today, and the sweep case
asserts both halves.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
The fourth-edge docblock above packageManifestTargets opened "The three
follows above all end at a PROGRAM ... and inherit that program's declared
population". The middle one does not: readProgramTargetsInSource fills
entry.reads / entry.readOrigin and nothing feeds those into hintsOfModule.
The read edge reaches a card only through the LAST key in coveringKey, which
is an identity claim about a single FILE and never a population.
Re-derived at this PR's base: the read edge is 12 families, 16 (family,target)
pairs over 15 distinct targets, and it carries 0 inherited hints -- against 373
for the import edge, 7 for the manifest edge and 1 for the run edge. Reading a
program's TEXT is not performing its work, so identity-only is the correct
behaviour and the sentence was the defect. No behaviour change.
The two numberings inside the file also disagreed: discoverFamilies called the
spawn follow the SECOND edge and the manifest follow the THIRD, while the
docblock called the manifest one the FOURTH. They count different things --
source-order edges, and population follows -- so each now says which, and the
claim that there are three population follows all ending at a program is gone.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 2, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 2, 2026 08:12
@baozhoutao
baozhoutao added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 9e286e2Sep 2, 2026
36 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14208-dispatch-gates-package-root-anchor branch September 2, 2026 08:39
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

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(pm): read a package-root-anchored literal against the root its writer binds, and stop the residue calling it a non-path - #14450

Merged
baozhoutao merged 4 commits into
mainfrom
claude/issue-14208-dispatch-gates-package-root-anchor
Sep 2, 2026
Merged

fix(pm): read a package-root-anchored literal against the root its writer binds, and stop the residue calling it a non-path#14450
baozhoutao merged 4 commits into
mainfrom
claude/issue-14208-dispatch-gates-package-root-anchor

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#14208
Fixes#14289

Two deliverables in one PR on one file, scripts/pm/dispatch-gates.mjs, as three commits: the package-root anchor (#14208), the residue reason text it is coupled to (#14208), and the read-edge prose repair (#14289, no behaviour change).

#14208 — measure first, as the card required

Premise re-confirmed on this PR's base (ad54eb342), with the card's own command:

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --residue packages/spec/src/index.ts

check:generated printed 5 of 10 declared literal(s) reach nothing tracked, the three named literals among them, each under the reason no tracked path under its first segment; never was a repo path — while all three sit on disk under packages/spec/.

The population sweep

Over the 201 discovered families' 195 scannable gate scripts, resolving every import.meta-seeded binding through the tool's own resolvePathExpression:

measurementvalue
discovered gate scripts binding a package root8 (all packages/spec)
...of those, scripts carrying a hint dead at the repo root2
dead-at-root hints in that population6
...that re-anchor onto something the tree HAS5
...refused because the re-anchored form reaches nothing1 ('zod', a package specifier)

⚠️The card's description of the shape does not survive the sweep, and this changed the rule. The card described the class as literals passed to resolve(PKG_DIR, …). That shape is real — 6 such literals across 6 of the 8 scripts, every one a tracked file — but it is not where the confirmed instance comes from. All five of check:generated's dead literals are artifact: values in its GATED ledger (artifact: 'api-surface/', artifact: 'src/meta-spelling/meta-url-data.generated.ts', …), describing what each gate verifies; nothing resolves them. A rule restricted to the syntactic shape would have repaired none of the three dead leads the card names. The restriction was measured and dropped; the reachability test is what keeps the wider reading honest.

The rule that landed

A hint is re-read against the package root its writer binds only when deepestTrackedPrefix is empty — no tracked path begins with even its first segment, which is exactly the branch whose reason text was false — and only if the re-anchored form reaches the tree through hintReachesTree. It replaces rather than adds. Neither priced refusal is relaxed and neither is reachable from here: admission is untouched (a literal that is not already a hint never arrives), and a re-anchoring that names nothing is dropped, so the rule cannot invent a path. The layout-moved class (a shorter surviving prefix) is deliberately left alone — its first segment is a real repo directory, so re-anchoring it would replace a triage lead with a fabricated one.

The price, before and after, on this base

Over 201 discovered families and 7900 tracked files:

beforeafter
watch-hint (gate, file) pairs142089142139 (+50, 0 lost)
families whose hint set moves1 (check:generated, no other)
pairs re-attributed (same pair, new via)0 — every replaced spelling was dead, so it covered nothing to re-attribute
(check, hint) live / inert1108 / 1521113 / 147
distinct hints in the fleet725722 — three of the five re-anchored forms are already spelled from the root by other gates
families carrying dead literals4645
dead literals152147
...in the empty-deepest class128123

Provenance, which is the criterion this file prices and never volume: 5 of 5 admitted hints are TRUE leads, and 50 of 50 pairs with them, each verified at its declaration site — they are check:generated's own artifact ledger and verifying those artifacts is the gate's whole job:

re-anchored hintfilesledger entry
packages/spec/api-surface17check:api-surface
packages/spec/export-origins17check:export-origins
packages/spec/declaration-map14check:declaration-map
packages/spec/src/meta-spelling/meta-url-data.generated.ts1check:meta-url-spelling
packages/spec/liveness/state-counts.md1the liveness counts

0 fabricated. For scale, hintCovers' docblock prices the bare-top-level-word admission it refuses at +139084 pairs on this corpus; this is 0.04% of it, and moduleRelativeDirectoryHint's naive-resolved-form widening was +53 hints for 1 true lead against this rule's 5 for 5.

The whole --residue output moves by exactly one row before and after (diffed line by line).

The reason text

The by-construction sentence ended never was a repo path — a claim about EVERY base made from evidence about ONE, and a dead-lead label printed over three live files. It now reads:

'HINT' — no tracked path under its first segment; a specifier, or a path anchored at a base this scan did not resolve

Checkable by the reader (git ls-files on the first segment is empty) and no longer asserting a base it did not test. The three docblocks stating the same universal as current fact are corrected with it; the two that quote it as history keep the quote and say it is retired, so a grep still lands on the incident. The pins move with the text: pinned to a retired phrase, the dist-freshness case would pass on any tree at all — a green over the exact regression it exists to catch.

Ablation

Anchor call replaced with return [...hints] on the committed tree, mutation proven on disk before the run (anchor occurrences 1 to 0, injected marker 1, blob 9743275c to e51f9d24), restore leg trapped and then proven by byte comparison (git diff HEAD empty, blob back to the HEAD blob 9743275c). Ablated, check:generated returns to 5 of 10 declared literal(s) reach nothing tracked with the three named literals marked. The ablated run also shows the corrected reason text in place, so the two halves are independent — the reason-text repair is the deliverable the card specified for the no-repair branch, and it stands on its own.

#14289 — prose only, no behaviour change

The fourth-edge docblock above packageManifestTargets opened "The three follows above all end at a PROGRAM … and inherit that program's declared population". The middle one does not: readProgramTargetsInSource fills entry.reads / entry.readOrigin and nothing feeds those into hintsOfModule. The read edge reaches a card only through the last key in coveringKey, an identity claim about a single FILE (#13000).

Re-derived at this PR's base, not copied from the card:

EDGE import: 27 families, 38 (family,target) pairs, 15 distinct targets
EDGE run: 1 family, 1 pair, 1 distinct target
EDGE manifest: 10 families, 10 pairs, 2 distinct targets
EDGE read: 12 families, 16 pairs, 15 distinct targets
INHERITED HINTS BY EDGE LABEL:
import: 373 hints across 13 families
manifest: 7 hints across 7 families
run: 1 hint across 1 family
read: 0

Read targets that carried a hint into their family: 0. Of the 16 read-edge pairs, 8 are answered by the READ identity key and 8 by an earlier key — consistent with coveringKey's "additive by construction" note.

The two numberings are reconciled rather than flattened: the docblock numbers all four scans in source order (so the manifest edge is the fourth), discoverFamilies numbers only the population follows (so the same edge is its third), and each now says which. The claim that there are three population follows all ending at a program is gone, and packageManifestTargets' "the other three follows take" is corrected to two.

Gates

Union re-run after the final commit, at git rev-parse --short HEAD = 70c1aedff (merge of origin/maind16df7418). Derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands — 15 families, all named by path, 0 by kind; the change set is exactly the one file. Exit codes captured to a file before any pipe.

gateexitverdict
pnpm check:pm-dispatch-gates (the tool's --self-test)0RAN-PASS — dispatch-gates self-test: 1240 cases pass
node scripts/check-ci-filter-parity.mjs0RAN-PASS
node scripts/check-cross-package-test-inputs.mjs / pnpm check:cross-package-test-inputs0 / 0RAN-PASS (both spellings)
node scripts/check-self-test-wired.mjs0RAN-PASS
node scripts/check-shard-attestation.mjs0RAN-PASS
node scripts/check-test-completeness.mjs3NOT MEASURED — no test log to hand it; the gate says so itself and calls exit 3 distinct from a finding's 1
pnpm check:agent-test-spelling0RAN-PASS
pnpm check:bash32-floor0RAN-PASS
pnpm check:cli-command-ids0RAN-PASS
pnpm check:declared-population-live0RAN-PASS
pnpm check:entry-guard0RAN-PASS
pnpm check:parse-guard0RAN-PASS
pnpm check:pnpm-filter-targets0RAN-PASS
pnpm check:watch-hint-literal0RAN-PASS
pnpm check:ratchet-remedy-authority0RAN-PASS (beyond the derived family)
node scripts/check-self-test-workflow-commands.mjs0RAN-PASS (beyond the derived family)
pnpm check:nul-bytes0RAN-PASS

Lint, narrowed and declared.pnpm lint is eslint . --no-inline-config over the whole repo; the run here is targeted at the one changed file: 1 file linted, 0 errors, 0 warnings, exit 0, the count read from --format json. The narrowing is a measurement rather than a gap because this repo runs one eslint.config.mjs which never enables type-aware linting for any file (no parserOptions.project, no typed rules — stated and positive-control-measured in that config's own header), so a one-file diff cannot move the verdict on any untouched file, and the file is a config match rather than an ignore (it produced a result object, not an ignore warning).

Changeset: none. This is a scripts/**-only diff that publishes nothing from any package, which is the repo convention on recent scripts-only PRs; the skip-changeset label carries it.

Generated by Claude Code


Generated by Claude Code

…iter binds
extractWatchHints resolves a path literal against exactly two anchors: the
repo root, and the writer's own directory when the author spelled a ./ or ../
prefix. A gate that lives inside a package binds a third at module top --
join(dirname(fileURLToPath(import.meta.url)), '..') -- and every literal it
writes against that binding was read from the REPO root, where nothing of the
sort exists. check:generated's five artifact-ledger literals are the confirmed
instance: all five alive under packages/spec/ and all five printed by the
residue as dead leads, under a reason text asserting they were never repo
paths.
Measured first, as the card required. Over the 201 discovered families' 195
scannable gate scripts, 8 bind a package root this way, 2 of them carry a hint
dead at the repo root, 6 such hints exist, 5 re-anchor onto something the tree
has and 1 is refused because it reaches nothing. Price: 142089 -> 142139
watch-hint (gate, file) pairs (+50, 0 lost, 0 re-attributed), one family's hint
set moves, and 5 of 5 admitted hints are true leads verified at their
declaration site.
The admitted class is exactly the one whose residue sentence was false: a hint
is re-anchored only when no tracked path begins with even its first segment.
The layout-moved class is left to triage, admission is untouched, and a
re-anchoring that reaches nothing is dropped, so the rule cannot invent a path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
unreachableReason's by-construction branch ended "never was a repo path" -- a
claim about EVERY base, made from evidence about ONE. check:generated's
api-surface, export-origins and src/meta-spelling/... all printed under it
while sitting on disk under packages/spec/: a dead-lead label on a live path,
telling the reader there is nothing to chase about three files they could
open.
The sentence now names what the sweep actually established (no tracked path
under the literal's first segment) and the two ways that happens, without
asserting the literal never existed anywhere. The three docblocks that stated
the same universal as current fact are corrected with it; the two that quote it
as history keep the quote and say it is retired, so a grep for it still lands
on the incident.
The pins move with the text rather than staying on the retired phrase: pinned
to a string nothing renders, the dist-freshness case would pass on any tree at
all -- a green over the exact regression it exists to catch -- so it now
asserts against the sentence that branch prints today, and the sweep case
asserts both halves.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
The fourth-edge docblock above packageManifestTargets opened "The three
follows above all end at a PROGRAM ... and inherit that program's declared
population". The middle one does not: readProgramTargetsInSource fills
entry.reads / entry.readOrigin and nothing feeds those into hintsOfModule.
The read edge reaches a card only through the LAST key in coveringKey, which
is an identity claim about a single FILE and never a population.
Re-derived at this PR's base: the read edge is 12 families, 16 (family,target)
pairs over 15 distinct targets, and it carries 0 inherited hints -- against 373
for the import edge, 7 for the manifest edge and 1 for the run edge. Reading a
program's TEXT is not performing its work, so identity-only is the correct
behaviour and the sentence was the defect. No behaviour change.
The two numberings inside the file also disagreed: discoverFamilies called the
spawn follow the SECOND edge and the manifest follow the THIRD, while the
docblock called the manifest one the FOURTH. They count different things --
source-order edges, and population follows -- so each now says which, and the
claim that there are three population follows all ending at a program is gone.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 2, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 2, 2026 08:12
@baozhoutao
baozhoutao added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 9e286e2Sep 2, 2026
36 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14208-dispatch-gates-package-root-anchor branch September 2, 2026 08:39
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

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(pm): read a package-root-anchored literal against the root its writer binds, and stop the residue calling it a non-path - #14450

Merged
baozhoutao merged 4 commits into
mainfrom
claude/issue-14208-dispatch-gates-package-root-anchor
Sep 2, 2026
Merged

fix(pm): read a package-root-anchored literal against the root its writer binds, and stop the residue calling it a non-path#14450
baozhoutao merged 4 commits into
mainfrom
claude/issue-14208-dispatch-gates-package-root-anchor

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#14208
Fixes#14289

Two deliverables in one PR on one file, scripts/pm/dispatch-gates.mjs, as three commits: the package-root anchor (#14208), the residue reason text it is coupled to (#14208), and the read-edge prose repair (#14289, no behaviour change).

#14208 — measure first, as the card required

Premise re-confirmed on this PR's base (ad54eb342), with the card's own command:

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --residue packages/spec/src/index.ts

check:generated printed 5 of 10 declared literal(s) reach nothing tracked, the three named literals among them, each under the reason no tracked path under its first segment; never was a repo path — while all three sit on disk under packages/spec/.

The population sweep

Over the 201 discovered families' 195 scannable gate scripts, resolving every import.meta-seeded binding through the tool's own resolvePathExpression:

measurementvalue
discovered gate scripts binding a package root8 (all packages/spec)
...of those, scripts carrying a hint dead at the repo root2
dead-at-root hints in that population6
...that re-anchor onto something the tree HAS5
...refused because the re-anchored form reaches nothing1 ('zod', a package specifier)

⚠️The card's description of the shape does not survive the sweep, and this changed the rule. The card described the class as literals passed to resolve(PKG_DIR, …). That shape is real — 6 such literals across 6 of the 8 scripts, every one a tracked file — but it is not where the confirmed instance comes from. All five of check:generated's dead literals are artifact: values in its GATED ledger (artifact: 'api-surface/', artifact: 'src/meta-spelling/meta-url-data.generated.ts', …), describing what each gate verifies; nothing resolves them. A rule restricted to the syntactic shape would have repaired none of the three dead leads the card names. The restriction was measured and dropped; the reachability test is what keeps the wider reading honest.

The rule that landed

A hint is re-read against the package root its writer binds only when deepestTrackedPrefix is empty — no tracked path begins with even its first segment, which is exactly the branch whose reason text was false — and only if the re-anchored form reaches the tree through hintReachesTree. It replaces rather than adds. Neither priced refusal is relaxed and neither is reachable from here: admission is untouched (a literal that is not already a hint never arrives), and a re-anchoring that names nothing is dropped, so the rule cannot invent a path. The layout-moved class (a shorter surviving prefix) is deliberately left alone — its first segment is a real repo directory, so re-anchoring it would replace a triage lead with a fabricated one.

The price, before and after, on this base

Over 201 discovered families and 7900 tracked files:

beforeafter
watch-hint (gate, file) pairs142089142139 (+50, 0 lost)
families whose hint set moves1 (check:generated, no other)
pairs re-attributed (same pair, new via)0 — every replaced spelling was dead, so it covered nothing to re-attribute
(check, hint) live / inert1108 / 1521113 / 147
distinct hints in the fleet725722 — three of the five re-anchored forms are already spelled from the root by other gates
families carrying dead literals4645
dead literals152147
...in the empty-deepest class128123

Provenance, which is the criterion this file prices and never volume: 5 of 5 admitted hints are TRUE leads, and 50 of 50 pairs with them, each verified at its declaration site — they are check:generated's own artifact ledger and verifying those artifacts is the gate's whole job:

re-anchored hintfilesledger entry
packages/spec/api-surface17check:api-surface
packages/spec/export-origins17check:export-origins
packages/spec/declaration-map14check:declaration-map
packages/spec/src/meta-spelling/meta-url-data.generated.ts1check:meta-url-spelling
packages/spec/liveness/state-counts.md1the liveness counts

0 fabricated. For scale, hintCovers' docblock prices the bare-top-level-word admission it refuses at +139084 pairs on this corpus; this is 0.04% of it, and moduleRelativeDirectoryHint's naive-resolved-form widening was +53 hints for 1 true lead against this rule's 5 for 5.

The whole --residue output moves by exactly one row before and after (diffed line by line).

The reason text

The by-construction sentence ended never was a repo path — a claim about EVERY base made from evidence about ONE, and a dead-lead label printed over three live files. It now reads:

'HINT' — no tracked path under its first segment; a specifier, or a path anchored at a base this scan did not resolve

Checkable by the reader (git ls-files on the first segment is empty) and no longer asserting a base it did not test. The three docblocks stating the same universal as current fact are corrected with it; the two that quote it as history keep the quote and say it is retired, so a grep still lands on the incident. The pins move with the text: pinned to a retired phrase, the dist-freshness case would pass on any tree at all — a green over the exact regression it exists to catch.

Ablation

Anchor call replaced with return [...hints] on the committed tree, mutation proven on disk before the run (anchor occurrences 1 to 0, injected marker 1, blob 9743275c to e51f9d24), restore leg trapped and then proven by byte comparison (git diff HEAD empty, blob back to the HEAD blob 9743275c). Ablated, check:generated returns to 5 of 10 declared literal(s) reach nothing tracked with the three named literals marked. The ablated run also shows the corrected reason text in place, so the two halves are independent — the reason-text repair is the deliverable the card specified for the no-repair branch, and it stands on its own.

#14289 — prose only, no behaviour change

The fourth-edge docblock above packageManifestTargets opened "The three follows above all end at a PROGRAM … and inherit that program's declared population". The middle one does not: readProgramTargetsInSource fills entry.reads / entry.readOrigin and nothing feeds those into hintsOfModule. The read edge reaches a card only through the last key in coveringKey, an identity claim about a single FILE (#13000).

Re-derived at this PR's base, not copied from the card:

EDGE import: 27 families, 38 (family,target) pairs, 15 distinct targets
EDGE run: 1 family, 1 pair, 1 distinct target
EDGE manifest: 10 families, 10 pairs, 2 distinct targets
EDGE read: 12 families, 16 pairs, 15 distinct targets
INHERITED HINTS BY EDGE LABEL:
import: 373 hints across 13 families
manifest: 7 hints across 7 families
run: 1 hint across 1 family
read: 0

Read targets that carried a hint into their family: 0. Of the 16 read-edge pairs, 8 are answered by the READ identity key and 8 by an earlier key — consistent with coveringKey's "additive by construction" note.

The two numberings are reconciled rather than flattened: the docblock numbers all four scans in source order (so the manifest edge is the fourth), discoverFamilies numbers only the population follows (so the same edge is its third), and each now says which. The claim that there are three population follows all ending at a program is gone, and packageManifestTargets' "the other three follows take" is corrected to two.

Gates

Union re-run after the final commit, at git rev-parse --short HEAD = 70c1aedff (merge of origin/maind16df7418). Derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands — 15 families, all named by path, 0 by kind; the change set is exactly the one file. Exit codes captured to a file before any pipe.

gateexitverdict
pnpm check:pm-dispatch-gates (the tool's --self-test)0RAN-PASS — dispatch-gates self-test: 1240 cases pass
node scripts/check-ci-filter-parity.mjs0RAN-PASS
node scripts/check-cross-package-test-inputs.mjs / pnpm check:cross-package-test-inputs0 / 0RAN-PASS (both spellings)
node scripts/check-self-test-wired.mjs0RAN-PASS
node scripts/check-shard-attestation.mjs0RAN-PASS
node scripts/check-test-completeness.mjs3NOT MEASURED — no test log to hand it; the gate says so itself and calls exit 3 distinct from a finding's 1
pnpm check:agent-test-spelling0RAN-PASS
pnpm check:bash32-floor0RAN-PASS
pnpm check:cli-command-ids0RAN-PASS
pnpm check:declared-population-live0RAN-PASS
pnpm check:entry-guard0RAN-PASS
pnpm check:parse-guard0RAN-PASS
pnpm check:pnpm-filter-targets0RAN-PASS
pnpm check:watch-hint-literal0RAN-PASS
pnpm check:ratchet-remedy-authority0RAN-PASS (beyond the derived family)
node scripts/check-self-test-workflow-commands.mjs0RAN-PASS (beyond the derived family)
pnpm check:nul-bytes0RAN-PASS

Lint, narrowed and declared.pnpm lint is eslint . --no-inline-config over the whole repo; the run here is targeted at the one changed file: 1 file linted, 0 errors, 0 warnings, exit 0, the count read from --format json. The narrowing is a measurement rather than a gap because this repo runs one eslint.config.mjs which never enables type-aware linting for any file (no parserOptions.project, no typed rules — stated and positive-control-measured in that config's own header), so a one-file diff cannot move the verdict on any untouched file, and the file is a config match rather than an ignore (it produced a result object, not an ignore warning).

Changeset: none. This is a scripts/**-only diff that publishes nothing from any package, which is the repo convention on recent scripts-only PRs; the skip-changeset label carries it.

Generated by Claude Code


Generated by Claude Code

…iter binds
extractWatchHints resolves a path literal against exactly two anchors: the
repo root, and the writer's own directory when the author spelled a ./ or ../
prefix. A gate that lives inside a package binds a third at module top --
join(dirname(fileURLToPath(import.meta.url)), '..') -- and every literal it
writes against that binding was read from the REPO root, where nothing of the
sort exists. check:generated's five artifact-ledger literals are the confirmed
instance: all five alive under packages/spec/ and all five printed by the
residue as dead leads, under a reason text asserting they were never repo
paths.
Measured first, as the card required. Over the 201 discovered families' 195
scannable gate scripts, 8 bind a package root this way, 2 of them carry a hint
dead at the repo root, 6 such hints exist, 5 re-anchor onto something the tree
has and 1 is refused because it reaches nothing. Price: 142089 -> 142139
watch-hint (gate, file) pairs (+50, 0 lost, 0 re-attributed), one family's hint
set moves, and 5 of 5 admitted hints are true leads verified at their
declaration site.
The admitted class is exactly the one whose residue sentence was false: a hint
is re-anchored only when no tracked path begins with even its first segment.
The layout-moved class is left to triage, admission is untouched, and a
re-anchoring that reaches nothing is dropped, so the rule cannot invent a path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
unreachableReason's by-construction branch ended "never was a repo path" -- a
claim about EVERY base, made from evidence about ONE. check:generated's
api-surface, export-origins and src/meta-spelling/... all printed under it
while sitting on disk under packages/spec/: a dead-lead label on a live path,
telling the reader there is nothing to chase about three files they could
open.
The sentence now names what the sweep actually established (no tracked path
under the literal's first segment) and the two ways that happens, without
asserting the literal never existed anywhere. The three docblocks that stated
the same universal as current fact are corrected with it; the two that quote it
as history keep the quote and say it is retired, so a grep for it still lands
on the incident.
The pins move with the text rather than staying on the retired phrase: pinned
to a string nothing renders, the dist-freshness case would pass on any tree at
all -- a green over the exact regression it exists to catch -- so it now
asserts against the sentence that branch prints today, and the sweep case
asserts both halves.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
The fourth-edge docblock above packageManifestTargets opened "The three
follows above all end at a PROGRAM ... and inherit that program's declared
population". The middle one does not: readProgramTargetsInSource fills
entry.reads / entry.readOrigin and nothing feeds those into hintsOfModule.
The read edge reaches a card only through the LAST key in coveringKey, which
is an identity claim about a single FILE and never a population.
Re-derived at this PR's base: the read edge is 12 families, 16 (family,target)
pairs over 15 distinct targets, and it carries 0 inherited hints -- against 373
for the import edge, 7 for the manifest edge and 1 for the run edge. Reading a
program's TEXT is not performing its work, so identity-only is the correct
behaviour and the sentence was the defect. No behaviour change.
The two numberings inside the file also disagreed: discoverFamilies called the
spawn follow the SECOND edge and the manifest follow the THIRD, while the
docblock called the manifest one the FOURTH. They count different things --
source-order edges, and population follows -- so each now says which, and the
claim that there are three population follows all ending at a program is gone.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 2, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 2, 2026 08:12
@baozhoutao
baozhoutao added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 9e286e2Sep 2, 2026
36 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14208-dispatch-gates-package-root-anchor branch September 2, 2026 08:39
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

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(pm): read a package-root-anchored literal against the root its writer binds, and stop the residue calling it a non-path - #14450

Merged
baozhoutao merged 4 commits into
mainfrom
claude/issue-14208-dispatch-gates-package-root-anchor
Sep 2, 2026
Merged

fix(pm): read a package-root-anchored literal against the root its writer binds, and stop the residue calling it a non-path#14450
baozhoutao merged 4 commits into
mainfrom
claude/issue-14208-dispatch-gates-package-root-anchor

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#14208
Fixes#14289

Two deliverables in one PR on one file, scripts/pm/dispatch-gates.mjs, as three commits: the package-root anchor (#14208), the residue reason text it is coupled to (#14208), and the read-edge prose repair (#14289, no behaviour change).

#14208 — measure first, as the card required

Premise re-confirmed on this PR's base (ad54eb342), with the card's own command:

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --residue packages/spec/src/index.ts

check:generated printed 5 of 10 declared literal(s) reach nothing tracked, the three named literals among them, each under the reason no tracked path under its first segment; never was a repo path — while all three sit on disk under packages/spec/.

The population sweep

Over the 201 discovered families' 195 scannable gate scripts, resolving every import.meta-seeded binding through the tool's own resolvePathExpression:

measurementvalue
discovered gate scripts binding a package root8 (all packages/spec)
...of those, scripts carrying a hint dead at the repo root2
dead-at-root hints in that population6
...that re-anchor onto something the tree HAS5
...refused because the re-anchored form reaches nothing1 ('zod', a package specifier)

⚠️The card's description of the shape does not survive the sweep, and this changed the rule. The card described the class as literals passed to resolve(PKG_DIR, …). That shape is real — 6 such literals across 6 of the 8 scripts, every one a tracked file — but it is not where the confirmed instance comes from. All five of check:generated's dead literals are artifact: values in its GATED ledger (artifact: 'api-surface/', artifact: 'src/meta-spelling/meta-url-data.generated.ts', …), describing what each gate verifies; nothing resolves them. A rule restricted to the syntactic shape would have repaired none of the three dead leads the card names. The restriction was measured and dropped; the reachability test is what keeps the wider reading honest.

The rule that landed

A hint is re-read against the package root its writer binds only when deepestTrackedPrefix is empty — no tracked path begins with even its first segment, which is exactly the branch whose reason text was false — and only if the re-anchored form reaches the tree through hintReachesTree. It replaces rather than adds. Neither priced refusal is relaxed and neither is reachable from here: admission is untouched (a literal that is not already a hint never arrives), and a re-anchoring that names nothing is dropped, so the rule cannot invent a path. The layout-moved class (a shorter surviving prefix) is deliberately left alone — its first segment is a real repo directory, so re-anchoring it would replace a triage lead with a fabricated one.

The price, before and after, on this base

Over 201 discovered families and 7900 tracked files:

beforeafter
watch-hint (gate, file) pairs142089142139 (+50, 0 lost)
families whose hint set moves1 (check:generated, no other)
pairs re-attributed (same pair, new via)0 — every replaced spelling was dead, so it covered nothing to re-attribute
(check, hint) live / inert1108 / 1521113 / 147
distinct hints in the fleet725722 — three of the five re-anchored forms are already spelled from the root by other gates
families carrying dead literals4645
dead literals152147
...in the empty-deepest class128123

Provenance, which is the criterion this file prices and never volume: 5 of 5 admitted hints are TRUE leads, and 50 of 50 pairs with them, each verified at its declaration site — they are check:generated's own artifact ledger and verifying those artifacts is the gate's whole job:

re-anchored hintfilesledger entry
packages/spec/api-surface17check:api-surface
packages/spec/export-origins17check:export-origins
packages/spec/declaration-map14check:declaration-map
packages/spec/src/meta-spelling/meta-url-data.generated.ts1check:meta-url-spelling
packages/spec/liveness/state-counts.md1the liveness counts

0 fabricated. For scale, hintCovers' docblock prices the bare-top-level-word admission it refuses at +139084 pairs on this corpus; this is 0.04% of it, and moduleRelativeDirectoryHint's naive-resolved-form widening was +53 hints for 1 true lead against this rule's 5 for 5.

The whole --residue output moves by exactly one row before and after (diffed line by line).

The reason text

The by-construction sentence ended never was a repo path — a claim about EVERY base made from evidence about ONE, and a dead-lead label printed over three live files. It now reads:

'HINT' — no tracked path under its first segment; a specifier, or a path anchored at a base this scan did not resolve

Checkable by the reader (git ls-files on the first segment is empty) and no longer asserting a base it did not test. The three docblocks stating the same universal as current fact are corrected with it; the two that quote it as history keep the quote and say it is retired, so a grep still lands on the incident. The pins move with the text: pinned to a retired phrase, the dist-freshness case would pass on any tree at all — a green over the exact regression it exists to catch.

Ablation

Anchor call replaced with return [...hints] on the committed tree, mutation proven on disk before the run (anchor occurrences 1 to 0, injected marker 1, blob 9743275c to e51f9d24), restore leg trapped and then proven by byte comparison (git diff HEAD empty, blob back to the HEAD blob 9743275c). Ablated, check:generated returns to 5 of 10 declared literal(s) reach nothing tracked with the three named literals marked. The ablated run also shows the corrected reason text in place, so the two halves are independent — the reason-text repair is the deliverable the card specified for the no-repair branch, and it stands on its own.

#14289 — prose only, no behaviour change

The fourth-edge docblock above packageManifestTargets opened "The three follows above all end at a PROGRAM … and inherit that program's declared population". The middle one does not: readProgramTargetsInSource fills entry.reads / entry.readOrigin and nothing feeds those into hintsOfModule. The read edge reaches a card only through the last key in coveringKey, an identity claim about a single FILE (#13000).

Re-derived at this PR's base, not copied from the card:

EDGE import: 27 families, 38 (family,target) pairs, 15 distinct targets
EDGE run: 1 family, 1 pair, 1 distinct target
EDGE manifest: 10 families, 10 pairs, 2 distinct targets
EDGE read: 12 families, 16 pairs, 15 distinct targets
INHERITED HINTS BY EDGE LABEL:
import: 373 hints across 13 families
manifest: 7 hints across 7 families
run: 1 hint across 1 family
read: 0

Read targets that carried a hint into their family: 0. Of the 16 read-edge pairs, 8 are answered by the READ identity key and 8 by an earlier key — consistent with coveringKey's "additive by construction" note.

The two numberings are reconciled rather than flattened: the docblock numbers all four scans in source order (so the manifest edge is the fourth), discoverFamilies numbers only the population follows (so the same edge is its third), and each now says which. The claim that there are three population follows all ending at a program is gone, and packageManifestTargets' "the other three follows take" is corrected to two.

Gates

Union re-run after the final commit, at git rev-parse --short HEAD = 70c1aedff (merge of origin/maind16df7418). Derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands — 15 families, all named by path, 0 by kind; the change set is exactly the one file. Exit codes captured to a file before any pipe.

gateexitverdict
pnpm check:pm-dispatch-gates (the tool's --self-test)0RAN-PASS — dispatch-gates self-test: 1240 cases pass
node scripts/check-ci-filter-parity.mjs0RAN-PASS
node scripts/check-cross-package-test-inputs.mjs / pnpm check:cross-package-test-inputs0 / 0RAN-PASS (both spellings)
node scripts/check-self-test-wired.mjs0RAN-PASS
node scripts/check-shard-attestation.mjs0RAN-PASS
node scripts/check-test-completeness.mjs3NOT MEASURED — no test log to hand it; the gate says so itself and calls exit 3 distinct from a finding's 1
pnpm check:agent-test-spelling0RAN-PASS
pnpm check:bash32-floor0RAN-PASS
pnpm check:cli-command-ids0RAN-PASS
pnpm check:declared-population-live0RAN-PASS
pnpm check:entry-guard0RAN-PASS
pnpm check:parse-guard0RAN-PASS
pnpm check:pnpm-filter-targets0RAN-PASS
pnpm check:watch-hint-literal0RAN-PASS
pnpm check:ratchet-remedy-authority0RAN-PASS (beyond the derived family)
node scripts/check-self-test-workflow-commands.mjs0RAN-PASS (beyond the derived family)
pnpm check:nul-bytes0RAN-PASS

Lint, narrowed and declared.pnpm lint is eslint . --no-inline-config over the whole repo; the run here is targeted at the one changed file: 1 file linted, 0 errors, 0 warnings, exit 0, the count read from --format json. The narrowing is a measurement rather than a gap because this repo runs one eslint.config.mjs which never enables type-aware linting for any file (no parserOptions.project, no typed rules — stated and positive-control-measured in that config's own header), so a one-file diff cannot move the verdict on any untouched file, and the file is a config match rather than an ignore (it produced a result object, not an ignore warning).

Changeset: none. This is a scripts/**-only diff that publishes nothing from any package, which is the repo convention on recent scripts-only PRs; the skip-changeset label carries it.

Generated by Claude Code


Generated by Claude Code

…iter binds
extractWatchHints resolves a path literal against exactly two anchors: the
repo root, and the writer's own directory when the author spelled a ./ or ../
prefix. A gate that lives inside a package binds a third at module top --
join(dirname(fileURLToPath(import.meta.url)), '..') -- and every literal it
writes against that binding was read from the REPO root, where nothing of the
sort exists. check:generated's five artifact-ledger literals are the confirmed
instance: all five alive under packages/spec/ and all five printed by the
residue as dead leads, under a reason text asserting they were never repo
paths.
Measured first, as the card required. Over the 201 discovered families' 195
scannable gate scripts, 8 bind a package root this way, 2 of them carry a hint
dead at the repo root, 6 such hints exist, 5 re-anchor onto something the tree
has and 1 is refused because it reaches nothing. Price: 142089 -> 142139
watch-hint (gate, file) pairs (+50, 0 lost, 0 re-attributed), one family's hint
set moves, and 5 of 5 admitted hints are true leads verified at their
declaration site.
The admitted class is exactly the one whose residue sentence was false: a hint
is re-anchored only when no tracked path begins with even its first segment.
The layout-moved class is left to triage, admission is untouched, and a
re-anchoring that reaches nothing is dropped, so the rule cannot invent a path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
unreachableReason's by-construction branch ended "never was a repo path" -- a
claim about EVERY base, made from evidence about ONE. check:generated's
api-surface, export-origins and src/meta-spelling/... all printed under it
while sitting on disk under packages/spec/: a dead-lead label on a live path,
telling the reader there is nothing to chase about three files they could
open.
The sentence now names what the sweep actually established (no tracked path
under the literal's first segment) and the two ways that happens, without
asserting the literal never existed anywhere. The three docblocks that stated
the same universal as current fact are corrected with it; the two that quote it
as history keep the quote and say it is retired, so a grep for it still lands
on the incident.
The pins move with the text rather than staying on the retired phrase: pinned
to a string nothing renders, the dist-freshness case would pass on any tree at
all -- a green over the exact regression it exists to catch -- so it now
asserts against the sentence that branch prints today, and the sweep case
asserts both halves.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
The fourth-edge docblock above packageManifestTargets opened "The three
follows above all end at a PROGRAM ... and inherit that program's declared
population". The middle one does not: readProgramTargetsInSource fills
entry.reads / entry.readOrigin and nothing feeds those into hintsOfModule.
The read edge reaches a card only through the LAST key in coveringKey, which
is an identity claim about a single FILE and never a population.
Re-derived at this PR's base: the read edge is 12 families, 16 (family,target)
pairs over 15 distinct targets, and it carries 0 inherited hints -- against 373
for the import edge, 7 for the manifest edge and 1 for the run edge. Reading a
program's TEXT is not performing its work, so identity-only is the correct
behaviour and the sentence was the defect. No behaviour change.
The two numberings inside the file also disagreed: discoverFamilies called the
spawn follow the SECOND edge and the manifest follow the THIRD, while the
docblock called the manifest one the FOURTH. They count different things --
source-order edges, and population follows -- so each now says which, and the
claim that there are three population follows all ending at a program is gone.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 2, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 2, 2026 08:12
@baozhoutao
baozhoutao added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 9e286e2Sep 2, 2026
36 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14208-dispatch-gates-package-root-anchor branch September 2, 2026 08:39
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

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(pm): read a package-root-anchored literal against the root its writer binds, and stop the residue calling it a non-path - #14450

Merged
baozhoutao merged 4 commits into
mainfrom
claude/issue-14208-dispatch-gates-package-root-anchor
Sep 2, 2026
Merged

fix(pm): read a package-root-anchored literal against the root its writer binds, and stop the residue calling it a non-path#14450
baozhoutao merged 4 commits into
mainfrom
claude/issue-14208-dispatch-gates-package-root-anchor

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#14208
Fixes#14289

Two deliverables in one PR on one file, scripts/pm/dispatch-gates.mjs, as three commits: the package-root anchor (#14208), the residue reason text it is coupled to (#14208), and the read-edge prose repair (#14289, no behaviour change).

#14208 — measure first, as the card required

Premise re-confirmed on this PR's base (ad54eb342), with the card's own command:

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --residue packages/spec/src/index.ts

check:generated printed 5 of 10 declared literal(s) reach nothing tracked, the three named literals among them, each under the reason no tracked path under its first segment; never was a repo path — while all three sit on disk under packages/spec/.

The population sweep

Over the 201 discovered families' 195 scannable gate scripts, resolving every import.meta-seeded binding through the tool's own resolvePathExpression:

measurementvalue
discovered gate scripts binding a package root8 (all packages/spec)
...of those, scripts carrying a hint dead at the repo root2
dead-at-root hints in that population6
...that re-anchor onto something the tree HAS5
...refused because the re-anchored form reaches nothing1 ('zod', a package specifier)

⚠️The card's description of the shape does not survive the sweep, and this changed the rule. The card described the class as literals passed to resolve(PKG_DIR, …). That shape is real — 6 such literals across 6 of the 8 scripts, every one a tracked file — but it is not where the confirmed instance comes from. All five of check:generated's dead literals are artifact: values in its GATED ledger (artifact: 'api-surface/', artifact: 'src/meta-spelling/meta-url-data.generated.ts', …), describing what each gate verifies; nothing resolves them. A rule restricted to the syntactic shape would have repaired none of the three dead leads the card names. The restriction was measured and dropped; the reachability test is what keeps the wider reading honest.

The rule that landed

A hint is re-read against the package root its writer binds only when deepestTrackedPrefix is empty — no tracked path begins with even its first segment, which is exactly the branch whose reason text was false — and only if the re-anchored form reaches the tree through hintReachesTree. It replaces rather than adds. Neither priced refusal is relaxed and neither is reachable from here: admission is untouched (a literal that is not already a hint never arrives), and a re-anchoring that names nothing is dropped, so the rule cannot invent a path. The layout-moved class (a shorter surviving prefix) is deliberately left alone — its first segment is a real repo directory, so re-anchoring it would replace a triage lead with a fabricated one.

The price, before and after, on this base

Over 201 discovered families and 7900 tracked files:

beforeafter
watch-hint (gate, file) pairs142089142139 (+50, 0 lost)
families whose hint set moves1 (check:generated, no other)
pairs re-attributed (same pair, new via)0 — every replaced spelling was dead, so it covered nothing to re-attribute
(check, hint) live / inert1108 / 1521113 / 147
distinct hints in the fleet725722 — three of the five re-anchored forms are already spelled from the root by other gates
families carrying dead literals4645
dead literals152147
...in the empty-deepest class128123

Provenance, which is the criterion this file prices and never volume: 5 of 5 admitted hints are TRUE leads, and 50 of 50 pairs with them, each verified at its declaration site — they are check:generated's own artifact ledger and verifying those artifacts is the gate's whole job:

re-anchored hintfilesledger entry
packages/spec/api-surface17check:api-surface
packages/spec/export-origins17check:export-origins
packages/spec/declaration-map14check:declaration-map
packages/spec/src/meta-spelling/meta-url-data.generated.ts1check:meta-url-spelling
packages/spec/liveness/state-counts.md1the liveness counts

0 fabricated. For scale, hintCovers' docblock prices the bare-top-level-word admission it refuses at +139084 pairs on this corpus; this is 0.04% of it, and moduleRelativeDirectoryHint's naive-resolved-form widening was +53 hints for 1 true lead against this rule's 5 for 5.

The whole --residue output moves by exactly one row before and after (diffed line by line).

The reason text

The by-construction sentence ended never was a repo path — a claim about EVERY base made from evidence about ONE, and a dead-lead label printed over three live files. It now reads:

'HINT' — no tracked path under its first segment; a specifier, or a path anchored at a base this scan did not resolve

Checkable by the reader (git ls-files on the first segment is empty) and no longer asserting a base it did not test. The three docblocks stating the same universal as current fact are corrected with it; the two that quote it as history keep the quote and say it is retired, so a grep still lands on the incident. The pins move with the text: pinned to a retired phrase, the dist-freshness case would pass on any tree at all — a green over the exact regression it exists to catch.

Ablation

Anchor call replaced with return [...hints] on the committed tree, mutation proven on disk before the run (anchor occurrences 1 to 0, injected marker 1, blob 9743275c to e51f9d24), restore leg trapped and then proven by byte comparison (git diff HEAD empty, blob back to the HEAD blob 9743275c). Ablated, check:generated returns to 5 of 10 declared literal(s) reach nothing tracked with the three named literals marked. The ablated run also shows the corrected reason text in place, so the two halves are independent — the reason-text repair is the deliverable the card specified for the no-repair branch, and it stands on its own.

#14289 — prose only, no behaviour change

The fourth-edge docblock above packageManifestTargets opened "The three follows above all end at a PROGRAM … and inherit that program's declared population". The middle one does not: readProgramTargetsInSource fills entry.reads / entry.readOrigin and nothing feeds those into hintsOfModule. The read edge reaches a card only through the last key in coveringKey, an identity claim about a single FILE (#13000).

Re-derived at this PR's base, not copied from the card:

EDGE import: 27 families, 38 (family,target) pairs, 15 distinct targets
EDGE run: 1 family, 1 pair, 1 distinct target
EDGE manifest: 10 families, 10 pairs, 2 distinct targets
EDGE read: 12 families, 16 pairs, 15 distinct targets
INHERITED HINTS BY EDGE LABEL:
import: 373 hints across 13 families
manifest: 7 hints across 7 families
run: 1 hint across 1 family
read: 0

Read targets that carried a hint into their family: 0. Of the 16 read-edge pairs, 8 are answered by the READ identity key and 8 by an earlier key — consistent with coveringKey's "additive by construction" note.

The two numberings are reconciled rather than flattened: the docblock numbers all four scans in source order (so the manifest edge is the fourth), discoverFamilies numbers only the population follows (so the same edge is its third), and each now says which. The claim that there are three population follows all ending at a program is gone, and packageManifestTargets' "the other three follows take" is corrected to two.

Gates

Union re-run after the final commit, at git rev-parse --short HEAD = 70c1aedff (merge of origin/maind16df7418). Derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands — 15 families, all named by path, 0 by kind; the change set is exactly the one file. Exit codes captured to a file before any pipe.

gateexitverdict
pnpm check:pm-dispatch-gates (the tool's --self-test)0RAN-PASS — dispatch-gates self-test: 1240 cases pass
node scripts/check-ci-filter-parity.mjs0RAN-PASS
node scripts/check-cross-package-test-inputs.mjs / pnpm check:cross-package-test-inputs0 / 0RAN-PASS (both spellings)
node scripts/check-self-test-wired.mjs0RAN-PASS
node scripts/check-shard-attestation.mjs0RAN-PASS
node scripts/check-test-completeness.mjs3NOT MEASURED — no test log to hand it; the gate says so itself and calls exit 3 distinct from a finding's 1
pnpm check:agent-test-spelling0RAN-PASS
pnpm check:bash32-floor0RAN-PASS
pnpm check:cli-command-ids0RAN-PASS
pnpm check:declared-population-live0RAN-PASS
pnpm check:entry-guard0RAN-PASS
pnpm check:parse-guard0RAN-PASS
pnpm check:pnpm-filter-targets0RAN-PASS
pnpm check:watch-hint-literal0RAN-PASS
pnpm check:ratchet-remedy-authority0RAN-PASS (beyond the derived family)
node scripts/check-self-test-workflow-commands.mjs0RAN-PASS (beyond the derived family)
pnpm check:nul-bytes0RAN-PASS

Lint, narrowed and declared.pnpm lint is eslint . --no-inline-config over the whole repo; the run here is targeted at the one changed file: 1 file linted, 0 errors, 0 warnings, exit 0, the count read from --format json. The narrowing is a measurement rather than a gap because this repo runs one eslint.config.mjs which never enables type-aware linting for any file (no parserOptions.project, no typed rules — stated and positive-control-measured in that config's own header), so a one-file diff cannot move the verdict on any untouched file, and the file is a config match rather than an ignore (it produced a result object, not an ignore warning).

Changeset: none. This is a scripts/**-only diff that publishes nothing from any package, which is the repo convention on recent scripts-only PRs; the skip-changeset label carries it.

Generated by Claude Code


Generated by Claude Code

…iter binds
extractWatchHints resolves a path literal against exactly two anchors: the
repo root, and the writer's own directory when the author spelled a ./ or ../
prefix. A gate that lives inside a package binds a third at module top --
join(dirname(fileURLToPath(import.meta.url)), '..') -- and every literal it
writes against that binding was read from the REPO root, where nothing of the
sort exists. check:generated's five artifact-ledger literals are the confirmed
instance: all five alive under packages/spec/ and all five printed by the
residue as dead leads, under a reason text asserting they were never repo
paths.
Measured first, as the card required. Over the 201 discovered families' 195
scannable gate scripts, 8 bind a package root this way, 2 of them carry a hint
dead at the repo root, 6 such hints exist, 5 re-anchor onto something the tree
has and 1 is refused because it reaches nothing. Price: 142089 -> 142139
watch-hint (gate, file) pairs (+50, 0 lost, 0 re-attributed), one family's hint
set moves, and 5 of 5 admitted hints are true leads verified at their
declaration site.
The admitted class is exactly the one whose residue sentence was false: a hint
is re-anchored only when no tracked path begins with even its first segment.
The layout-moved class is left to triage, admission is untouched, and a
re-anchoring that reaches nothing is dropped, so the rule cannot invent a path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
unreachableReason's by-construction branch ended "never was a repo path" -- a
claim about EVERY base, made from evidence about ONE. check:generated's
api-surface, export-origins and src/meta-spelling/... all printed under it
while sitting on disk under packages/spec/: a dead-lead label on a live path,
telling the reader there is nothing to chase about three files they could
open.
The sentence now names what the sweep actually established (no tracked path
under the literal's first segment) and the two ways that happens, without
asserting the literal never existed anywhere. The three docblocks that stated
the same universal as current fact are corrected with it; the two that quote it
as history keep the quote and say it is retired, so a grep for it still lands
on the incident.
The pins move with the text rather than staying on the retired phrase: pinned
to a string nothing renders, the dist-freshness case would pass on any tree at
all -- a green over the exact regression it exists to catch -- so it now
asserts against the sentence that branch prints today, and the sweep case
asserts both halves.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
The fourth-edge docblock above packageManifestTargets opened "The three
follows above all end at a PROGRAM ... and inherit that program's declared
population". The middle one does not: readProgramTargetsInSource fills
entry.reads / entry.readOrigin and nothing feeds those into hintsOfModule.
The read edge reaches a card only through the LAST key in coveringKey, which
is an identity claim about a single FILE and never a population.
Re-derived at this PR's base: the read edge is 12 families, 16 (family,target)
pairs over 15 distinct targets, and it carries 0 inherited hints -- against 373
for the import edge, 7 for the manifest edge and 1 for the run edge. Reading a
program's TEXT is not performing its work, so identity-only is the correct
behaviour and the sentence was the defect. No behaviour change.
The two numberings inside the file also disagreed: discoverFamilies called the
spawn follow the SECOND edge and the manifest follow the THIRD, while the
docblock called the manifest one the FOURTH. They count different things --
source-order edges, and population follows -- so each now says which, and the
claim that there are three population follows all ending at a program is gone.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 2, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 2, 2026 08:12
@baozhoutao
baozhoutao added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 9e286e2Sep 2, 2026
36 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14208-dispatch-gates-package-root-anchor branch September 2, 2026 08:39
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

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(pm): read a package-root-anchored literal against the root its writer binds, and stop the residue calling it a non-path - #14450

Merged
baozhoutao merged 4 commits into
mainfrom
claude/issue-14208-dispatch-gates-package-root-anchor
Sep 2, 2026
Merged

fix(pm): read a package-root-anchored literal against the root its writer binds, and stop the residue calling it a non-path#14450
baozhoutao merged 4 commits into
mainfrom
claude/issue-14208-dispatch-gates-package-root-anchor

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#14208
Fixes#14289

Two deliverables in one PR on one file, scripts/pm/dispatch-gates.mjs, as three commits: the package-root anchor (#14208), the residue reason text it is coupled to (#14208), and the read-edge prose repair (#14289, no behaviour change).

#14208 — measure first, as the card required

Premise re-confirmed on this PR's base (ad54eb342), with the card's own command:

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --residue packages/spec/src/index.ts

check:generated printed 5 of 10 declared literal(s) reach nothing tracked, the three named literals among them, each under the reason no tracked path under its first segment; never was a repo path — while all three sit on disk under packages/spec/.

The population sweep

Over the 201 discovered families' 195 scannable gate scripts, resolving every import.meta-seeded binding through the tool's own resolvePathExpression:

measurementvalue
discovered gate scripts binding a package root8 (all packages/spec)
...of those, scripts carrying a hint dead at the repo root2
dead-at-root hints in that population6
...that re-anchor onto something the tree HAS5
...refused because the re-anchored form reaches nothing1 ('zod', a package specifier)

⚠️The card's description of the shape does not survive the sweep, and this changed the rule. The card described the class as literals passed to resolve(PKG_DIR, …). That shape is real — 6 such literals across 6 of the 8 scripts, every one a tracked file — but it is not where the confirmed instance comes from. All five of check:generated's dead literals are artifact: values in its GATED ledger (artifact: 'api-surface/', artifact: 'src/meta-spelling/meta-url-data.generated.ts', …), describing what each gate verifies; nothing resolves them. A rule restricted to the syntactic shape would have repaired none of the three dead leads the card names. The restriction was measured and dropped; the reachability test is what keeps the wider reading honest.

The rule that landed

A hint is re-read against the package root its writer binds only when deepestTrackedPrefix is empty — no tracked path begins with even its first segment, which is exactly the branch whose reason text was false — and only if the re-anchored form reaches the tree through hintReachesTree. It replaces rather than adds. Neither priced refusal is relaxed and neither is reachable from here: admission is untouched (a literal that is not already a hint never arrives), and a re-anchoring that names nothing is dropped, so the rule cannot invent a path. The layout-moved class (a shorter surviving prefix) is deliberately left alone — its first segment is a real repo directory, so re-anchoring it would replace a triage lead with a fabricated one.

The price, before and after, on this base

Over 201 discovered families and 7900 tracked files:

beforeafter
watch-hint (gate, file) pairs142089142139 (+50, 0 lost)
families whose hint set moves1 (check:generated, no other)
pairs re-attributed (same pair, new via)0 — every replaced spelling was dead, so it covered nothing to re-attribute
(check, hint) live / inert1108 / 1521113 / 147
distinct hints in the fleet725722 — three of the five re-anchored forms are already spelled from the root by other gates
families carrying dead literals4645
dead literals152147
...in the empty-deepest class128123

Provenance, which is the criterion this file prices and never volume: 5 of 5 admitted hints are TRUE leads, and 50 of 50 pairs with them, each verified at its declaration site — they are check:generated's own artifact ledger and verifying those artifacts is the gate's whole job:

re-anchored hintfilesledger entry
packages/spec/api-surface17check:api-surface
packages/spec/export-origins17check:export-origins
packages/spec/declaration-map14check:declaration-map
packages/spec/src/meta-spelling/meta-url-data.generated.ts1check:meta-url-spelling
packages/spec/liveness/state-counts.md1the liveness counts

0 fabricated. For scale, hintCovers' docblock prices the bare-top-level-word admission it refuses at +139084 pairs on this corpus; this is 0.04% of it, and moduleRelativeDirectoryHint's naive-resolved-form widening was +53 hints for 1 true lead against this rule's 5 for 5.

The whole --residue output moves by exactly one row before and after (diffed line by line).

The reason text

The by-construction sentence ended never was a repo path — a claim about EVERY base made from evidence about ONE, and a dead-lead label printed over three live files. It now reads:

'HINT' — no tracked path under its first segment; a specifier, or a path anchored at a base this scan did not resolve

Checkable by the reader (git ls-files on the first segment is empty) and no longer asserting a base it did not test. The three docblocks stating the same universal as current fact are corrected with it; the two that quote it as history keep the quote and say it is retired, so a grep still lands on the incident. The pins move with the text: pinned to a retired phrase, the dist-freshness case would pass on any tree at all — a green over the exact regression it exists to catch.

Ablation

Anchor call replaced with return [...hints] on the committed tree, mutation proven on disk before the run (anchor occurrences 1 to 0, injected marker 1, blob 9743275c to e51f9d24), restore leg trapped and then proven by byte comparison (git diff HEAD empty, blob back to the HEAD blob 9743275c). Ablated, check:generated returns to 5 of 10 declared literal(s) reach nothing tracked with the three named literals marked. The ablated run also shows the corrected reason text in place, so the two halves are independent — the reason-text repair is the deliverable the card specified for the no-repair branch, and it stands on its own.

#14289 — prose only, no behaviour change

The fourth-edge docblock above packageManifestTargets opened "The three follows above all end at a PROGRAM … and inherit that program's declared population". The middle one does not: readProgramTargetsInSource fills entry.reads / entry.readOrigin and nothing feeds those into hintsOfModule. The read edge reaches a card only through the last key in coveringKey, an identity claim about a single FILE (#13000).

Re-derived at this PR's base, not copied from the card:

EDGE import: 27 families, 38 (family,target) pairs, 15 distinct targets
EDGE run: 1 family, 1 pair, 1 distinct target
EDGE manifest: 10 families, 10 pairs, 2 distinct targets
EDGE read: 12 families, 16 pairs, 15 distinct targets
INHERITED HINTS BY EDGE LABEL:
import: 373 hints across 13 families
manifest: 7 hints across 7 families
run: 1 hint across 1 family
read: 0

Read targets that carried a hint into their family: 0. Of the 16 read-edge pairs, 8 are answered by the READ identity key and 8 by an earlier key — consistent with coveringKey's "additive by construction" note.

The two numberings are reconciled rather than flattened: the docblock numbers all four scans in source order (so the manifest edge is the fourth), discoverFamilies numbers only the population follows (so the same edge is its third), and each now says which. The claim that there are three population follows all ending at a program is gone, and packageManifestTargets' "the other three follows take" is corrected to two.

Gates

Union re-run after the final commit, at git rev-parse --short HEAD = 70c1aedff (merge of origin/maind16df7418). Derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands — 15 families, all named by path, 0 by kind; the change set is exactly the one file. Exit codes captured to a file before any pipe.

gateexitverdict
pnpm check:pm-dispatch-gates (the tool's --self-test)0RAN-PASS — dispatch-gates self-test: 1240 cases pass
node scripts/check-ci-filter-parity.mjs0RAN-PASS
node scripts/check-cross-package-test-inputs.mjs / pnpm check:cross-package-test-inputs0 / 0RAN-PASS (both spellings)
node scripts/check-self-test-wired.mjs0RAN-PASS
node scripts/check-shard-attestation.mjs0RAN-PASS
node scripts/check-test-completeness.mjs3NOT MEASURED — no test log to hand it; the gate says so itself and calls exit 3 distinct from a finding's 1
pnpm check:agent-test-spelling0RAN-PASS
pnpm check:bash32-floor0RAN-PASS
pnpm check:cli-command-ids0RAN-PASS
pnpm check:declared-population-live0RAN-PASS
pnpm check:entry-guard0RAN-PASS
pnpm check:parse-guard0RAN-PASS
pnpm check:pnpm-filter-targets0RAN-PASS
pnpm check:watch-hint-literal0RAN-PASS
pnpm check:ratchet-remedy-authority0RAN-PASS (beyond the derived family)
node scripts/check-self-test-workflow-commands.mjs0RAN-PASS (beyond the derived family)
pnpm check:nul-bytes0RAN-PASS

Lint, narrowed and declared.pnpm lint is eslint . --no-inline-config over the whole repo; the run here is targeted at the one changed file: 1 file linted, 0 errors, 0 warnings, exit 0, the count read from --format json. The narrowing is a measurement rather than a gap because this repo runs one eslint.config.mjs which never enables type-aware linting for any file (no parserOptions.project, no typed rules — stated and positive-control-measured in that config's own header), so a one-file diff cannot move the verdict on any untouched file, and the file is a config match rather than an ignore (it produced a result object, not an ignore warning).

Changeset: none. This is a scripts/**-only diff that publishes nothing from any package, which is the repo convention on recent scripts-only PRs; the skip-changeset label carries it.

Generated by Claude Code


Generated by Claude Code

…iter binds
extractWatchHints resolves a path literal against exactly two anchors: the
repo root, and the writer's own directory when the author spelled a ./ or ../
prefix. A gate that lives inside a package binds a third at module top --
join(dirname(fileURLToPath(import.meta.url)), '..') -- and every literal it
writes against that binding was read from the REPO root, where nothing of the
sort exists. check:generated's five artifact-ledger literals are the confirmed
instance: all five alive under packages/spec/ and all five printed by the
residue as dead leads, under a reason text asserting they were never repo
paths.
Measured first, as the card required. Over the 201 discovered families' 195
scannable gate scripts, 8 bind a package root this way, 2 of them carry a hint
dead at the repo root, 6 such hints exist, 5 re-anchor onto something the tree
has and 1 is refused because it reaches nothing. Price: 142089 -> 142139
watch-hint (gate, file) pairs (+50, 0 lost, 0 re-attributed), one family's hint
set moves, and 5 of 5 admitted hints are true leads verified at their
declaration site.
The admitted class is exactly the one whose residue sentence was false: a hint
is re-anchored only when no tracked path begins with even its first segment.
The layout-moved class is left to triage, admission is untouched, and a
re-anchoring that reaches nothing is dropped, so the rule cannot invent a path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
unreachableReason's by-construction branch ended "never was a repo path" -- a
claim about EVERY base, made from evidence about ONE. check:generated's
api-surface, export-origins and src/meta-spelling/... all printed under it
while sitting on disk under packages/spec/: a dead-lead label on a live path,
telling the reader there is nothing to chase about three files they could
open.
The sentence now names what the sweep actually established (no tracked path
under the literal's first segment) and the two ways that happens, without
asserting the literal never existed anywhere. The three docblocks that stated
the same universal as current fact are corrected with it; the two that quote it
as history keep the quote and say it is retired, so a grep for it still lands
on the incident.
The pins move with the text rather than staying on the retired phrase: pinned
to a string nothing renders, the dist-freshness case would pass on any tree at
all -- a green over the exact regression it exists to catch -- so it now
asserts against the sentence that branch prints today, and the sweep case
asserts both halves.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
The fourth-edge docblock above packageManifestTargets opened "The three
follows above all end at a PROGRAM ... and inherit that program's declared
population". The middle one does not: readProgramTargetsInSource fills
entry.reads / entry.readOrigin and nothing feeds those into hintsOfModule.
The read edge reaches a card only through the LAST key in coveringKey, which
is an identity claim about a single FILE and never a population.
Re-derived at this PR's base: the read edge is 12 families, 16 (family,target)
pairs over 15 distinct targets, and it carries 0 inherited hints -- against 373
for the import edge, 7 for the manifest edge and 1 for the run edge. Reading a
program's TEXT is not performing its work, so identity-only is the correct
behaviour and the sentence was the defect. No behaviour change.
The two numberings inside the file also disagreed: discoverFamilies called the
spawn follow the SECOND edge and the manifest follow the THIRD, while the
docblock called the manifest one the FOURTH. They count different things --
source-order edges, and population follows -- so each now says which, and the
claim that there are three population follows all ending at a program is gone.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 2, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 2, 2026 08:12
@baozhoutao
baozhoutao added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 9e286e2Sep 2, 2026
36 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14208-dispatch-gates-package-root-anchor branch September 2, 2026 08:39
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

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(pm): read a package-root-anchored literal against the root its writer binds, and stop the residue calling it a non-path - #14450

Merged
baozhoutao merged 4 commits into
mainfrom
claude/issue-14208-dispatch-gates-package-root-anchor
Sep 2, 2026
Merged

fix(pm): read a package-root-anchored literal against the root its writer binds, and stop the residue calling it a non-path#14450
baozhoutao merged 4 commits into
mainfrom
claude/issue-14208-dispatch-gates-package-root-anchor

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#14208
Fixes#14289

Two deliverables in one PR on one file, scripts/pm/dispatch-gates.mjs, as three commits: the package-root anchor (#14208), the residue reason text it is coupled to (#14208), and the read-edge prose repair (#14289, no behaviour change).

#14208 — measure first, as the card required

Premise re-confirmed on this PR's base (ad54eb342), with the card's own command:

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --residue packages/spec/src/index.ts

check:generated printed 5 of 10 declared literal(s) reach nothing tracked, the three named literals among them, each under the reason no tracked path under its first segment; never was a repo path — while all three sit on disk under packages/spec/.

The population sweep

Over the 201 discovered families' 195 scannable gate scripts, resolving every import.meta-seeded binding through the tool's own resolvePathExpression:

measurementvalue
discovered gate scripts binding a package root8 (all packages/spec)
...of those, scripts carrying a hint dead at the repo root2
dead-at-root hints in that population6
...that re-anchor onto something the tree HAS5
...refused because the re-anchored form reaches nothing1 ('zod', a package specifier)

⚠️The card's description of the shape does not survive the sweep, and this changed the rule. The card described the class as literals passed to resolve(PKG_DIR, …). That shape is real — 6 such literals across 6 of the 8 scripts, every one a tracked file — but it is not where the confirmed instance comes from. All five of check:generated's dead literals are artifact: values in its GATED ledger (artifact: 'api-surface/', artifact: 'src/meta-spelling/meta-url-data.generated.ts', …), describing what each gate verifies; nothing resolves them. A rule restricted to the syntactic shape would have repaired none of the three dead leads the card names. The restriction was measured and dropped; the reachability test is what keeps the wider reading honest.

The rule that landed

A hint is re-read against the package root its writer binds only when deepestTrackedPrefix is empty — no tracked path begins with even its first segment, which is exactly the branch whose reason text was false — and only if the re-anchored form reaches the tree through hintReachesTree. It replaces rather than adds. Neither priced refusal is relaxed and neither is reachable from here: admission is untouched (a literal that is not already a hint never arrives), and a re-anchoring that names nothing is dropped, so the rule cannot invent a path. The layout-moved class (a shorter surviving prefix) is deliberately left alone — its first segment is a real repo directory, so re-anchoring it would replace a triage lead with a fabricated one.

The price, before and after, on this base

Over 201 discovered families and 7900 tracked files:

beforeafter
watch-hint (gate, file) pairs142089142139 (+50, 0 lost)
families whose hint set moves1 (check:generated, no other)
pairs re-attributed (same pair, new via)0 — every replaced spelling was dead, so it covered nothing to re-attribute
(check, hint) live / inert1108 / 1521113 / 147
distinct hints in the fleet725722 — three of the five re-anchored forms are already spelled from the root by other gates
families carrying dead literals4645
dead literals152147
...in the empty-deepest class128123

Provenance, which is the criterion this file prices and never volume: 5 of 5 admitted hints are TRUE leads, and 50 of 50 pairs with them, each verified at its declaration site — they are check:generated's own artifact ledger and verifying those artifacts is the gate's whole job:

re-anchored hintfilesledger entry
packages/spec/api-surface17check:api-surface
packages/spec/export-origins17check:export-origins
packages/spec/declaration-map14check:declaration-map
packages/spec/src/meta-spelling/meta-url-data.generated.ts1check:meta-url-spelling
packages/spec/liveness/state-counts.md1the liveness counts

0 fabricated. For scale, hintCovers' docblock prices the bare-top-level-word admission it refuses at +139084 pairs on this corpus; this is 0.04% of it, and moduleRelativeDirectoryHint's naive-resolved-form widening was +53 hints for 1 true lead against this rule's 5 for 5.

The whole --residue output moves by exactly one row before and after (diffed line by line).

The reason text

The by-construction sentence ended never was a repo path — a claim about EVERY base made from evidence about ONE, and a dead-lead label printed over three live files. It now reads:

'HINT' — no tracked path under its first segment; a specifier, or a path anchored at a base this scan did not resolve

Checkable by the reader (git ls-files on the first segment is empty) and no longer asserting a base it did not test. The three docblocks stating the same universal as current fact are corrected with it; the two that quote it as history keep the quote and say it is retired, so a grep still lands on the incident. The pins move with the text: pinned to a retired phrase, the dist-freshness case would pass on any tree at all — a green over the exact regression it exists to catch.

Ablation

Anchor call replaced with return [...hints] on the committed tree, mutation proven on disk before the run (anchor occurrences 1 to 0, injected marker 1, blob 9743275c to e51f9d24), restore leg trapped and then proven by byte comparison (git diff HEAD empty, blob back to the HEAD blob 9743275c). Ablated, check:generated returns to 5 of 10 declared literal(s) reach nothing tracked with the three named literals marked. The ablated run also shows the corrected reason text in place, so the two halves are independent — the reason-text repair is the deliverable the card specified for the no-repair branch, and it stands on its own.

#14289 — prose only, no behaviour change

The fourth-edge docblock above packageManifestTargets opened "The three follows above all end at a PROGRAM … and inherit that program's declared population". The middle one does not: readProgramTargetsInSource fills entry.reads / entry.readOrigin and nothing feeds those into hintsOfModule. The read edge reaches a card only through the last key in coveringKey, an identity claim about a single FILE (#13000).

Re-derived at this PR's base, not copied from the card:

EDGE import: 27 families, 38 (family,target) pairs, 15 distinct targets
EDGE run: 1 family, 1 pair, 1 distinct target
EDGE manifest: 10 families, 10 pairs, 2 distinct targets
EDGE read: 12 families, 16 pairs, 15 distinct targets
INHERITED HINTS BY EDGE LABEL:
import: 373 hints across 13 families
manifest: 7 hints across 7 families
run: 1 hint across 1 family
read: 0

Read targets that carried a hint into their family: 0. Of the 16 read-edge pairs, 8 are answered by the READ identity key and 8 by an earlier key — consistent with coveringKey's "additive by construction" note.

The two numberings are reconciled rather than flattened: the docblock numbers all four scans in source order (so the manifest edge is the fourth), discoverFamilies numbers only the population follows (so the same edge is its third), and each now says which. The claim that there are three population follows all ending at a program is gone, and packageManifestTargets' "the other three follows take" is corrected to two.

Gates

Union re-run after the final commit, at git rev-parse --short HEAD = 70c1aedff (merge of origin/maind16df7418). Derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands — 15 families, all named by path, 0 by kind; the change set is exactly the one file. Exit codes captured to a file before any pipe.

gateexitverdict
pnpm check:pm-dispatch-gates (the tool's --self-test)0RAN-PASS — dispatch-gates self-test: 1240 cases pass
node scripts/check-ci-filter-parity.mjs0RAN-PASS
node scripts/check-cross-package-test-inputs.mjs / pnpm check:cross-package-test-inputs0 / 0RAN-PASS (both spellings)
node scripts/check-self-test-wired.mjs0RAN-PASS
node scripts/check-shard-attestation.mjs0RAN-PASS
node scripts/check-test-completeness.mjs3NOT MEASURED — no test log to hand it; the gate says so itself and calls exit 3 distinct from a finding's 1
pnpm check:agent-test-spelling0RAN-PASS
pnpm check:bash32-floor0RAN-PASS
pnpm check:cli-command-ids0RAN-PASS
pnpm check:declared-population-live0RAN-PASS
pnpm check:entry-guard0RAN-PASS
pnpm check:parse-guard0RAN-PASS
pnpm check:pnpm-filter-targets0RAN-PASS
pnpm check:watch-hint-literal0RAN-PASS
pnpm check:ratchet-remedy-authority0RAN-PASS (beyond the derived family)
node scripts/check-self-test-workflow-commands.mjs0RAN-PASS (beyond the derived family)
pnpm check:nul-bytes0RAN-PASS

Lint, narrowed and declared.pnpm lint is eslint . --no-inline-config over the whole repo; the run here is targeted at the one changed file: 1 file linted, 0 errors, 0 warnings, exit 0, the count read from --format json. The narrowing is a measurement rather than a gap because this repo runs one eslint.config.mjs which never enables type-aware linting for any file (no parserOptions.project, no typed rules — stated and positive-control-measured in that config's own header), so a one-file diff cannot move the verdict on any untouched file, and the file is a config match rather than an ignore (it produced a result object, not an ignore warning).

Changeset: none. This is a scripts/**-only diff that publishes nothing from any package, which is the repo convention on recent scripts-only PRs; the skip-changeset label carries it.

Generated by Claude Code


Generated by Claude Code

…iter binds
extractWatchHints resolves a path literal against exactly two anchors: the
repo root, and the writer's own directory when the author spelled a ./ or ../
prefix. A gate that lives inside a package binds a third at module top --
join(dirname(fileURLToPath(import.meta.url)), '..') -- and every literal it
writes against that binding was read from the REPO root, where nothing of the
sort exists. check:generated's five artifact-ledger literals are the confirmed
instance: all five alive under packages/spec/ and all five printed by the
residue as dead leads, under a reason text asserting they were never repo
paths.
Measured first, as the card required. Over the 201 discovered families' 195
scannable gate scripts, 8 bind a package root this way, 2 of them carry a hint
dead at the repo root, 6 such hints exist, 5 re-anchor onto something the tree
has and 1 is refused because it reaches nothing. Price: 142089 -> 142139
watch-hint (gate, file) pairs (+50, 0 lost, 0 re-attributed), one family's hint
set moves, and 5 of 5 admitted hints are true leads verified at their
declaration site.
The admitted class is exactly the one whose residue sentence was false: a hint
is re-anchored only when no tracked path begins with even its first segment.
The layout-moved class is left to triage, admission is untouched, and a
re-anchoring that reaches nothing is dropped, so the rule cannot invent a path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
unreachableReason's by-construction branch ended "never was a repo path" -- a
claim about EVERY base, made from evidence about ONE. check:generated's
api-surface, export-origins and src/meta-spelling/... all printed under it
while sitting on disk under packages/spec/: a dead-lead label on a live path,
telling the reader there is nothing to chase about three files they could
open.
The sentence now names what the sweep actually established (no tracked path
under the literal's first segment) and the two ways that happens, without
asserting the literal never existed anywhere. The three docblocks that stated
the same universal as current fact are corrected with it; the two that quote it
as history keep the quote and say it is retired, so a grep for it still lands
on the incident.
The pins move with the text rather than staying on the retired phrase: pinned
to a string nothing renders, the dist-freshness case would pass on any tree at
all -- a green over the exact regression it exists to catch -- so it now
asserts against the sentence that branch prints today, and the sweep case
asserts both halves.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
The fourth-edge docblock above packageManifestTargets opened "The three
follows above all end at a PROGRAM ... and inherit that program's declared
population". The middle one does not: readProgramTargetsInSource fills
entry.reads / entry.readOrigin and nothing feeds those into hintsOfModule.
The read edge reaches a card only through the LAST key in coveringKey, which
is an identity claim about a single FILE and never a population.
Re-derived at this PR's base: the read edge is 12 families, 16 (family,target)
pairs over 15 distinct targets, and it carries 0 inherited hints -- against 373
for the import edge, 7 for the manifest edge and 1 for the run edge. Reading a
program's TEXT is not performing its work, so identity-only is the correct
behaviour and the sentence was the defect. No behaviour change.
The two numberings inside the file also disagreed: discoverFamilies called the
spawn follow the SECOND edge and the manifest follow the THIRD, while the
docblock called the manifest one the FOURTH. They count different things --
source-order edges, and population follows -- so each now says which, and the
claim that there are three population follows all ending at a program is gone.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 2, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 2, 2026 08:12
@baozhoutao
baozhoutao added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 9e286e2Sep 2, 2026
36 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14208-dispatch-gates-package-root-anchor branch September 2, 2026 08:39
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

@baozhoutao@claude