Repository navigation
fix(scripts): refuse pathlessLineCitations on a corpus that declares a doc projection - #18884
Merged
os-bill merged 1 commit intoSep 18, 2026
Conversation
…s a doc projection `defineCorpus` took the path-less line-citation declaration from any corpus, so the prose-vs-data distinction the option rests on was advisory. Make it mechanical: a corpus declaring a `docProjection` may not declare `pathlessLineCitations: true`, and the refusal names the corpus and both declarations. The rule is keyed on ANY projection rather than on one projection function's identity: an identity test is evaded by a wrapper of the same function, silently, while over-refusing a projected data corpus fails loudly at registration with the core's own widen-the-core exit. Re-took the reading the rule rests on. Through the extractor as it ships, the two spellings admit 112 + 25 citations across the three prose corpora, not the 222 + 23 that travelled onto the cards: that figure counts raw regex matches (230 + 25 when re-run that way) and subtracts neither fenced blocks nor the citations the path-anchored passes already recorded. Of the 120 admitted in the two projected corpora, zero inherit a tracked path, so opting in there buys unjudged residual and no actionable finding at all. Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3 Co-authored-by: Claude <noreply@anthropic.com>
os-bill
marked this pull request as ready for review
September 18, 2026 04:53
os-bill
deleted the
claude/issue-18845-prose-corpus-refuses-pathless-citations
branch
September 18, 2026 05:23
This was referenced Sep 18, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 28, 2026
…oid tally in the platform checklist's `pathlessLineCitations` rationale (objectstack-ai#18969) Fixes objectstack-ai#18913 Clause-②: no ## What was wrong `scripts/check-platform-checklist.mjs` justified its own corpus declaring `pathlessLineCitations: true` with a corpus-wide count of what the two path-less spellings admit across `docs/adr/**`, `scripts/**` and `packages/spec/src/**`. That figure was a RAW-REGEX tally. PR objectstack-ai#18884 re-took the reading beside the grammar itself and named the raw-regex pair void: it subtracts neither the fenced blocks the extractor skips nor the citations the path-anchored passes had already recorded, so it overstates what the option actually admits by about a half. The aggravating half is where the number sat: inside the file's own stated rationale for a live configuration decision, a dozen lines above the `defineCorpus` call it argues for. An author reading it to decide whether their corpus should opt in was reading a cost the same repository had already withdrawn. ## Premise verification, done first The card's load-bearing claim — that objectstack-ai#18884 named that figure void — was the one thing the dispatching seat had deliberately NOT checked. Confirmed before any edit: - The re-take text and the registration-time refusal both arrive in the same commit, and that commit names the PR: `0a5851d2` "fix(scripts): refuse `pathlessLineCitations` on a corpus that declares a doc projection (objectstack-ai#18884)" (`git log -S` over `scripts/symbol-anchors.mjs`; a positive reading, so the shallow checkout does not weaken it). - The card's census reproduces on this tree. `git grep -nE '245 times|222 and 23|222 \+ 23|222\+23'` over `*.mjs *.ts *.mts *.md *.mdx *.json` returns exactly one hit, the rationale line itself; the control the card names, `112 and 25`, returns exactly one hit in `scripts/symbol-anchors.mjs`, so the one is a reading and not a dumb instrument. - This is the only corpus in the repository declaring the option. The five registered corpora were enumerated; four declare nothing, and the default in `scripts/symbol-anchors.mjs#extractAnchors` is `false`. Nothing here generalises, and nothing here was applied to another corpus. ## Does the corrected figure change the decision it justified? No, and this was checked before the prose was touched, because flipping a corpus declaration under cover of a prose fix is exactly what this card must not become. The old sentence carried two jobs. The qualitative one — in prose a colon before digits is punctuation, so the option admits ports, scenario labels and docblock back-references — survives the re-take unchanged; the re-take's own text enumerates the same three shapes. The quantitative one halves but keeps its sign, so "default OFF is a measurement" still holds. What actually changed is that the reason this corpus may opt in is no longer a cost reading at all. `scripts/symbol-anchors.mjs#defineCorpus` now REFUSES the option, at registration, to any corpus declaring a `docProjection` (objectstack-ai#18845), and the sharper half of the same re-take is that the projected corpora resolve the option into unjudged residual and no actionable finding. This corpus declares no projection and sweeps its documents raw, so the refusal does not reach it. That is a mechanical test the registration passes, which is strictly better than the prose cost argument it replaces. ## The repair Triage asked for a bounded transfer rather than a second absolute number ("绝对读数会腐烂,有界转移不会"), and the card asked that a successor not swap one transcribed reading for another. Both are satisfied by citing rather than restating: - The qualitative claim stays in this file. - The reading stays with the grammar that admits the spellings, cited as `scripts/symbol-anchors.mjs#PATHLESS_COLON_CITATION` — the docblock that carries the re-take, and that names the tree it was taken against, which this entry never did. - The live legality test — the registration-time refusal, and this corpus declaring no projection — is stated where the cost reading used to be. No new corpus-wide figure is written here. The point of the card is that a digit retyped into a second file is gated by nothing; a symbol anchor in a `scripts/**` comment is resolved on every PR by `check:scripts-symbol-anchors`, so the pointer reds if its target is renamed or deleted. ## Reverse verification of the pointer The claim "this transfer is gated, the digit was not" is itself measured, from the committed state, predicting a red: Mutant leg — the anchor's symbol replaced with one that does not exist, via `scripts/ablation-replace.mjs --expect 1` so the anchor must hit; proven on disk before reading any verdict (real anchor occurrences 1 to 0, mutant 0 to 1, blob `91d4962f` to `0c98d3cd`): ``` check-scripts-symbol-anchors EXIT=1 ❌ check-scripts-symbol-anchors: 1 finding(s) across 264 scripts. [unresolved-symbol] scripts/check-platform-checklist.mjs:1565 `PATHLESS_COLON_CITATION_NO_SUCH_SYMBOL` has no declaration site or string-literal token in `scripts/symbol-anchors.mjs` ``` Restore leg — `git checkout HEAD -- scripts/check-platform-checklist.mjs` under an `EXIT INT TERM` trap on an absolute path; `git diff HEAD` prints zero bytes and `git hash-object` returns `91d4962f`, byte-identical to the HEAD blob. Declared: the FIRST ablation attempt was a NO-OP. It passed flag names the tool does not take (`--find`/`--replace` instead of `--anchor`/`--replacement`), the tool printed its usage, and the on-disk blob was unchanged — the reading from that run is void and is not counted. It is reported rather than quietly re-run. No build or `dist/` is in this path: these are plain `.mjs` gates resolved from source, so on-disk blob change is the whole preflight. ## A second stale count in the same block, fixed in place The list this entry belongs to was introduced as "Two declarations this corpus needed and the core did not have:" and has carried THREE items since the `pathlessLineCitations` fold was appended to it — the entry is tagged `(objectstack-ai#18592)` while the other two came from objectstack-ai#18107, so the count was simply never updated. That is the same defect class as the card (a number in this rationale block that the file itself contradicts) and the fix is one word, in the block already named on the claim, adding no verification surface. Changed to "Three". Evidence is the list itself: `unspannedAnchors`, `excludeDirs`, `pathlessLineCitations`. ## Tests Comment-only change to one file. Every reading below was taken on this branch at `5f63efc5`, after the merge of `origin/main`, with `pnpm install --frozen-lockfile` re-run for the lockfile the merge moved. `node scripts/pm/dispatch-gates.mjs --commands` derived 29 gate commands from the change set; all 29 were run and recorded as `command :: exit code`, and `--ran` reconciles them: "29 derived famil(ies) accounted for — 29 run, 0 NOT-MEASURED (a DERIVED zero — all 29 recorded an exit code and none of them is 3)". Every exit code is 0. The derivation is byte-identical before and after the merge, so the pre-merge sweep and this one cover the same families. `check:nul-bytes` is green and a direct control-byte scan of the edited file finds none. `pnpm lint` is the repo-wide scan CI owns. Targeted run on the edited file: 0 errors, 0 warnings, 1 file from `--format json`. The narrowing is sound because this repository never enables type-aware linting — `eslint.config.mjs` says so in its own words, "this repo runs one `eslint.config.mjs`, which never enables type-aware linting (no `parserOptions.project`, no typed `@typescript-eslint` rules) for ANY file, test or not" — so a comment-only edit inside one file cannot move the verdict on any file it does not contain. ## Acceptance notes Nothing to file. The only out-of-scope thing this work turned up is the "Two" versus three miscount above, and it was repaired in place rather than noted, under the bounded in-place rule: same defect class as the card, one word, mechanically determined, inside the block the claim already names, and adding no verification surface. It is called out in its own section rather than buried in the diff. The two fenced files were not touched: `scripts/pm/check-widening-tells.mjs` (PR objectstack-ai#18948) and `scripts/check-sdui-manifest.mjs` (objectstack-ai#18879) are absent from this diff, which carries exactly one path. --- _Generated by [Claude Code](https://claude.ai/code/session_01AmH9bKvGoLjiY86Q4Z3og2)_ Co-authored-by: Claude <noreply@anthropic.com>
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 free
to 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 #18845
Clause-②: no
defineCorpustookpathlessLineCitations: truefrom any corpus, so the prose-vs-datadistinction that declaration rests on was advisory: the next corpus author either remembered it
or did not. This makes it mechanical. A corpus that declares a
docProjectionmay not declarepathlessLineCitations: true; the refusal fires at registration and names the corpus id and bothdeclarations, so the author is told which of the two to drop.
The reading this rests on, re-taken first
The card's cost basis — 222 + 23 hits on the prose corpora, "essentially all false positives" —
was #18592's dev's reading, transcribed onto the card and into two script headers, and never run
twice. Re-taken here on
b0b5f31cc6, through the extractor as it ships (fences skipped,de-duplicated against the path-anchored passes), over each corpus's own declared population and
its own projection:
Lpindocs/adr/**scripts/**commentProsepackages/spec/src/**commentProse137 (112 + 25), not 245. The gap is METHOD, not tree drift: re-run against the very commit
the original names, PR head
88172978c7, the extractor still admits 112 + 25. Counting RAW REGEXMATCHES over the same projected text — no fence skip, no de-duplication — gives 230 + 25 on that
same tree, which is the figure that travelled. So the transcribed number overstates what the
option actually admits by about a half, and it has now been corrected in this module's header
with the commit it was taken against.
The sharper half, and the one that makes this a rule rather than advice: of the 120 hits in the
two projected corpora, ZERO inherit a path this tree tracks. Both of those corpora declare
judgeUntrackedLineAnchors: false, so every one of the 120 is DECLINED, not judged. Opting inthere buys unjudged residual — the
scripts/**gate's own waived count goes 15 to 65, measured —and not one finding an author could act on, at exit 0, with nothing red to notice it by. A
projected corpus that opts in does not break; it quietly stops meaning what its numbers say.
Sampled by hand, the character of the 120 is: this module's own header teaching the spellings it
bans (23 hits across three gate headers, including 15 in
scripts/symbol-anchors.mjsitself), ascenario label (
L11,L12), a docker tag tail, a count after a colon in a quoted ruling, and —in
packages/spec/src/ui/component.zod.tsand its test — dated read positions inside a SIBLINGrepository's renderer at a pinned sha, which are real line pointers but name nothing this tree
holds. So "essentially all false positives" reproduces for
scripts/**; forpackages/spec/src/**the honest restatement is "real but unresolvable, every one of them". Thecost is the same either way, and it is silent.
The boundary, and what the other arm would have cost
The card left one call to the dev: refuse on ANY
docProjection, or only ondocProjection === commentProse. Both readings name the same two corpora today (every projectionin the tree is
commentProse), so no measurement separates them — it is a choice about whom therule governs later.
Chosen: any projection. A projection is the corpus saying the judged text is authored prose
carved out of something else; that is exactly the premise the option contradicts. And the two
failure directions are not symmetric. An identity test is evaded by a wrapper around the same
function —
(src) => commentProse(src)— or by the next projection of the same kind, silently,with nobody deciding it; a self-test case pins that wrapper as refused here. This arm's failure is
the opposite shape: a hypothetical projected DATA corpus is over-refused LOUDLY, at registration,
by a message that names the exit the module's own ruling already prescribes — widen the core, with
the maintainer, rather than fork the rule.
What the narrow arm would have bought, and what it costs: it would let a future
data-shaped projection (say, one that carves JSON string values out of a data file) opt in without
a conversation. The price is that the rule's reach then changes silently whenever a projection is
renamed, wrapped or copied, and a governance rule keyed to one function's identity is a rule whose
population nobody is tracking.
Evidence
LIT — it must fire, both legs on the real corpus file.
pathlessLineCitations: truewasinjected into the
scripts/**corpus registration (byte-identical mutation in both legs, blob06d4eb83dc):node scripts/check-scripts-symbol-anchors.mjsexits 0, accepts silently,and reports its waived count rising 15 to 65 — the silent cost above, observed;
Both legs restored with
git checkout HEAD -- PATHand proven restored by bytes: on-disk hashback to the HEAD blob
68940d132c,git diff HEADempty,git status --porcelainempty.DARK — it must not fire on the one live opt-in.
scripts/check-platform-checklist.mjsregisters the tree's only
pathlessLineCitations: true, and it declares no projection. Itswhole-tree output is byte-identical before and after — same sha256
f1abe15388f9, same exit 0 —which is what says the new rule bites projected corpora only. (That gate was measured
deterministic first: two consecutive pre-change runs are byte-identical.)
The other four corpora are unmoved.
check-scripts-symbol-anchors,check-spec-docblock-symbol-anchors,check-adr-symbol-anchorsandcheck-system-context-censusall exit 0, and the two whose summary lines were captured before the change print byte-identical
summaries after it.
Self-test. Seven cases added in a new block 11f — the refusal, the two things its message must
name, the wrapped-projection row that pins the rule to the declaration rather than to one
function's identity, and three controls: the live DATA opt-in shape, a projected corpus that never
mentions the option, and the option declared OFF beside a projection. The battery floor is raised
149 to 156 with its ledger line. The count was read from the instrument, not assumed: with the
floor temporarily pinned at 9999 the battery reports
registered 156 case(s). The cases ride thisfile's ONE battery deliberately — its roster is per section banner and this file carries none,
which is why a single hoisted battery is its landed shape.
Gate families.
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderives 30 commands for this diff; 29 ran green, with the 30th
(
pnpm check:pm-dispatch-gates, a several-minute self-test) recorded in the report. Thederivation was taken at HEAD
ed9b43360dwhileorigin/mainstood at0b31d90fb3; the toolflags one derivation input as stale across that range and the derived command list is identical
either way.
Acceptance notes
scripts/check-platform-checklist.mjscarries the same transcribed figure in its own corpusledger comment ("these two turned on corpus-wide fire 245 times"). It is read-only for this
card, so it still carries a number this PR proves wrong by about a half. Noted, not filed; the
successor is whichever PR next touches that ledger's line-citation binding block.
docs/adr/**declares no projection, so this rule leaves it free to opt in — and there theoption is not harmless: 2 of its 17 hits are dev-server ports, and one inherits a tracked path
and would become a red finding. The boundary is deliberately about who has DECLARED their
documents to be prose, not about where the option happens to be a bad idea.
Generated by Claude Code