Skip to content

fix(scripts): refuse pathlessLineCitations on a corpus that declares a doc projection - #18884

Merged
os-bill merged 1 commit into
mainfrom
claude/issue-18845-prose-corpus-refuses-pathless-citations
Sep 18, 2026
Merged

os-bill merged 1 commit into
mainfrom
claude/issue-18845-prose-corpus-refuses-pathless-citations

Conversation

@os-bill

@os-bill os-bill commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Fixes #18845

Clause-②: no

defineCorpus took pathlessLineCitations: true from any corpus, so the prose-vs-data
distinction 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 docProjection may not declare
pathlessLineCitations: true; the refusal fires at registration and names the corpus id and both
declarations, 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:

corpus projection docs admitted colon form L pin
docs/adr/** none 139 17 5 12
scripts/** commentProse 264 50 38 12
packages/spec/src/** commentProse 1458 70 69 1
the system-context page none 1 0 0 0

137 (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 REGEX
MATCHES 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 in
there 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.mjs itself), a
scenario label (L11, L12), a docker tag tail, a count after a colon in a quoted ruling, and —
in packages/spec/src/ui/component.zod.ts and its test — dated read positions inside a SIBLING
repository'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/**; for
packages/spec/src/** the honest restatement is "real but unresolvable, every one of them". The
cost 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 on
docProjection === commentProse. Both readings name the same two corpora today (every projection
in the tree is commentProse), so no measurement separates them — it is a choice about whom the
rule 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: true was
injected into the scripts/** corpus registration (byte-identical mutation in both legs, blob
06d4eb83dc):

  • before the change: node scripts/check-scripts-symbol-anchors.mjs exits 0, accepts silently,
    and reports its waived count rising 15 to 65 — the silent cost above, observed;
  • after the change: exits 1, and the first line of the refusal is
defineCorpus: corpus `scripts` declares a `docProjection` AND `pathlessLineCitations: true`.

Both legs restored with git checkout HEAD -- PATH and proven restored by bytes: on-disk hash
back to the HEAD blob 68940d132c, git diff HEAD empty, git status --porcelain empty.

DARK — it must not fire on the one live opt-in. scripts/check-platform-checklist.mjs
registers the tree's only pathlessLineCitations: true, and it declares no projection. Its
whole-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-anchors and check-system-context-census
all 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 this
file'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/objectstack
derives 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. The
derivation was taken at HEAD ed9b43360d while origin/main stood at 0b31d90fb3; the tool
flags one derivation input as stale across that range and the derived command list is identical
either way.

Acceptance notes

  • scripts/check-platform-checklist.mjs carries the same transcribed figure in its own corpus
    ledger 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 the
    option 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

…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 os-bill added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 18, 2026 — with Claude
@os-bill
os-bill marked this pull request as ready for review September 18, 2026 04:53
@os-bill
os-bill added this pull request to the merge queue Sep 18, 2026
Merged via the queue into main with commit 0a5851d Sep 18, 2026
37 checks passed
@os-bill
os-bill deleted the claude/issue-18845-prose-corpus-refuses-pathless-citations branch September 18, 2026 05:23
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants