Skip to content

Commit 6fc22b7

Browse files
docs(merge-driver): the system-context row's prose says the line-anchors comparator is the identity on that page today (#21075)
Fixes #16612 Clause-②: no Ruling-ref: 5748968327 Executes ruling A (`5748968327`): `mixed: 'line-anchors'` stays on the `system-context.mdx` row, and the three prose blocks that still describe the line-anchor page as current are rewritten so history reads as history and today reads as today. Prose only. `scripts/git-merge-regen.mjs` is untouched, and the `endToEndMixed()` self-test still has its row to exercise. ## What changed - **`scripts/regen-artifacts.mjs`, the row comment.** Every original argument is kept, now in the past tense and marked as true "while the page carried `file:line` anchors": the #13646 routing argument, the #13625 `4408/5771/6019/6382/6575` measurement, #14064's correction with its three measurements, the "keep the routing, not NOT_DRIVER_MANAGED" argument and the 24-of-25 census. Two paragraphs are new: - **Today (since #15921).** Every citation on the page is a `path#symbol` anchor, so `blankAnchorLineNumbers` has nothing to blank and the `'line-anchors'` comparator is the identity on this page. A deferral is proven lossless only when THEIRS equals the ancestor, or equals OURS, byte for byte. So every real edit takes the text-merge branch or conflicts loudly, and that is by design. - **Keep the field.** `mixed` is what routes the row through the lossless check in the first place. Without it, the row takes the unconditional deferral, which is the silent deletion #14064 closed. The reproduction below shows this. - **`scripts/regen-artifacts.mjs`, the `why:` string of the `content/docs/permissions/**` `NOT_DRIVER_MANAGED` entry.** It no longer calls the page "a generated anchor table". It was one when it was routed; today its anchors are `path#symbol` and only its declared counts are generated. This is the one executable line in the diff, and no code reads it: in `git-merge-regen.mjs` every `.why` read is `verdict.why`, never an entry's `why`. - **`.gitattributes`, the paragraph above the row.** The history is now in the past tense ("caught the stale anchors a merge left behind", "the cheap case stayed cheap"), and a "Today (since #15921)" paragraph is added with the same facts and a pointer to the row comment. One clause inside the rewritten paragraph was inverted and is now corrected. It said "hand-written paragraphs the gate is constitutionally unable to miss"; it now says "…whose loss the gate is constitutionally unable to notice". The measured point is that ten gates did NOT notice a deleted paragraph. - **`scripts/doc-line-anchors.mjs`, the module header.** - Kept as the record of why the module exists: the 101-of-111 measurement and the "ONE reader" origin. - A new "Today, since #15921" section states: - the page cites by `path#symbol`; - the grammar is `scripts/symbol-anchors.mjs`, which reads a line-number spelling only to report it; - the census gate reads its anchors through that module, not this one; - the module's one in-tree importer is `scripts/git-merge-regen.mjs` (`git grep doc-line-anchors` outside the module itself has that one hit); - the comparator is the identity on the page by design; - the census `--fix` no longer touches anchors. **Proof that only comment lines changed** (base `6073bb96b8`, head `7270d9c5ed`). Every changed line in the two `.mjs` files is a `//` or ` *` comment line, except the three lines of that `why:` string. Every changed line in `.gitattributes` starts with `#`. The negative control below backs this up: both exported tables are deep-equal to base, and the `.gitattributes` non-comment lines are identical. ## 验收备注 **1. Direct reading.** Hypothesis 1 held. Measured at `7270d9c5ed`, with a pre-#15921 control taken from the page at `f2f6684cd5`, the base the card itself measured. The script imports `scripts/doc-line-anchors.mjs` and prints `extractLineAnchors(page).length` and `blankAnchorLineNumbers(page) === page` for each page: ``` control: page at f2f6684 (pre-#15921) | extractLineAnchors(page).length = 141 | blankAnchorLineNumbers(page) === page : false page at HEAD | extractLineAnchors(page).length = 0 | blankAnchorLineNumbers(page) === page : true ``` The control reproduces the card's own `141 / false`, so the parser is live and the zero reflects the page. **2. Control leg: field present ⇒ prose survives.** Hypothesis 2 held. Setup: a throwaway repo whose `merge.os-regen.driver` is this worktree's `scripts/git-merge-regen.mjs %O %A %B %P`, with `.gitattributes` routing the page `merge=os-regen`. The ancestor is the real page at HEAD (anchor-free). THEIRS appends one hand-written paragraph carrying `NEEDLE-16612`. OURS edits only the frontmatter `title:` line. The edits do not overlap. Run at `7270d9c5ed` (excerpt of the script's output; the full log is the dev report's evidence): ``` --- [control-head] row field lines ` mixed: 'line-anchors',` in that driver's table: 1 ⟳ content/docs/permissions/system-context.mdx NOT deferred: the incoming side carries hand-written changes that no regeneration can restore. This file is MIXED, so it was TEXT-MERGED (cleanly — both sides' prose is in the result) rather than resolved to one side. The generated half still needs: pnpm gen:system-context-census Auto-merging content/docs/permissions/system-context.mdx Merge made by the 'ort' strategy. --- [control-head] git merge exit: 0 --- [control-head] conflict markers in merged file: 0 --- [control-head] ours title edit present: 1 --- [control-head] NEEDLE present in merged file? === true ``` **Mutation leg (optional; re-run here).** Field deleted ⇒ prose silently lost. The worktree was never mutated. The mutation ran on a scratch copy of `scripts/`, committed in its own throwaway repo. Its `regen-artifacts.mjs` blob was `5ae1ba8bef`, identical to the PR head. The field line was deleted through `scripts/ablation-replace.mjs`, which reported `ok mutation landed: anchor 1 -> 0, blob 5ae1ba8 -> f7365b0f98b9`. Excerpt of the output: ``` --- [mutation-head] row field lines ` mixed: 'line-anchors',` in that driver's table: 0 ⟳ content/docs/permissions/system-context.mdx not text-merged — it is generated. Regenerate from the merged tree: Auto-merging content/docs/permissions/system-context.mdx Merge made by the 'ort' strategy. --- [mutation-head] git merge exit: 0 --- [mutation-head] conflict markers in merged file: 0 --- [mutation-head] ours title edit present: 1 --- [mutation-head] NEEDLE present in merged file? === false ``` After both legs: worktree `git diff HEAD` is empty and `git status --porcelain` is clean. **3. `pnpm check:merge-driver`: green, which is necessary but not sufficient.** At `7270d9c5ed` it printed: - `✓ 1 mixed row(s) name a comparator that exists and discriminates (line-anchors)` - `✓ end-to-end (mixed): anchors-only deferred to OURS; incoming prose survived instead of being dropped` - `✓ merge driver wiring is consistent (34 path(s) deliberately excluded).` It was green before this change as well, so its green is not the evidence. Items 1 and 2 are. **4. Negative control: no routing or field changes.** Base modules imported against head: - `REGEN_ARTIFACTS`: 18 rows at base and at head, deep-equal. - `NOT_DRIVER_MANAGED`: 34 entries at base and at head, equal ignoring `why`. The only `why` that changed is `content/docs/permissions/**`. - `DEFAULT_OWNER` / `ROOT_OWNER` and the export set: unchanged. - `.gitattributes`: non-comment lines identical, `merge=os-regen` rows 19 / 19. **5.** Nothing under `content/docs/releases/`, `docs/adr/**`, `.claude/**`, `skills/**`, `AGENTS.md` or `CLAUDE.md` is touched. ## Gates (all at `7270d9c5ed`, the final commit) - `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` derived 28 commands for these three paths. All 28 exited 0. `--ran` reconciliation: `✓ dispatch-gates --ran: 28 derived famil(ies) accounted for — 28 run, 0 NOT-MEASURED (a DERIVED zero — all 28 recorded an exit code and none of them is 3).` - Named: `pnpm check:merge-driver` exit 0 (above). `pnpm check:nul-bytes` exit 0 (`no raw ASCII control bytes`). `node scripts/check-scripts-symbol-anchors.mjs` exit 0 (`3718 anchors across 282 scripts resolve … 0 line anchors on tracked targets survive`); its `--self-test` also exit 0. The edited scripts carry no `--self-test` of their own. `regen-artifacts.mjs` is exercised by `git-merge-regen.mjs --self-test`, which is inside `check:merge-driver`. - Roster gates flagged as sharing `scripts/`: - Exit 0: `check-published-list-mirrors`, `check:console-injection`, `check:engine-double-contract`, `check:i18n-stale-fill`. - NOT MEASURED: `check:dts-closure` and `check:published-readme-exports`. Both exited 3 (PREREQUISITE NOT MET: no package `dist/` in this worktree). This diff touches no workspace package. - Lint, as a proven narrowing (the repo-wide `pnpm lint` is CI's run). `eslint --no-inline-config --format json` over both edited `.mjs` files exited 0 with 0 errors and 0 warnings. - (1) Both files are in eslint's own population: `--print-config` resolves a config for each, and the JSON carries no ignored-file message. - (2) The JSON reports 2 files. - (3) Type-aware linting is not enabled: `parserOptions.project` and `projectService` are both null in the resolved config, so this diff cannot move the verdict on any untouched file. - `.gitattributes` is not lintable. - Changeset: none. The root package `@objectstack/spec-monorepo` is `private: true` and owns `scripts/` and `.gitattributes`, so nothing published changes ⇒ `skip-changeset` label. ## Acceptance notes These were noticed while doing the work. They are not fixed here, and none is filed (none has a public door or a named real producer): - **Outside the claimed file surface, same staleness, left untouched.** The claim admitted the module header only, and this PR does not widen it: - `scripts/doc-line-anchors.mjs`, the `blankAnchorLineNumbers` docblock, still says the census `--fix` "rewrites exactly these numbers and nothing else". The new header names that account as the pre-#15921 one. - `scripts/regen-artifacts.mjs`, the `mixed` section of the `REGEN_ARTIFACTS` docblock, still cites the page as "the measured case" of a generated half "unreachable by any text merge". That was true of the line-anchor half. - **`scripts/git-merge-regen.mjs` (fenced off by the ruling).** - The `MIXED_COMPARATORS['line-anchors']` docblock says the page's generated half "is exactly those numbers: `check-system-context-census.mjs --fix` rewrites them and touches nothing else". - The `deferralIsLossless` docblock cites "24 of the last 25 main commits" as the common case. - The `drive()` conflict message tells the human "the anchor numbers do not matter here — take either side and then run … which re-derives them from the merged tree". On today's page there are no anchor numbers, and `--fix` re-derives only declared counts. - **Dormant parser over-read in `extractLineAnchors`.** A bare number after a dash is taken as a RANGE_END anchor when the span to its left is any code span, not necessarily an anchor, provided an earlier path-only citation set the current file. Measured over the 408 `content/docs` md/mdx files: one hit, `content/docs/protocol/objectui/layout-dsl.mdx` doc line 577, where `` `1`–`4` `` is read as an anchor into `packages/spec/src/ui/view.zod.ts`. No consumer reads that page through this parser, and `system-context.mdx` has 0 hits. - **Population counts in this routing prose disagree with each other.** The `why:` string says "22 of its 23 pages are hand-written", while the row comment says "22 hand-written prose pages with ONE generated page" and "21 prose files". Measured at HEAD: 22 `.mdx` files plus `meta.json` (21 prose and `system-context.mdx`). These are untouched here, because the ruling scoped the anchor-table facts. --- _Generated by [Claude Code](https://claude.ai/code/session_01JAhu8u8QfBvRjVZDox7CP9)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent e0c768f commit 6fc22b7

