Uh oh!
There was an error while loading. Please reload this page.
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 intoSep 2, 2026
Conversation
…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
…spatch-gates-package-root-anchor
This was referenced Sep 2, 2026
baozhoutao
marked this pull request as ready for review
September 2, 2026 08:12
baozhoutao
enabled auto-merge
September 2, 2026 08:13
Uh oh!
There was an error while loading. Please reload this page.
baozhoutao
deleted the
claude/issue-14208-dispatch-gates-package-root-anchor
branch
September 2, 2026 08:39
This was referenced Sep 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:check:generatedprinted5 of 10 declared literal(s) reach nothing tracked, the three named literals among them, each under the reasonno tracked path under its first segment; never was a repo path— while all three sit on disk underpackages/spec/.The population sweep
Over the 201 discovered families' 195 scannable gate scripts, resolving every
import.meta-seeded binding through the tool's ownresolvePathExpression:packages/spec)'zod', a package specifier)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 ofcheck:generated's dead literals areartifact:values in itsGATEDledger (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
deepestTrackedPrefixis 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 throughhintReachesTree. 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:
check:generated, no other)deepestclassProvenance, 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:packages/spec/api-surfacecheck:api-surfacepackages/spec/export-originscheck:export-originspackages/spec/declaration-mapcheck:declaration-mappackages/spec/src/meta-spelling/meta-url-data.generated.tscheck:meta-url-spellingpackages/spec/liveness/state-counts.md0 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, andmoduleRelativeDirectoryHint's naive-resolved-form widening was +53 hints for 1 true lead against this rule's 5 for 5.The whole
--residueoutput 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:Checkable by the reader (
git ls-fileson 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, thedist-freshnesscase 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, blob9743275ctoe51f9d24), restore leg trapped and then proven by byte comparison (git diff HEADempty, blob back to the HEAD blob9743275c). Ablated,check:generatedreturns to5 of 10 declared literal(s) reach nothing trackedwith 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
packageManifestTargetsopened "The three follows above all end at a PROGRAM … and inherit that program's declared population". The middle one does not:readProgramTargetsInSourcefillsentry.reads/entry.readOriginand nothing feeds those intohintsOfModule. The read edge reaches a card only through the last key incoveringKey, an identity claim about a single FILE (#13000).Re-derived at this PR's base, not copied from the card:
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),
discoverFamiliesnumbers 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, andpackageManifestTargets' "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 oforigin/maind16df7418). Derived withnode 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.pnpm check:pm-dispatch-gates(the tool's--self-test)dispatch-gates self-test: 1240 cases passnode scripts/check-ci-filter-parity.mjsnode scripts/check-cross-package-test-inputs.mjs/pnpm check:cross-package-test-inputsnode scripts/check-self-test-wired.mjsnode scripts/check-shard-attestation.mjsnode scripts/check-test-completeness.mjspnpm check:agent-test-spellingpnpm check:bash32-floorpnpm check:cli-command-idspnpm check:declared-population-livepnpm check:entry-guardpnpm check:parse-guardpnpm check:pnpm-filter-targetspnpm check:watch-hint-literalpnpm check:ratchet-remedy-authoritynode scripts/check-self-test-workflow-commands.mjspnpm check:nul-bytesLint, narrowed and declared.
pnpm lintiseslint . --no-inline-configover 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 oneeslint.config.mjswhich never enables type-aware linting for any file (noparserOptions.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; theskip-changesetlabel carries it.Generated by Claude Code
Generated by Claude Code