|
| 1 | +# Partial-retirement-annotation signal — corpus measurement (#6635) |
| 2 | + |
| 3 | +**Measured at** `origin/main` `bf32d4a0ec1cebca4f636b26c040ad6903c54287` (2026-08-10). |
| 4 | +`main` takes roughly 18 merges a day, so every number below belongs to that sha. |
| 5 | + |
| 6 | +**Instrument**: `scripts/measure-partial-retirement-annotation.mjs`. Not a gate, not |
| 7 | +wired into any workflow, not a `check:`/`gen:` script. Committed so the numbers are |
| 8 | +reproducible rather than asserted. |
| 9 | + |
| 10 | +**Mandate**: the maintainer ruling of 2026-08-09 on #6635 — measurement first, no gate |
| 11 | +is built yet. This document reports total hits, a spot-verified true/false-positive |
| 12 | +split, and the exemption count a warning-tier rule would need on day one. **It does not |
| 13 | +select a decision branch**; the ruling reserves that. |
| 14 | + |
| 15 | +--- |
| 16 | + |
| 17 | +## 1. The signal as implemented |
| 18 | + |
| 19 | +For each (file, retired symbol): flag the file when at least one mention of the symbol |
| 20 | +cites its retirement issue number and at least one other mention does not and does not |
| 21 | +frame the symbol historically. Annotation-presence, never name-presence — which is what |
| 22 | +lets a tombstone error string, whose whole job is to name the retired key, pass. |
| 23 | + |
| 24 | +The annotation window is the **stanza**: a maximal run of lines with non-empty content |
| 25 | +once comment furniture is stripped. In TSDoc that is the paragraph; in markdown it is |
| 26 | +the paragraph. |
| 27 | + |
| 28 | +Inventory (141 symbols) is the repo's own declaration surface — `RETIRED_DEFS_BY_MAJOR`, |
| 29 | +`RETIRED_KEYS_BY_MAJOR` and the `retiredKey()` tombstone guidance strings — tiered by how |
| 30 | +distinctive the name is: |
| 31 | + |
| 32 | +| tier | what | example | count | |
| 33 | +|---|---|---|---| |
| 34 | +| A | retired def names | `ETLPipeline`, `WidgetManifest` | 45 | |
| 35 | +| B | retired keys, qualified | `Manifest.loading`, `crypto.hash` | 81 | |
| 36 | +| C | retired keys, bare | `transform`, `type`, `cursor` | 15 | |
| 37 | + |
| 38 | +## 2. Positive control — the scanner is proven to see before any number is believed |
| 39 | + |
| 40 | +The specimen fixes landed before this measurement, so the control was rebuilt from |
| 41 | +history rather than read off `main`: |
| 42 | + |
| 43 | +| tree | file | result | |
| 44 | +|---|---|---| |
| 45 | +| `4e271b2c6` (= the #6630 fix commit's parent) | `packages/spec/src/shared/retry-policy.zod.ts` | **FLAGGED** — `ETLPipeline`, cited at L30 (`#6414`), bare at L81 and L133 | |
| 46 | +| `bf32d4a0e` (`origin/main`) | same file | **clear** — 0 hits | |
| 47 | + |
| 48 | +That is the #6630 finding reproduced mechanically, and the expected direction in both |
| 49 | +legs: red before the fix, green after it. |
| 50 | + |
| 51 | +**Recall bound, measured on the same specimen.** #6630 landed in two parts. The signal |
| 52 | +catches part 1 (PR #6701) and **structurally cannot catch part 2** (PR #6753): that site |
| 53 | +had a single mention, phrased as prose (`an ETL pipeline's retry`) rather than as the |
| 54 | +symbol name. The signal needs at least two mentions with at least one already annotated, |
| 55 | +so it is blind both to single-mention drift and to the pure-miss case where a retirement |
| 56 | +pass touched nothing in the file at all — the more common shape of a missed retirement. |
| 57 | + |
| 58 | +## 3. Total hits |
| 59 | + |
| 60 | +| scope | hits (file x symbol) | files | |
| 61 | +|---|---|---| |
| 62 | +| all tiers, no exclusions | **197** | **65** | |
| 63 | +| tier A only | 42 | 10 | |
| 64 | +| tier B only | 99 | 45 | |
| 65 | +| tier C only | 56 | 22 | |
| 66 | + |
| 67 | +Window sensitivity — the burden is not an artifact of the stanza choice: |
| 68 | + |
| 69 | +| window | hits | files | |
| 70 | +|---|---|---| |
| 71 | +| stanza (default) | 197 | 65 | |
| 72 | +| plus/minus 3 lines | 184 | 66 | |
| 73 | +| plus/minus 6 lines | 179 | 56 | |
| 74 | + |
| 75 | +## 4. True/false-positive split |
| 76 | + |
| 77 | +**34 of 197 hits were verified by hand** (file opened, mention and its stanza read in |
| 78 | +context). Sampling was not random: every hit in the residual set of section 5 was |
| 79 | +verified exhaustively (20/20), plus 14 more drawn one-per-class from the record surfaces |
| 80 | +and tier C, chosen to cover each structural class rather than to estimate a rate. |
| 81 | + |
| 82 | +**Result: 0 true positives, 34 false positives.** |
| 83 | + |
| 84 | +No hit was a retired symbol taught in the present tense as if it still existed. The |
| 85 | +closest candidate — `packages/spec/docs/SYNC_ARCHITECTURE.md` L399, an |
| 86 | +`import type { ETLPipeline }` — is the deliberate "Before" specimen of a migration |
| 87 | +guide, in a plain fence rather than a `typescript` one, with the file stating in prose |
| 88 | +that it must not compile. |
| 89 | + |
| 90 | +## 5. What a warning-tier rule would emit on day one |
| 91 | + |
| 92 | +Building the most generous structural exclusions into the rule itself — drop tier C |
| 93 | +entirely, path-exclude every record surface (the two registries, `CHANGELOG.md`, |
| 94 | +`.changeset/`, `docs/audits/`, `docs/protocol-upgrade-guide.md`, |
| 95 | +`content/docs/releases/`) — leaves: |
| 96 | + |
| 97 | +| rule shape | hits | files | |
| 98 | +|---|---|---| |
| 99 | +| all tiers, no exclusions | 197 | 65 | |
| 100 | +| tier A+B only | 141 | 51 | |
| 101 | +| **tier A+B, record surfaces excluded** | **20** | **16** | |
| 102 | +| tier A only, record surfaces excluded | 8 | 5 | |
| 103 | + |
| 104 | +**All 20 were hand-verified. All 20 are false positives.** So the day-one exemption |
| 105 | +count for the best-case rule is **20 entries across 16 files, against 0 true positives** |
| 106 | +— every warning it emits on the current tree would have to be exempted. |
| 107 | + |
| 108 | +## 6. Structurally legitimate partial-citation classes |
| 109 | + |
| 110 | +Five classes where partial citation is correct authoring, not drift. These are not |
| 111 | +"files that happen to be noisy" — in each, annotating every mention would make the prose |
| 112 | +worse. |
| 113 | + |
| 114 | +1. **The retirement ledgers themselves** (62 hits / 2 files). `RETIRED_DEFS_BY_MAJOR`'s |
| 115 | + entries are bare string literals — `'ui/I18nObject',` — that can never carry an issue |
| 116 | + number, and the D2/D3 evidence strings enumerate the retired names by design |
| 117 | + (`'No source imports \`ServerEvent\`, \`ServerEventType\`, ...'`). |
| 118 | +2. **Retirement pin tests.** A removal's pin test asserts the name is gone — |
| 119 | + `expect(name in httpServer).toBe(false)`, |
| 120 | + `expect(HookBodyCapability.options).not.toContain('crypto.hash')`, |
| 121 | + `it('rejects the retired \`body\` with the rename prescription')`. The name must |
| 122 | + appear bare, once per assertion. This is a genre the repo requires of every |
| 123 | + retirement. |
| 124 | +3. **Migration guides with a "Before" specimen.** The retired shape must be spelled out |
| 125 | + un-annotated or the guide teaches nothing. |
| 126 | +4. **Changelogs, changesets and generated projections** (74 hits / 34 files). Historical |
| 127 | + by construction: the release note that removed the symbol names it, and so do the |
| 128 | + earlier entries that introduced it. |
| 129 | +5. **Annotated header plus enumeration stanza.** The repo's own correct pattern is a |
| 130 | + section header carrying `#NNNN` followed by a markdown table or bullet list naming one |
| 131 | + retired shape per row (`system/http-server.zod.ts` L198-215 is the model). Any |
| 132 | + stanza-scoped window splits the citation from the rows it governs. |
| 133 | + |
| 134 | +## 7. Two detector-level defects found while building the instrument |
| 135 | + |
| 136 | +Reported because they price the gate, not as findings against any file. |
| 137 | + |
| 138 | +**a. Bare-key matching collides, including with other retirements.** Tier C keys — |
| 139 | +`type`, `body`, `layout`, `multiple`, `transform`, `cursor`, `distinct` — are ordinary |
| 140 | +words. Sharpest case: `distinct` was retired **twice by different issues** |
| 141 | +(`query.distinct` at #4286, `AggregationNode:distinct` at #6815), so a paragraph |
| 142 | +correctly annotated `#4286` reads as un-annotated when checked against `#6815` |
| 143 | +(`content/docs/protocol/objectql/query-syntax.mdx` L102). |
| 144 | + |
| 145 | +**b. There is no machine-readable retirement-to-issue mapping in the repo.** A gate |
| 146 | +needs one as data. The registry tables are flat string arrays whose attribution lives |
| 147 | +only in free prose above each group, and parsing that prose is wrong in two silent ways: |
| 148 | +it picks the wrong number when a comment names several (the `transform` trio's header |
| 149 | +reads "The first entries since #4659 built this table (#5552)" — #4659 built the table, |
| 150 | +#5552 is the retirement), and it cannot see repo boundaries (the #6946 group cites |
| 151 | +`objectui#3829` and `objectui#3818`, indistinguishable from local numbers once the |
| 152 | +prefix is dropped). The mapping in the instrument is therefore hand-curated and stated |
| 153 | +explicitly so it can be audited. |
| 154 | + |
| 155 | +## 8. Reproducing |
| 156 | + |
| 157 | +```bash |
| 158 | +node scripts/measure-partial-retirement-annotation.mjs # summary |
| 159 | +node scripts/measure-partial-retirement-annotation.mjs --hits # every hit with context |
| 160 | +node scripts/measure-partial-retirement-annotation.mjs --tier A # one tier |
| 161 | +node scripts/measure-partial-retirement-annotation.mjs --window 3 # window sensitivity |
| 162 | +node scripts/measure-partial-retirement-annotation.mjs --inventory # the 141 symbols |
| 163 | +``` |
| 164 | + |
| 165 | +The positive control needs a tree at the pre-fix commit: |
| 166 | + |
| 167 | +```bash |
| 168 | +git worktree add --detach ../objectstack-6635-cmp 4e271b2c670a1262b83dc50450c1244639df6115 |
| 169 | +cd ../objectstack-6635-cmp |
| 170 | +node <path-to>/scripts/measure-partial-retirement-annotation.mjs \ |
| 171 | + --tier A --file packages/spec/src/shared/retry-policy.zod.ts --hits |
| 172 | +``` |
0 commit comments