3 files changed

Lines changed: 137 additions & 63 deletions

File tree

‎.gitattributes‎

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -86,38 +86,53 @@
8686
# merge-and-regenerate round. The cure is the one the header above records for
8787
# the three hottest artifacts; each gate sums its shards when it reads them.
8888
#
89-
# The elevation census page joined at #13646 — a generated `file:line` anchor
90-
# table whose correct merged values are on NEITHER side of a conflict (measured on
91-
# #13625: five conflicted anchors resolved to 4408/5771/6019/6382/6575 against
92-
# branch 4407/5770/… and main 4284/5647/…), so no text merge and no hand merge can
93-
# reach them. ⚠️ It is routed as the FILE and NOT as `content/docs/permissions/**`:
94-
# unlike `content/docs/references/**` above, which is generated whole, that
95-
# directory is 22 hand-written prose pages around one generated one, and the glob
96-
# would defer the prose to OURS. See NOT_DRIVER_MANAGED for that entry.
89+
# The elevation census page joined at #13646, when it was a generated `file:line`
90+
# anchor table whose correct merged values were on NEITHER side of a conflict
91+
# (measured on #13625: five conflicted anchors resolved to 4408/5771/6019/6382/6575
92+
# against branch 4407/5770/… and main 4284/5647/…), so no text merge and no hand
93+
# merge could reach them. ⚠️ It is routed as the FILE and NOT as
94+
# `content/docs/permissions/**`: unlike `content/docs/references/**` above, which
95+
# is generated whole, that directory is 22 hand-written prose pages around one
96+
# generated one, and the glob would defer the prose to OURS. See
97+
# NOT_DRIVER_MANAGED for that entry.
9798
#
9899
# This is also the row where the header's own warning is answered rather than
99100
# accepted: `scripts/check-system-context-census.mjs` still reddens on every PR from
100-
# the required `Lint & Repo Gates` job — it RE-DERIVES the census from the tree, so
101-
# it catches the stale anchors a merge leaves behind even when nothing conflicted,
102-
# which is the majority case (#13625: 18 anchors stale, 5 marked). The driver removes
103-
# hand-merge rounds; it is never the only signal.
101+
# the required `Lint & Repo Gates` job — it RE-DERIVES the census from the tree.
102+
# While the page carried line anchors, that is what caught the stale anchors a merge
103+
# left behind even when nothing conflicted, which was the majority case (#13625: 18
104+
# anchors stale, 5 marked). The driver removes hand-merge rounds; it is never the
105+
# only signal.
104106
#
105107
# ⚠️ #14064 CORRECTED the sentence that used to open that paragraph — "deferring is
106-
# safe here BECAUSE the census gate re-derives". The gate re-derives the census and
108+
# safe here BECAUSE the census gate re-derives". The gate re-derived the census and
107109
# the anchors. It re-derives no PROSE, because prose is derived from nothing, and
108110
# this page is the one routed path that carries both. So the argument was true and
109111
# its domain was half the risk surface: the driver drops a side WHOLE, and on this
110-
# page that side can carry hand-written paragraphs the gate is constitutionally
111-
# unable to miss. Measured, not reasoned — deleting a 3-line anchor-free paragraph
112-
# and running ten doc-family gates returned ten exit 0 over deleted documentation.
113-
#
114-
# Routing this file is still RIGHT (the correct anchors are on neither side; nothing
115-
# above changes). What #14064 added is the missing half: the row now declares
116-
# `mixed: 'line-anchors'` in scripts/regen-artifacts.mjs, and the driver refuses to
117-
# defer SILENTLY when the incoming side carries anything but anchor numbers — it
118-
# text-merges instead, and conflicts loudly if that cannot be done. The cheap case
119-
# stays cheap: of the last 25 main commits to this page, 24 changed nothing but
120-
# anchor numbers.
112+
# page that side can carry hand-written paragraphs whose loss the gate is
113+
# constitutionally unable to notice. Measured, not reasoned — deleting a 3-line
114+
# anchor-free paragraph and running ten doc-family gates returned ten exit 0 over
115+
# deleted documentation.
116+
#
117+
# #14064 kept the routing (the correct anchors were on neither side) and added the
118+
# missing half: the row declares `mixed: 'line-anchors'` in
119+
# scripts/regen-artifacts.mjs, and the driver refuses to defer SILENTLY when the
120+
# incoming side carries anything but anchor numbers — it text-merges instead, and
121+
# conflicts loudly if that cannot be done. The cheap case stayed cheap: of the last
122+
# 25 main commits to this page when that was measured, 24 changed nothing but anchor
123+
# numbers.
124+
#
125+
# Today (since #15921) the page carries `path#symbol` anchors and no line numbers,
126+
# so the `line-anchors` comparator is the IDENTITY on it (measured on #16612:
127+
# `extractLineAnchors(page).length === 0`, `blankAnchorLineNumbers(page) === page`).
128+
# Every real edit therefore takes the text-merge branch, or conflicts loudly, by
129+
# design; the renumbering case is gone because there is nothing left to renumber.
130+
# What the census gate catches after a merge is now a symbol anchor that no longer
131+
# resolves, a population that no longer matches, or a declared count that no longer
132+
# equals the census. ⛔ The `mixed` field stays: it is what routes this row through
133+
# the lossless check at all, and deleting it sends the row to the unconditional
134+
# deferral, the silent deletion #14064 closed (reproduced on #16612). The row's
135+
# comment in scripts/regen-artifacts.mjs carries the measurements.
121136

