Skip to content

Commit 453ede3

Browse files
fix(scripts): the checklist's line-citation limb refuses the comma form and a tilde range (#25340)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} --> _Requested by **Elon** · [project thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DMTMjHLabD7ET8s6UeiyXxh)_ Fixes #24894 Clause-②: no Metadata-protocol: none ## What changed `check-platform-checklist`'s line-citation limb now refuses the two spellings the card measured going through: the comma form (`foo.ts,123`, `foo.ts,123-140`) and the bare tilde range (`~7354-7368`). Both arrive as ordinary `line-anchor` findings from the one shared sweep, with the remedy the limb already names (a `file#symbol` anchor or a file-level anchor). - **`scripts/symbol-anchors.mjs`, the comma form.** `LINE_ANCHOR`'s separator is now a colon (with the optional approximation tilde) or a comma. It is tight: no space between the path, the comma and the number, so `foo.test.ts, 12 cases` stays a count. Because it carries its own path it is a row for every corpus, not an option, and that was measured first (table below): zero new hits outside the checklist. - **`scripts/symbol-anchors.mjs`, the tilde range.** A new corpus declaration, `tildeRangeCitations` (default OFF), and its pattern `TILDE_RANGE_CITATION`: a tilde, two numbers of at least two digits joined by a hyphen or an en dash, no whitespace, left side refusing an identifier character or a colon, right side refusing an identifier character or a percent sign, and the second number not below the first (so an approximate month stays silent). It inherits the path named before it, exactly as the two path-less spellings do. `defineCorpus` refuses it beside a `docProjection` by the same rule that refuses `pathlessLineCitations` there, and the message names whichever option was declared. Line-anchor findings now carry `comma` / `tildeRange` booleans. - **`scripts/check-platform-checklist.mjs`.** The corpus declares `tildeRangeCitations: true`. A finding in one of the two new spellings that sits inside a `history` array of an area file, or anywhere in `FOLLOW-UPS.md`, is counted and printed, not judged (`appendOnlyLineCitation`, keyed on where the finding sits; `historyLineRanges` walks the JSON with string state so line numbers survive). It is a filter on the sweep's findings, not a grammar: the source-read pin D2 (no local citation grammar) still holds. ⛔ It is no wider than the two spellings: a colon-form citation in a history row is judged exactly as before. The console line now names the comma form and the tilde range and prints the append-only count (12 on `main`'s ledger). ## Why the shared core and not only the checklist's projection The dispatch preferred a checklist-only change. That route is closed by this file's own design: the checklist gate carries no line-citation grammar of its own (self-test D2 reds on one), and the shared core's header records the 2026-09-01 ruling 「⛔ Do not fork this file for a second corpus; if a corpus needs behaviour this core lacks, widen the core」. So the scoping is done the core's way: the path-less spelling is an opt-in declaration only this corpus makes, and the path-carrying spelling became a shared row only after it measured zero new hits in every other corpus. The tilde range is its own declaration rather than a third row inside `pathlessLineCitations`, because `check-adr-symbol-anchors` prints that it leaves "the two path-less spellings" unjudged; folding a third in would make that sentence false. ## Corpus measurement (before any shared pattern moved) Every corpus registered on the shared core, on `7b047b4483`, with the real extractor (tilde range forced on for the measurement): | corpus | comma form (now on for all) | tilde range (if it were on) | |:--|:--|:--| | `docs/adr/**` | 0 | 2: one count, one dated read position (ADR-0131) | | `scripts/**` comment prose | 0 | 3: durations or counts; an approximate month is refused by the ascending rule | | `packages/spec/src/**` docblocks | 0 | 0 | | `isSystem` census page | 0 | 0 | | platform checklist | 11, all in `FOLLOW-UPS.md` | 1, in a `records-forms` history row | Gate verdicts for the four other corpora are byte-identical in their line-anchor and declined counts before and after (ADR 0 line anchors; `scripts/**` 0, 11 declined; spec docblocks 0 new, 202 declined, 7 residual; census page OK). `check-issue-citations`, `isystem-census`, `doc-line-anchors` and `check-react-page-adapter-contract` import no grammar from the core (`commentProse` / `symbolResolutionClass` only, or a comment mention), so nothing there moves. ## Red-first proof The same plant, in `records-forms.json` outside any history row: `sql-driver.ts#createColumn ~7354-7368 and sql-driver.ts,7354`, applied and restored by `scripts/ablation-replace.mjs` (blob back to HEAD, `git diff HEAD` empty, both legs). - On `main` (`7b047b4483`): `node scripts/check-platform-checklist.mjs` exits 0 and prints "line citations: 0 survive". This is the escape. - On this branch: exit 1, and the live control names both: "B5 the live family carries NO judged line citation … records-forms.json:3871 sql-driver.ts,7354, records-forms.json:3871 ~7354-7368". - On this branch, a comma citation planted inside a history row's `change` text: exit 0, append-only count 12 → 13. ## Gates Head `35c4358`. `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 34 commands; each ran with its exit code captured before any pipe and all 34 exit 0, including `pnpm check:platform-checklist`, `node scripts/symbol-anchors.mjs --self-test`, `check-scripts-symbol-anchors` and its `--self-test`, `check-self-test-wired` and `pnpm check:pm-dispatch-gates`. `--ran` reconciliation: "34 derived famil(ies) accounted for — 34 run, 0 NOT-MEASURED". Seven more ran outside the derivation, all exit 0: the checklist's `--self-test` and the three other corpus gates with their `--self-test`s. - `symbol-anchors` self-test floor 156 → 175 (each new firing spelling beside the silent neighbour that bounds it, default-off pair, inheritance, de-duplication, and the projection refusal for the new option with its data-corpus control). - Checklist line-citation-binding battery 9 → 18 (D4 declaration; C1–C3, the card's pins through this corpus's declarations; E1–E5, the append-only filter both ways on a synthetic document, its not-wider case included). - On PR #25323's head (`14056c9`) with this branch's two scripts overlaid: `pnpm check:platform-checklist` exit 0 (329 items, append-only count 12) and its `--self-test` exit 0. `node scripts/pm/check-governed-merges.mjs --branch HEAD`: NOT governed, 337 changed lines. `scripts/` gate files publish nothing, so this PR carries `skip-changeset`. No package is touched, so no build closure, package test or typecheck is owed; no test file names either script. ## Acceptance notes - **Clause-②: no.** The refusal narrows what the QA ledger accepts; that ledger and these scripts are repo tooling, not a published surface, and no metadata author's input changes. - **Append-only scope.** The exemption covers only the two new spellings, because only they have a measured append-only population (11 + 1). A colon-form citation quoted in a history row is still judged, as on `main` (0 today). - **ADR corpus, noted not filed.** `docs/adr/0131-…md` line 1174 carries a tilde range (`applyTenantScope (~12016–12066)`), a position dated to `2514d49f3` in its own sentence. The ADR corpus does not declare `tildeRangeCitations` and its verdict sentence names only the two path-less spellings it leaves off; opting that corpus in is a separate decision with a price (2 hits, one a count). - The attribution block's first line is an HTML comment, which the session requires as the body's first line: a declared deviation from `os-dev.md`'s no-HTML-comment rule (same shape as PR #25323). - The branch is the harness-designated `claude/project-thread-gdsmhy`, the one the seat's claim names, rather than a `claude/issue-24894-*` name. Work was done in a linked worktree (`../objectstack-issue-24894`). --- _Generated by [Claude Code](https://claude.ai/code/session_01PPd7QqomCKXskDb8wkVgDV)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5e772c7 commit 453ede3

2 files changed

Lines changed: 310 additions & 27 deletions

File tree

‎scripts/check-platform-checklist.mjs‎

Lines changed: 151 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1129,7 +1129,12 @@ const SELF_TEST_BATTERIES = Object.freeze({
11291129
// case that a citation both grammars already agreed on keeps its verdict, the
11301130
// over-firing refusal on this ledger's own colon-then-digit neighbours, and
11311131
// the live reading with its control.
1132-
[BATTERY_LINE_CITATION_BINDING]: 9,
1132+
//
1133+
// 9 → 18 at #24894: the tilde-range declaration (D4), the card's own pins
1134+
// through this corpus's declarations (C1 to C3), and the append-only filter
1135+
// driven both ways on a synthetic document (E1 to E5), its ⛔ not-wider case
1136+
// included.
1137+
[BATTERY_LINE_CITATION_BINDING]: 18,
11331138
// 40 → 42 at #18107, and the SHAPE of the battery changed under it: the 25
11341139
// detector/resolver cases that pinned this file's own forked grammar moved
11351140
// into `scripts/symbol-anchors.mjs`'s battery (93 → 122 there) and 27 took
@@ -1700,6 +1705,79 @@ export const NEIGHBOURING_MAP: Readonly<Record<string, string>> = Object.freeze(
17001705
// carries none of its own. `selfTestLineCitationBinding` below is that, driven
17011706
// live in both directions on a synthetic corpus — the citation is a finding
17021707
// with the declaration, and is not one without it.
1708+
//
1709+
// ⭐ Two spellings still went through (#24894), and the fix took the same exit:
1710+
// the core was widened, ⛔ no detector came back here. The COMMA form — a
1711+
// path, a comma, a line spec, no space — is now a row of the shared path-
1712+
// anchored grammar for every corpus, and the bare TILDE RANGE is the core's
1713+
// fourth corpus declaration, `tildeRangeCitations`, which this corpus makes.
1714+
// This ledger had accumulated 42 comma citations before a hand sweep moved
1715+
// them to symbol anchors; nothing refused the 43rd.
1716+
//
1717+
// What stays LOCAL is one thing the core cannot know: which text in THIS
1718+
// ledger is append-only. A `history[*]` row quotes what an item used to say,
1719+
// and `FOLLOW-UPS.md` is the sweeps' decision register ("append, never
1720+
// rewrite"); both were written before any gate read these two spellings, and
1721+
// rewriting a record to pass a gate falsifies the record. So a finding in the
1722+
// two NEW spellings inside that text is counted and NOT judged
1723+
// (`appendOnlyLineCitation`). ⛔ It is a filter on the sweep's findings, keyed
1724+
// on WHERE a finding sits — never a grammar, and never wider than the two
1725+
// spellings: a colon-form citation in a history row is judged exactly as
1726+
// before, because no measured population needs it excused.
1727+
1728+
/* The documents of this family that are append-only as a whole. A path under
1729+
* `CHECKLIST_REL`, compared against a finding's `doc`. */
1730+
const APPEND_ONLY_DOCS = Object.freeze([`${CHECKLIST_REL}/FOLLOW-UPS.md`]);
1731+
1732+
/**
1733+
* The 1-based line ranges of every `"history"` array in one JSON document,
1734+
* inclusive at both ends. A character walk that tracks string state, so a
1735+
* bracket or the word inside a string value is payload — never a parse that
1736+
* would lose the line numbers the sweep reports findings against.
1737+
*/
1738+
function historyLineRanges(text) {
1739+
const ranges = [];
1740+
const open = [];
1741+
let line = 1;
1742+
let inString = false;
1743+
let escaped = false;
1744+
let stringStart = -1;
1745+
let lastString = null;
1746+
let key = null;
1747+
for (let i = 0; i < text.length; i += 1) {
1748+
const c = text[i];
1749+
if (c === '\n') line += 1;
1750+
if (inString) {
1751+
if (escaped) escaped = false;
1752+
else if (c === '\\') escaped = true;
1753+
else if (c === '"') { inString = false; lastString = text.slice(stringStart + 1, i); }
1754+
continue;
1755+
}
1756+
if (c === '"') { inString = true; stringStart = i; continue; }
1757+
if (c === ':') { key = lastString; continue; }
1758+
if (c === '[' || c === '{') { open.push({ history: c === '[' && key === 'history', start: line }); key = null; continue; }
1759+
if (c === ']' || c === '}') {
1760+
const top = open.pop();
1761+
if (top?.history) ranges.push([top.start, line]);
1762+
key = null;
1763+
continue;
1764+
}
1765+
if (c === ',') key = null;
1766+
}
1767+
return ranges;
1768+
}
1769+
1770+
/**
1771+
* Is this sweep finding one of the two #24894 spellings, sitting in text this
1772+
* ledger keeps append-only? `readDoc` answers a finding's `doc` with its text,
1773+
* so the self-test can drive the rule on a synthetic document.
1774+
*/
1775+
function appendOnlyLineCitation(finding, readDoc) {
1776+
if (finding.kind !== 'line-anchor' || !(finding.comma || finding.tildeRange)) return false;
1777+
if (APPEND_ONLY_DOCS.includes(finding.doc)) return true;
1778+
if (!finding.doc.endsWith('.json')) return false;
1779+
return historyLineRanges(readDoc(finding.doc)).some(([from, to]) => finding.line >= from && finding.line <= to);
1780+
}
17031781

17041782
/**
17051783
* ⛔ NOT a grammar, and not a detector — the two things this limb used to be.
@@ -1735,13 +1813,19 @@ function selfTestLineCitationBinding() {
17351813
&& !/\bfunction\s+findLineCitations\w*\b/.test(OWN_SOURCE));
17361814
t('D3 CONTROL for D2 — the same source read DOES find the declaration, so a green above is "no fork" and not "the read returned nothing"',
17371815
/pathlessLineCitations:\s*true/.test(OWN_SOURCE) && /sweepCorpus\(CORPUS, ROOT\)/.test(OWN_SOURCE));
1816+
t('D4 the corpus DECLARES the tilde range too — this ledger wrote one, and without the declaration the shared grammar does not read it',
1817+
CORPUS.tildeRangeCitations === true);
17381818

17391819
// ── the binding, BOTH DIRECTIONS on the SAME text ────────────────────────
17401820
//
17411821
// ⭐ The declaration is read off `CORPUS` rather than written as a literal,
17421822
// so deleting it from the registration fails B1 instead of leaving a case
17431823
// that passes on a flag nothing reads.
1744-
const declared = { unspannedAnchors: CORPUS.unspannedAnchors, pathlessLineCitations: CORPUS.pathlessLineCitations };
1824+
const declared = {
1825+
unspannedAnchors: CORPUS.unspannedAnchors,
1826+
pathlessLineCitations: CORPUS.pathlessLineCitations,
1827+
tildeRangeCitations: CORPUS.tildeRangeCitations,
1828+
};
17451829
const cited = 'ManifestSchema id :140 and the leg ~L7246-7331';
17461830
t('B1 ON — a path-less citation in THIS ledger’s shape is read as a line anchor by the shared grammar, through the declarations this corpus makes',
17471831
extractAnchors(cited, declared).lineAnchors.length === 2,
@@ -1759,11 +1843,60 @@ function selfTestLineCitationBinding() {
17591843
t('B4 the neighbours this ledger is full of stay silent under the declaration — an HTTP status, a config literal, a URL port, a clock time and JSON quoted in prose',
17601844
extractAnchors('status:409 {maxRetries:3} http://localhost:3000/_console/ 08:00 {"scannedTypes":1}', declared).lineAnchors.length === 0);
17611845

1846+
// ── #24894: the two spellings that went through, in THIS ledger's shape ──
1847+
// The card's own pins, through this corpus's declarations. The grammar's
1848+
// full battery (every neighbour, both directions) is the core's.
1849+
const lineRaws = (text, opts = declared) => extractAnchors(text, opts).lineAnchors.map((l) => l.raw);
1850+
t('C1 the COMMA form is refused — a path, a comma and a line, and a path, a comma and a range',
1851+
lineRaws('see foo.ts,123 and foo.ts,123-140').join() === 'foo.ts,123,foo.ts,123-140',
1852+
JSON.stringify(lineRaws('see foo.ts,123 and foo.ts,123-140')));
1853+
t('C2 the TILDE RANGE is refused through this corpus’s declaration, and is NOT read without it',
1854+
lineRaws('createColumn ~7354-7368').join() === '~7354-7368'
1855+
&& lineRaws('createColumn ~7354-7368', { ...declared, tildeRangeCitations: false }).length === 0);
1856+
t('C3 a COUNT after a file name and a history index are admitted — the loose comma is prose',
1857+
lineRaws('pinned by foo.test.ts, 12 cases; see history[1]').length === 0);
1858+
1859+
// ── #24894: append-only text is counted, not judged ──────────────────────
1860+
// ⭐ Driven on a synthetic document through the SAME rule the live loop
1861+
// uses, in both directions: the same spelling one array over IS judged.
1862+
const fixtureDoc = `${CHECKLIST_REL}/areas/fixture.json`;
1863+
const fixture = [
1864+
'{', // 1
1865+
' "items": [', // 2
1866+
' {', // 3
1867+
' "fixtures": { "knownGaps": ["createColumn ~7354-7368"] },', // 4 judged
1868+
' "history": [', // 5
1869+
' { "revision": 1, "change": "had foo.ts,123 [sic]" },', // 6 quoted
1870+
' { "revision": 2, "change": "cut createColumn ~7354-7368" }', // 7 quoted
1871+
' ],', // 8
1872+
' "verify": "then foo.ts,123 again"', // 9 judged
1873+
' }', // 10
1874+
' ]', // 11
1875+
'}', // 12
1876+
].join('\n');
1877+
const readFixture = (doc) => (doc === fixtureDoc ? fixture : '');
1878+
const findingAt = (line, extra) => ({ kind: 'line-anchor', doc: fixtureDoc, line, raw: 'x', comma: false, tildeRange: false, ...extra });
1879+
t('E1 the history array is found by its lines, and a bracket inside a string value does not end it',
1880+
JSON.stringify(historyLineRanges(fixture)) === '[[5,8]]', JSON.stringify(historyLineRanges(fixture)));
1881+
t('E2 a comma citation and a tilde range INSIDE a history row are not judged',
1882+
appendOnlyLineCitation(findingAt(6, { comma: true }), readFixture)
1883+
&& appendOnlyLineCitation(findingAt(7, { tildeRange: true }), readFixture));
1884+
t('E3 CONTROL — the same two spellings OUTSIDE the history array ARE judged',
1885+
!appendOnlyLineCitation(findingAt(4, { tildeRange: true }), readFixture)
1886+
&& !appendOnlyLineCitation(findingAt(9, { comma: true }), readFixture));
1887+
t('E4 FOLLOW-UPS.md is append-only as a whole, for the two new spellings only',
1888+
appendOnlyLineCitation({ kind: 'line-anchor', doc: APPEND_ONLY_DOCS[0], line: 1, comma: true }, readFixture)
1889+
&& !appendOnlyLineCitation({ kind: 'line-anchor', doc: `${CHECKLIST_REL}/README.md`, line: 1, comma: true }, readFixture));
1890+
t('E5 ⛔ NOT wider than the two spellings — a colon-form citation in a history row is still judged',
1891+
!appendOnlyLineCitation(findingAt(6, {}), readFixture)
1892+
&& !appendOnlyLineCitation({ kind: 'line-anchor', doc: APPEND_ONLY_DOCS[0], line: 1 }, readFixture));
1893+
17621894
// ── the LIVE reading, so the zero above the console line prints is a
17631895
// reading and not an instrument that stopped ──────────────────────────
17641896
const liveSweep = sweepCorpus(CORPUS, ROOT);
1765-
const liveLineAnchors = liveSweep.findings.filter((f) => f.kind === 'line-anchor');
1766-
t('B5 the live family carries NO line citation — the class the migration deleted has not come back',
1897+
const readLive = (doc) => readFileSync(join(ROOT, doc), 'utf8');
1898+
const liveLineAnchors = liveSweep.findings.filter((f) => f.kind === 'line-anchor' && !appendOnlyLineCitation(f, readLive));
1899+
t('B5 the live family carries NO judged line citation — the class the migration deleted has not come back',
17671900
liveLineAnchors.length === 0,
17681901
liveLineAnchors.map((f) => `${f.doc}:${f.line} ${f.raw}`).join(', '));
17691902
t('B6 CONTROL for B5 — the same sweep over the same family DID read anchors, so the zero above is a reading and not a sweep that reached nothing',
@@ -1916,6 +2049,12 @@ function selfTestLineCitationBinding() {
19162049
// projection is a corpus saying its judged text is authored PROSE; this
19172050
// one declares none and sweeps its documents raw. That test is
19182051
// mechanical, and this registration passes it.
2052+
// `tildeRangeCitations: true` (#24894) The third path-less spelling, a bare
2053+
// approximate range continuing a filename already named; this ledger
2054+
// carried one in an item's fixtures until a hand sweep re-pointed it.
2055+
// Legal here by the same registration test as the option above. The
2056+
// measurement of what it reads in the corpora that do NOT declare it is
2057+
// with the grammar, `scripts/symbol-anchors.mjs#TILDE_RANGE_CITATION`.
19192058
//
19202059
// The dotted `#Outer.member` form this ledger's grammar carried is now the
19212060
// shared grammar's, and `sweepCorpus` requires EVERY segment to resolve — the
@@ -1934,6 +2073,7 @@ const CORPUS = defineCorpus({
19342073
excludeDirs: ['runs'],
19352074
unspannedAnchors: true,
19362075
pathlessLineCitations: true,
2076+
tildeRangeCitations: true,
19372077
});
19382078

19392079
// ── The residual the binding measured (#16898) ────────────────────────
@@ -3534,8 +3674,14 @@ for (const [doc, c] of sweep.byDoc) {
35343674
anchorCounts.set(rel, c.declaration + c.literal);
35353675
}
35363676

3677+
// The two #24894 spellings inside append-only text: counted and printed, NOT
3678+
// judged (see `appendOnlyLineCitation`).
3679+
let appendOnlyCitations = 0;
3680+
const readSweptDoc = (doc) => readFileSync(join(ROOT, doc), 'utf8');
3681+
35373682
for (const f of sweep.findings) {
35383683
const rel = relToChecklist(f.doc);
3684+
if (appendOnlyLineCitation(f, readSweptDoc)) { appendOnlyCitations += 1; continue; }
35393685
if (f.kind === 'unresolved-symbol') {
35403686
const anchor = `${f.path}#${f.symbol}`;
35413687
// On the measured residual? Then this is one of the 56 the binding
@@ -3640,7 +3786,7 @@ console.log(
36403786
` traps: ${TRAPS.size} documented, ${usedTraps.size} in use;` +
36413787
` provisioning: ${recipeTotal} area recipes, ${recipeRefs} item references resolved (${qualifiedRefs} area-qualified), ${recipesReferenced}/${recipeTotal} recipes referenced;` +
36423788
` meta-URL spelling: ${metaCallsScanned} \`call\` strings scanned against ${FOLDED_META_SPELLINGS.size} folded spellings;` +
3643-
` line citations: 0 survive across ${sweep.counts.docs} swept documents — \`file:line\`, a bare \`:NNN\` continuation and an \`L\` pin are all judged by \`symbol-anchors.mjs\`, through the same registration;` +
3789+
` line citations: 0 survive across ${sweep.counts.docs} swept documents — \`file:line\`, the comma form \`file,line\`, a bare \`:NNN\` continuation, an \`L\` pin and a tilde range \`~NNN-MMM\` are all judged by \`symbol-anchors.mjs\`, through the same registration (${appendOnlyCitations} comma or tilde-range citation(s) in append-only text — \`history\` rows and FOLLOW-UPS.md — counted, not judged);` +
36443790
` symbol anchors: ${anchorsResolved}/${anchorsScanned} resolved by \`symbol-anchors.mjs\` (the ONE resolver, reached as a REGISTERED corpus) across ${sweep.counts.docs} swept documents against ${sweep.counts.citedSources} cited sources` +
36453791
`, ${anchorsResidual} on the named #16898 residual, ${Object.keys(anchorFloors).length} file floors held;` +
36463792
` (self-checks: ${trapControl.checked} trap-vocabulary + ${provisioningControl.checked} provisioning-resolve + ${unreferencedControl.checked} unreferenced-recipe + ${metaCallControl.checked} meta-call-spelling + ${citationControl.checked} line-citation-binding + ${symbolAnchorControl.checked} symbol-anchor + ${plannedStatusControl.checked} planned-status assertions).`,

0 commit comments

Comments
 (0)