Commit 64de4c5
fix(check-adr-symbol-anchors): the verdict may not claim a census wider than the vocabulary it read (#18925)
Fixes #18899
Clause-②: no
`check-adr-symbol-anchors` printed `0 line anchors survive.` and that
sentence
was false. This change deletes the surviving pointers inside the
declared file
surface — every one of them sampled stale before it was deleted — and
makes the
verdict stop claiming a census it does not take.
## Caliber first, then the count
⛔ No count is inherited here, mine included. A **line pointer** is
defined as a
token in an authored document that designates a position in a source
file by
LINE NUMBER. The class was then enumerated by SPELLING — six of them —
and each
regex was lit on a literal it must see before any corpus was read; the
two
path-less spellings additionally got a negative control on the
neighbours they
must refuse (an HTTP status, a URL port, a clock time, a lane label, an
identifier ending in a capital L before digits). Fenced blocks are
skipped,
exactly as the extractor skips them, so quoted material is not counted
as
authored.
`docs/adr/**` at the branch point, 139 records:
| spelling | hits | records | this gate reads it? |
|:---|---:|---:|:---|
| A path-anchored (a path, a colon, digits) | 6 | 3 | yes — and all 6
carry `anchor-exempt` markers, which is why the gate was green |
| B backticked bare continuation | 0 | 0 | yes |
| C tilde-backticked number | 0 | 0 | yes |
| D path-less bare colon continuation | 5 | 2 | ⛔ no |
| E the `L` pin as a markdown link-target fragment | 2 | 1 | ⛔ no |
| F the `L` pin bare in prose (E is a subset of F) | 12 | 4 | ⛔ no |
Two of D's five are ADR-0047's dev-server ports, which are not line
pointers at
all, so the caliber's verdict is: **15 real line pointers across 5
records were
invisible to this gate** — not 5, and not 2.
### The card's 「未復現」 section, reproduced
The handed-over report claimed **12** fragments across ADR-0020×6,
ADR-0021×2,
ADR-0034×2, ADR-0077×1, and the filing seat could neither reproduce the
12 nor
make the parts sum to it. At spelling **F** the measurement is 12 across
exactly
those four records, distributed 0020×6 · 0021×2 · **0034×3** · 0077×1.
So the
**total was right**, the **0034 subtotal was one short**, and the class
it
counted was the `L` pin rather than the narrower `#L` fragment the
filing seat
measured at 2. ⛔ Neither reading was wrong; they were two calibers. Both
are
reproduced above, from one instrument, with the light on.
## The reading that decided deletion, not correction
#13556 ruled this class deleted with no transition period, on a 4/4
sample of
pointers landing on unrelated code. The card states plainly that it did
not take
that sample. Taken here, on every pointer the caliber found — **15/15
stale**:
| pointer | claims to point at | line N is, today |
|:---|:---|:---|
| `security-plugin.ts` 626 (ADR-0096 ×3) | the `getReadFilter`
read-filter fall-open | a JSDoc closing line inside the
`SecurityReportSink` logging-sink interface |
| `record-validator.ts` 198 (ADR-0020 ×2) | `validateRecord` | a comment
inside the `ValidationError` constructor; `validateRecord` is declared
at 1185 — 987 lines away |
| `object.zod.ts` 354 / 371 / 410 / 765 (ADR-0020 ×4) | "managed-by
buckets and toolbar lifecycle actions" | a closed-batch comment, a
`where`-guidance comment, `const nearMiss =`, an author-time-rejection
comment. The file's `lifecycle` block now sits at 818-1010 as an
ADR-0057 retention/ttl/storage/archive policy |
| `engine.ts` 806 + 961 (ADR-0021 ×2) | the two `metadataArrayKeys`
lists | a blank docblank line and a comment about answering one mistake
two ways. `metadataArrayKeys` is discussed at 2327, where a comment says
it USED to be two lists |
| `engine.ts` 1811, `sql-driver.ts` 561-563, `sql-driver.ts` 548-600
(ADR-0034 ×3) | a deadlock warning, a savepoint, the sequences table | a
docblock tail, a datetime-canonicalisation docblock, a
presentation-convergence comment. `SEQUENCES_TABLE` is at 181 |
| `engine.ts` 3217 (ADR-0077 ×1) | the sandbox-runner ALS limitation | a
comment about callers probing a private guard. The `setImmediate`
discussion is at 4572 / 15700 / 15866 |
The structural clue the card asked to be verified **holds**: ADR-0096's
inherited
path does sit in the same table row as the bare number, in a different
cell. That
is why the path-anchored pass cannot see it — there is no path adjacent
to the
number to anchor on.
## What this change does, and what it deliberately does not
Three deletion commits, one per distinct stale target, each carrying its
own
sample in the message; then one commit on the gate.
**ADR-0096** — deleting a path-less number leaves the sentence with no
referent,
so two of the three take the symbol anchor the record's own table row
already
uses for that site, and the third points at the row above it. Both
substitutions
resolve: the gate's anchor count rises 2093 to 2095 and 2 of those are
new
CHECKED pointers rather than merely shorter prose.
**ADR-0020** — the path half of the links is correct and survives, so
these are
pure fragment deletions dropping to the file-level target the gate's own
remedy
text names as its second exit. ⛔ The prose is not repaired: whether
ADR-0020's
parenthetical still describes `object.zod.ts` is a separate defect and
takes its
own card, exactly as this gate's header instructs.
**The gate** — `summaryLine()` now names the vocabulary it judged and
states that
the two path-less spellings are opt-in and off here. The self-test holds
the
claim in three parts, and the middle one is the control that makes the
battery
worth running: the corpus DECLARES the narrower vocabulary; the shared
grammar
really does see the wider one on the same text when asked; and the
sentence names
both halves and can no longer end in the unqualified form. Battery floor
17 to 22.
### ⛔ Route (c), widening the vocabulary, is MEASURED and NOT taken
`pathlessLineCitations: true` on this corpus yields **17 findings across
6
records**. Two of them are ADR-0047's dev-server ports: correct prose no
author
can repair, whose only remedy is the maintainer-only exemption marker.
Four of
the six records are outside this card's declared file surface. The
alternative
route is worse: `unspannedAnchors: true` yields **8 findings, 6 of them
fabrications** — correct relative ADR-to-ADR markdown links reported as
missing
FILES because a heading fragment reads as a symbol.
So the price tag is real and the decision is not this sentence's to
make. What
the sentence owed was to stop reading as though the decision had already
gone the
other way. ⛔ It also cannot be taken inside this fence at all: the
vocabulary
lives in `scripts/symbol-anchors.mjs`, shared by five corpora, and that
file is
outside the declared surface. **This change does lower the bill**: the
17 becomes
8 once these nine pointers are gone.
## Evidence
**Gates** — `dispatch-gates --repo objectstack-ai/objectstack
--commands` derived
**37** families from the real three-path change set; all 37 run, all
exit 0,
reconciled with `--ran` carrying exit codes: *"37 derived families
accounted for —
37 run, 0 NOT-MEASURED (a DERIVED zero — all 37 recorded an exit code
and none of
them is 3)"*. One needed a second pass:
`@objectstack/lint check:doc-formula-expressions` first exited **3 —
PREREQUISITE NOT MET**, which its own text declares is not a finding;
after
`turbo run build --filter=@objectstack/formula
--filter=@objectstack/lint` it
exits 0.
**Ablation, 4 legs, all on the final tree at `c38eead`.** Every leg
proved its
mutation reached disk (anchor count dropped, blob hash moved) before the
run was
read, and every restore was verified by blob-equals-HEAD plus an empty
`git diff HEAD` — not by an exit code:
| leg | mutation | the case that fired |
|:---|:---|:---|
| a | corpus declaration flipped to `pathlessLineCitations: true` | *the
narrower vocabulary must be this corpus's DECLARATION* |
| b | the probe literal stripped of both spellings | *the shared grammar
must SEE both path-less spellings when asked … got 0* |
| c1 | the unqualified sentence restored wholesale | *the verdict must
name BOTH the vocabulary it judged and the one it did not* |
| c2 | both halves named, the unqualified claim re-appended | *the
verdict must not end in the unqualified claim* |
Leg **b** is the one that matters: without it, legs a and c would hold
just as
well on a day when the wider grammar had stopped matching anything, and
the new
caveat would be documenting a dead rule.
**Lint, a declared narrowing rather than the farm run.** Three readings,
all at
`c38eead`: ① the examined population comes from eslint's own config, not
a
guess — `ESLint#isPathIgnored` answers `true` for both `docs/adr/**`
records
(they are outside eslint's population entirely) and `false` for the
script;
② `--format json` reports **1 file linted, 0 errors, 0 warnings**; ③
invariance —
`eslint.config.mjs` states in its own words that this repo *"runs one
`eslint.config.mjs`, which never enables type-aware linting (no
`parserOptions.project`, no typed `@typescript-eslint` rules) for ANY
file"*, and
a grep for that configuration finds only the comment saying it is
absent, so no
verdict on an untouched file can move as a function of this diff. The
repo-wide
`eslint . --no-inline-config` remains CI's run.
**Control bytes** — `check:nul-bytes` green, plus a direct scan of the
touched
files for the non-NUL control bytes it does not cover: no matches.
## Changeset: `skip-changeset`, measured
Nothing published moves. The root package is `private: true`, and **0 of
the 70
public packages** has a `files[]` entry that escapes its own directory
or names
`docs/adr`. Measured at the byte level against the built output with a
matched
positive control: a grep for the exact text this diff changed
(`record-validator.ts` plus the fragment, the four-pin list, the two
`:626`
sentences, the old verdict phrase) across every built `dist/` exits
**1**, while
the same grep shape fires on 168 files for a symbol that is published.
One
near-miss worth naming: published `.d.ts` files DO cite ADR-0020 by URL,
so the
record's *filename* ships — its *text* does not.
## Acceptance notes
⛔ Out of the declared file surface, so measured and reported rather than
touched:
- **Six line pointers of this same class survive in three records this
card's
fence does not name** — ADR-0021 ×2, ADR-0034 ×3, ADR-0077 ×1. All six
sampled
stale above. Editing them would breach the fence and would widen the
file
surface the claim comment declared, so they are left for a follow-up
card;
the staleness sampling is already done above. Dedupe words: `L pin
pathless
ADR line pointer` · `0021 0034 0077 stale L citation` · `adr-anchors
blind
form remainder` · `pathlessLineCitations adr corpus`.
- **ADR-0047's two dev-server ports are the standing obstacle to route
(c)** —
correct prose whose only remedy is a maintainer-only marker. Any
widening card
has to decide that first. Dedupe words: `dev-server port read as line
citation` · `pathless colon false positive 0047`.
- noted, not filed: whether ADR-0020's parenthetical ("managed-by
buckets and
toolbar lifecycle actions") still describes `object.zod.ts`, whose
`lifecycle`
block is now an ADR-0057 retention policy. The record's *claim* that
`lifecycle` is overloaded there is still true, so this is prose drift in
the
specifics, not a false statement — and this gate's header forbids
repairing
prose to clear it. Carrier: the next author of ADR-0020's naming
section; no
PR is queued on it.
- #18494 and #18493 are held out of this round at the seat and are not
addressed
here. #13556 · #14279 · #15809 · #18101 are near neighbours and none
folds in.
## 维护者速读(草稿)
**改了什么.** 两件事。一是删掉九个指向源码行号的旧指针——三个在 ADR-0096、六个在
ADR-0020,删前逐个抽样,九个全部指向无关代码(最离谱的一个偏了 987 行,落在一个错误类的
构造函数里)。二是把门禁那句结语从「0 line anchors survive.」改成说清它究竟量了哪几种
写法、哪两种没量。门禁逻辑一行没动,只动了它对外的说法,以及三条防止这句话退回去的自测。
**为什么改.** #13556 已裁决「ADR 行号锚」这一类无过渡期删除,依据是 4/4 抽样全烂。但门禁
的词汇表比那一类窄,于是两种写法活了下来:一种把行号藏在 markdown 链接的 fragment 里,
一种把行号与路径拆在同一表格行的不同单元格里。门禁读不到它们,却仍然印「0 … survive」。
**读数是真的,那句话不是**——这就是本卡判据 (b)。一句印在绿灯里的假零,下一个人会据它认定
这一类已经清完。
**风险与代价(含回滚).** 风险很低。① 不发布任何东西:根包是私有包,70 个公开包里没有一个
的 `files[]` 能伸到 `docs/adr` 或根 `scripts/`,已用字节级 grep 配正控实测,故声明
`skip-changeset`。② 本卡故意**没有**去扩宽门禁词汇表,因为实测代价摆在这里:打开那个开关
会在 6 份 ADR 上红 17 条,其中 2 条是 ADR-0047 的开发服务器端口号——那是完全正确的散文,
作者无法修,唯一出路是维护者专属的豁免标记;另有 4 份 ADR 在本卡申报的文件面之外。换一条
路更糟:`unspannedAnchors` 会红 8 条,其中 6 条是把正确的 ADR 互链误报成「文件不存在」。
⇒ **扩词汇表是一个带价签的决定,不该由一句结语替您做。** 本卡把账单从 17 降到 8。
③ 回滚成本近乎零:四个 commit 各自独立,`git revert` 任意一个都不影响其余,删 ADR 指针
那三个纯属文本删除。④ 唯一需要您过目的判断:ADR-0096 有两句删掉数字后会没有指代对象,
本卡让它们改用该 ADR **自己那张表里已有的**符号锚(`#getReadFilter`),没有新造事实;
这两处因此从「不可检查的数字」变成了「门禁会检查的锚点」。
**席位意见.**
**你要做的.** ① 这是受管面(`docs/adr/**`),CI 绿也不由 AI 座位合并——需要您亲手确认与
合并。② 请重点看 ADR-0096 那两句的指代替换读起来对不对(是否仍然说的是同一个站点);其余
七处是纯删除,不改变任何论断。③ 如果您认为剩下那三份 ADR(0021 / 0034 / 0077)的六个指针
应当一并清掉,或者愿意为扩宽门禁词汇表付那 8 条红灯的账,请说一声——两件都已量好,另立卡
即可动手。
---
_Generated by [Claude
Code](https://claude.ai/code/session_01AmH9bKvGoLjiY86Q4Z3og2)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent b84a847 commit 64de4c5
3 files changed
Lines changed: 83 additions & 13 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
45 | | - | |
| 45 | + | |
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
| |||
58 | 58 | | |
59 | 59 | | |
60 | 60 | | |
61 | | - | |
| 61 | + | |
62 | 62 | | |
63 | 63 | | |
64 | 64 | | |
| |||
105 | 105 | | |
106 | 106 | | |
107 | 107 | | |
108 | | - | |
| 108 | + | |
109 | 109 | | |
110 | 110 | | |
111 | 111 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
177 | 177 | | |
178 | 178 | | |
179 | 179 | | |
180 | | - | |
181 | | - | |
| 180 | + | |
| 181 | + | |
182 | 182 | | |
183 | 183 | | |
184 | 184 | | |
| |||
194 | 194 | | |
195 | 195 | | |
196 | 196 | | |
197 | | - | |
| 197 | + | |
198 | 198 | | |
199 | 199 | | |
200 | 200 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
85 | 85 | | |
86 | 86 | | |
87 | 87 | | |
88 | | - | |
| 88 | + | |
89 | 89 | | |
90 | 90 | | |
91 | 91 | | |
| |||
137 | 137 | | |
138 | 138 | | |
139 | 139 | | |
140 | | - | |
141 | | - | |
142 | | - | |
143 | | - | |
144 | | - | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
145 | 184 | | |
146 | 185 | | |
147 | 186 | | |
| |||
177 | 216 | | |
178 | 217 | | |
179 | 218 | | |
180 | | - | |
| 219 | + | |
181 | 220 | | |
182 | 221 | | |
183 | 222 | | |
| |||
300 | 339 | | |
301 | 340 | | |
302 | 341 | | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
303 | 373 | | |
304 | 374 | | |
305 | 375 | | |
| |||
0 commit comments