122137
#
123138
# #13731 enumerated this file's blind spot and closed it: `check:merge-driver` now

‎scripts/doc-line-anchors.mjs‎

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,45 @@
22
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
33

44
/**
5-
* doc-line-anchors -- the ONE reader for `file:line` anchors written in docs prose.
5+
* doc-line-anchors -- the parser for `file:line` anchors written in docs prose,
6+
* built as their ONE reader. Its one in-tree importer today is the merge driver;
7+
* "Today, since #15921" below is the current state, and the rest of this header
8+
* is the record of why the module exists.
69
*
710
* A docs page that cites source by `` `<file>.ts:1560` `` has created a
811
* two-sided invariant with no owner: the line lives in one tree, the citation in
912
* another, and nothing relates them. Measured on
10-
* `content/docs/permissions/system-context.mdx` over 19 days, **101 of its 111
11-
* anchors rotted** -- the construct still existed, the line number no longer
12-
* named it -- while CI stayed green throughout.
13+
* `content/docs/permissions/system-context.mdx` over 19 days, while it still
14+
* carried them, **101 of its 111 anchors rotted** -- the construct still existed,
15+
* the line number no longer named it -- while CI stayed green throughout.
1316
*
14-
* This module is the parsing half of the fix, kept separate from the gate that
15-
* uses it because the defect is not specific to one page: any page carrying
17+
* This module was the parsing half of the fix, kept separate from the gate that
18+
* used it because the defect is not specific to one page: any page carrying
1619
* `file:line` anchors has it, and the second page should cost a ledger rather
1720
* than a parser.
1821
*
19-
* ## The three anchor shapes, all of which are real on the corpus
22+
* ## Today, since #15921
23+
*
24+
* That page now cites source by `path#symbol`. The grammar is
25+
* `scripts/symbol-anchors.mjs`, which reads a line-number spelling only to report
26+
* it as a finding, and the page's census gate reads its anchors through that
27+
* module, not this one. So on the page this module was built for,
28+
* `extractLineAnchors` finds nothing and `blankAnchorLineNumbers` is the IDENTITY
29+
* -- measured on #16612: `extractLineAnchors(page).length === 0` and
30+
* `blankAnchorLineNumbers(page) === page`.
31+
*
32+
* The one in-tree importer is `scripts/git-merge-regen.mjs`: its `'line-anchors'`
33+
* comparator is `blankAnchorLineNumbers`, selected by that page's `mixed` field in
34+
* `scripts/regen-artifacts.mjs`. The identity is the intended answer there, not a
35+
* broken one. The driver proves a deferral lossless only when the incoming side
36+
* equals the ancestor or ours byte for byte, so every real edit to the page takes
37+
* the text-merge branch, or conflicts loudly, by design -- and the field stays,
38+
* because it is what sends the page through that check at all (that row's comment
39+
* carries the measurement). The account of `--fix` in `blankAnchorLineNumbers`'s
40+
* docblock below is the pre-#15921 one: the census `--fix` no longer touches
41+
* anchors, and regenerates only the page's declared counts.
42+
*
43+
* ## The three anchor shapes, all of which were real on that page
2044
*
2145
* A naive reader finds only the first and undercounts by a third.
2246
*
@@ -54,8 +78,9 @@
5478
* ## What is deliberately NOT here
5579
*
5680
* No knowledge of what a line should CONTAIN. That is the consuming gate's
57-
* question -- for `system-context.mdx` it is answered by an AST census -- and
58-
* baking any answer in here would make the module single-use.
81+
* question -- for `system-context.mdx` it was answered by an AST census, which
82+
* since #15921 holds that page's symbol anchors instead -- and baking any answer
83+
* in here would make the module single-use.
5984
*
6085
* @module
6186
*/

‎scripts/regen-artifacts.mjs‎

Lines changed: 64 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -234,9 +234,12 @@ export const REGEN_ARTIFACTS = Object.freeze([
234234
gen: 'gen:liveness-counts',
235235
check: 'check:liveness',
236236
},
237-
// #13646. The elevation census page — a generated `file:line` anchor table, and
238-
// the first row here owned by ROOT tooling rather than by `packages/spec` (the
239-
// owner field #13585 added exists for exactly this).
237+
// #13646. The elevation census page, and the first row here owned by ROOT tooling
238+
// rather than by `packages/spec` (the owner field #13585 added exists for exactly
239+
// this). It was routed as a generated `file:line` anchor table, and since #15921
240+
// it carries no line anchors at all: the argument below is kept as the record of
241+
// why the row exists, in the past tense, and "Today" at the end of this block is
242+
// the current state.
240243
//
241244
// ⚠️ The row is the FILE, not `content/docs/permissions/**`, and the difference
242245
// is safety rather than tidiness. Its routed sibling `content/docs/references/**`
@@ -246,20 +249,22 @@ export const REGEN_ARTIFACTS = Object.freeze([
246249
// prose edit, which is the exact trade `migrations/registry.ts` is kept out of
247250
// this table for. The glob is recorded in NOT_DRIVER_MANAGED below.
248251
//
249-
// Why it belongs here at all: two PRs that each ran `--fix` against their own
250-
// tree write correct-for-themselves line numbers into the same rows, and the
251-
// merged tree's correct values equal NEITHER side — measured on #13625's merge,
252-
// where the five conflicted anchors resolved to `4408/5771/6019/6382/6575`
253-
// against branch `4407/5770/…` and main `4284/5647/…`. A text merge cannot reach
254-
// that answer from either input, so this is a deferral-and-regenerate shape.
252+
// Why it was routed here (#13646, while the page carried `file:line` anchors): two
253+
// PRs that each ran `--fix` against their own tree wrote correct-for-themselves
254+
// line numbers into the same rows, and the merged tree's correct values equalled
255+
// NEITHER side — measured on #13625's merge, where the five conflicted anchors
256+
// resolved to `4408/5771/6019/6382/6575` against branch `4407/5770/…` and main
257+
// `4284/5647/…`. A text merge could not reach that answer from either input, so it
258+
// was a deferral-and-regenerate shape.
255259
//
256260
// ⚠️ #14064 CORRECTED the safety argument this row used to carry, and the
257261
// correction is why it is the one row with a `mixed` field. The old text said the
258262
// deferral is safe *because* `check-system-context-census.mjs` re-derives the
259-
// census from the tree on every PR. That argument is TRUE and it covers HALF of
260-
// what the driver discards. The gate re-derives the census and the anchors; it
263+
// census from the tree on every PR. That argument was TRUE and it covered HALF of
264+
// what the driver discards. The gate re-derived the census and the anchors; it
261265
// re-derives no prose, because the prose is derived from nothing. So the argument's
262-
// domain and the risk surface do not coincide, and the gap is not theoretical:
266+
// domain and the risk surface did not coincide, and the gap was not theoretical
267+
// (#14064's measurements, taken while the page still carried line anchors):
263268
//
264269
// - The driver never writes `%A`, so the side left behind is OURS and the side
265270
// dropped is always THEIRS — i.e. always main's already-landed work, never the
@@ -271,29 +276,56 @@ export const REGEN_ARTIFACTS = Object.freeze([
271276
// `check:docs-single-h1`, `check:corpus-claim-drift`, `check:docs-audit-scope`,
272277
// `check:role-word`, `check-doc-frontmatter`, `check-docs-section-name`,
273278
// `check-doc-route-spelling`): ten exit 0 over silently deleted documentation.
274-
// - It is not a rare path. main rewrites this page about every 75 minutes, so any
275-
// PR touching it and older than an hour meets the driver by construction.
279+
// - It was not a rare path. main rewrote this page about every 75 minutes, so any
280+
// PR touching it and older than an hour met the driver by construction.
276281
//
277-
// ⭐ The routing itself is still RIGHT and #14064 does not undo it: the merged
278-
// tree's correct anchors are on NEITHER side (measured on #13625 — five conflicted
279-
// anchors resolve to 4408/5771/6019/6382/6575 against branch 4407/5770/… and main
280-
// 4284/5647/…), so no text merge and no hand merge can reach them. Moving this row
281-
// to NOT_DRIVER_MANAGED would buy the prose back by handing a page that conflicts
282-
// hourly to a human who cannot resolve it correctly. The census over every routed
283-
// path says that trade is worse than it looks: of the last 25 main commits to this
284-
// page, 24 changed nothing but anchor line numbers — the case the driver handles
285-
// correctly and cheaply — and exactly 1 touched prose.
282+
// ⭐ #14064 kept the routing, and its argument was right about the page it
283+
// measured: the merged tree's correct anchors were on NEITHER side (measured on
284+
// #13625 — five conflicted anchors resolved to 4408/5771/6019/6382/6575 against
285+
// branch 4407/5770/… and main 4284/5647/…), so no text merge and no hand merge
286+
// could reach them. Moving this row to NOT_DRIVER_MANAGED would have bought the
287+
// prose back by handing a page that conflicted hourly to a human who could not
288+
// resolve it correctly. The census over every routed path said that trade was
289+
// worse than it looked: of the last 25 main commits to this page when that was
290+
// measured, 24 changed nothing but anchor line numbers — the case the driver
291+
// handled correctly and cheaply — and exactly 1 touched prose.
286292
//
287-
// ⇒ So the row keeps its routing and declares WHEN the deferral is lossless.
293+
// ⇒ So the row kept its routing and declared WHEN the deferral is lossless.
288294
// `mixed` names the equivalence: two revisions equal after
289-
// `blankAnchorLineNumbers` differ only in the half `--fix` re-derives, and
290-
// dropping either loses nothing. `git-merge-regen.mjs` refuses to defer silently
291-
// when they are not equal, so the 1-in-25 prose case becomes a text merge or a
292-
// loud conflict instead of a silent deletion, and the 24-in-25 anchor case keeps
293-
// #13646's win untouched. This is the field to reach for when the NEXT mixed
295+
// `blankAnchorLineNumbers` differed only in the anchor numbers `--fix` re-derived
296+
// then, and dropping either lost nothing. `git-merge-regen.mjs` refuses to defer
297+
// silently when they are not equal, so the 1-in-25 prose case became a text merge
298+
// or a loud conflict instead of a silent deletion, and the 24-in-25 anchor case
299+
// kept #13646's win. This is still the field to reach for when the NEXT mixed
294300
// artifact arrives — a whole-file generator needs no `mixed`, because there is
295301
// nothing on its page a regeneration cannot restore.
296302
//
303+
// ⭐ Today (since #15921): every citation on the page is a `path#symbol` anchor
304+
// (`scripts/symbol-anchors.mjs`), which does not move when its file grows, and the
305+
// census gate reports a surviving line number as a finding. So
306+
// `blankAnchorLineNumbers` finds nothing to blank, and the `'line-anchors'`
307+
// comparator is the IDENTITY on this page — measured on #16612,
308+
// `extractLineAnchors(page).length === 0` and
309+
// `blankAnchorLineNumbers(page) === page`.
310+
// A deferral is proven lossless only when THEIRS equals the ancestor or OURS byte
311+
// for byte, so every real edit — prose, an anchor, a declared count — takes the
312+
// text-merge branch, or conflicts loudly. That is by design, not a degraded mode:
313+
// it is the safe half of #14064's trade, and what it gives up is the 24-in-25
314+
// renumbering case, which can no longer occur because there are no numbers left to
315+
// renumber. A clean text merge is still recorded as owing
316+
// `gen:system-context-census`, which `pre-commit` collects.
317+
//
318+
// ⛔ Keep `mixed: 'line-anchors'` anyway. The field is not only the comparator's
319+
// name; it is what routes this row through the lossless check at all. Without it
320+
// the driver takes the unconditional deferral every wholly-generated row takes —
321+
// keep OURS, drop THEIRS — and that is the silent deletion #14064 closed. Measured
322+
// on #16612 in a throwaway repo with this driver, ours and theirs making
323+
// non-overlapping edits: field present ⇒ text-merged, the incoming paragraph kept;
324+
// field deleted ⇒ `git merge` exit 0, no markers, the incoming paragraph gone. It
325+
// is also the row `endToEndMixed()` in `git-merge-regen.mjs --self-test` exercises,
326+
// and that self-test reports "nothing to prove", and passes, once no row carries
327+
// `mixed`.
328+
//
297329
// No `readsDist`/`readsSchemaTree`: the census is an AST walk over `src/`, so a
298330
// merged tree is the whole prerequisite. `gen` still cannot launder a POPULATION
299331
// change into a row nobody wrote: a site that arrived or vanished needs a human
@@ -474,7 +506,9 @@ export const NOT_DRIVER_MANAGED = Object.freeze([
474506
why:
475507
'the DIRECTORY is not what #13646 routed, and recording that is the point of this ledger. '
476508
+ '22 of its 23 pages are hand-written permissions prose; exactly one — `system-context.mdx`, '
477-
+ 'declared above — is a generated anchor table. Routing the tree the way its sibling '
509+
+ 'declared above — is routed, as a MIXED row (a generated `file:line` anchor table when it was '
510+
+ 'routed; today its anchors are `path#symbol` ones and only its declared counts are '
511+
+ 'generated). Routing the tree the way its sibling '
478512
+ '`content/docs/references/**` is routed reads as symmetry and is not: that sibling is '
479513
+ 'generated whole, this one would defer 21 prose files to OURS and lose the other side\'s '
480514
+ 'edits silently. Route the generated FILE; leave the neighbours to text-merge, which is '

0 commit comments

Comments
 (0)