@am_shork/attest 0.11.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,11 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## Versioning
9
9
 
10
- Under 0.x SemVer a breaking change bumps the **minor**. "Breaking" means it
10
+ From `[1.0.0]` a breaking change bumps the **major**. "Breaking" means it
11
11
  changes the exit code of an existing valid setup, rejects previously-valid
12
12
  input, removes/renames a public API or `--json` field, or changes a default
13
13
  runtime behavior an existing invocation relies on — diagnostic message text is
14
- not API.
14
+ not API. The definition has not moved; what moved is the bump it earns, and the
15
+ whole history below `[1.0.0]` was written under the 0.x rule, where the same
16
+ definition earned a **minor**.
17
+
18
+ **One surface keeps the 0.x rule, and it is named rather than inferred.**
19
+ `./inspect` is excluded from the freeze below, so a breaking change to it is a
20
+ minor until a second adopter's usage exists. Everything else the next section
21
+ lists takes the major.
22
+
23
+ `schemaVersion` in the `--json` report is a **separate axis** and not a
24
+ restatement of this one: it is bumped whenever the emitted shape changes
25
+ incompatibly, and an additive field bumps neither it nor the package. Removing
26
+ or renaming a `--json` field is breaking by the definition above and is
27
+ therefore both — a `schemaVersion` bump and, from 1.0, a major.
15
28
 
16
29
  **A narrowing that closes a security hole is breaking by that definition, and
17
30
  takes the bump that definition gives it — including a major.** There is no
@@ -35,6 +48,224 @@ invites is the other one: a fix weakened until it fits a bump somebody
35
48
  preferred. A narrowing that ships smaller than the defect it closes is what this
36
49
  paragraph exists to make visible.
37
50
 
51
+ ## What 1.0 freezes
52
+
53
+ Decided 2026-08-31 and binding since `[1.0.0]`, where the argument, the
54
+ measurement it rests on and what is still unmeasured are recorded. The line
55
+ exists because every open entry waiting on an adopter is asking somebody to build
56
+ on a surface documented as movable, and a line is the only thing that makes that
57
+ a reasonable ask. It is here rather than only there because a stability promise a
58
+ reader cannot find is not one.
59
+
60
+ **Frozen at 1.0: the CLI — its commands, options and exit codes — and the root
61
+ export `.`.** Both are frozen on evidence that they have stopped moving rather
62
+ than on intention: the CLI's last change of shape was `--apply` on 2026-08-03,
63
+ twelve releases ago, and `src/index.ts` has had its export list edited in three
64
+ commits ever, the last on 2026-07-26.
65
+
66
+ **Explicitly not frozen at 1.0: `./inspect`.** It shipped in `[0.9.2]` and has
67
+ two releases and one adapter behind it. Freezing it would be freezing on one
68
+ adapter, which is the objection this project raises against every premature
69
+ interface and does not get to waive for its own. It stays under the 0.x rule
70
+ above until a second adopter's usage exists — stated here, rather than left to
71
+ be inferred from a version number that would say otherwise. `./define` sits
72
+ between the two and is frozen with the CLI, on seven releases.
73
+
74
+ **1.x, not 1.0:** everything currently blocked on an adopter's evidence — the
75
+ judgement schema, any retrieval surface over the archive. The freeze is not a
76
+ claim that those are settled. (A backlink index over `archive/` was named here
77
+ until 2026-09-01, when it was rejected outright; it is not deferred to 1.x, and
78
+ what closed it is at the foot of this file.)
79
+
80
+ **Not a frozen surface, and deliberately: the workflow document `attest init`
81
+ writes into the adopter's repository.** It is a function of the installed version
82
+ — which is what makes re-running `init` after an upgrade the whole update
83
+ mechanism — so freezing it would freeze the workflow the tool teaches. What 1.0
84
+ does not do is make its drift detectable, and that is a live question at the foot
85
+ of this file rather than something this line settles. It is named here because
86
+ the first adoption measured across an upgrade paid nothing on the four surfaces
87
+ above and 110 stale lines on this one.
88
+
89
+ *What this costs is the paragraph above.* Under 1.0 a narrowing that closes a
90
+ security hole takes a major, on whatever schedule a hole is found. That price is
91
+ now stated rather than unknown, which is what made the line decidable at all.
92
+
93
+ ## [1.0.0] - 2026-09-01
94
+
95
+ ### Added
96
+
97
+ - **`ATX-87` gates the CLI's command and option roster, which is the other half
98
+ of the same freeze.** `What 1.0 freezes` names two surfaces in one sentence —
99
+ the CLI, its commands, options and exit codes, and the root export `.` — and
100
+ after `ATX-86` three of those four halves were held: the export by `ATX-86`,
101
+ the exit codes by the requirement making `hasError` the single predicate the
102
+ verdict, the JSON `ok` and the process status all read from, and by the one
103
+ keeping the report alone on stdout. **The roster was held by nothing.** Seven
104
+ commands and seventeen options, and no test anywhere enumerated either, so
105
+ deleting a flag or renaming a command broke nothing a contributor runs.
106
+ `ATX-16.staticCommands` reads like the missing gate and is not: it names four
107
+ commands in order to say they execute no project code, which is a claim about
108
+ behaviour rather than a claim that the roster is closed.
109
+
110
+ **Read out of `--help` rather than out of `cli/index.ts`, and that is forced
111
+ rather than preferred.** Every option string in the source is a constant —
112
+ `JSON_FLAG`, `EVAL_FLAG`, `VITEST_CONFIG_FLAG`, `TARGET_FLAG` — so a static
113
+ walk for literals sees the command names and almost none of the flags, and
114
+ resolving the constants would mean reimplementing enough of the emitter to
115
+ guess what it emits. The built module cannot be introspected either:
116
+ `program.parseAsync()` runs at module scope and `program` is never exported,
117
+ so importing `dist/cli/index.js` runs the CLI instead of describing it. What
118
+ is left is the artifact a consumer actually reads, and it is the right ground
119
+ truth in both directions — a flag that works and is absent from the help is
120
+ not on the published surface, and one documented there is, whatever the source
121
+ says.
122
+
123
+ **commander's `-h, --help`, `-V, --version` and `help` command are excluded,
124
+ and matched by name rather than by shape.** They are not this project's to
125
+ freeze, and a gate that reddens because a dependency reworded its own built-in
126
+ is red for a change nobody here decided — the shape this repository refused
127
+ once already, when it rejected a freshness gate that could fail a fresh clone
128
+ on somebody else's release. Matching them by long name is what keeps the
129
+ exclusion from hiding one of ours: `-h, --help` and a hypothetical `-j,
130
+ --json` are the same shape, so a shape test would silently drop a real flag
131
+ the day anybody adds a short one.
132
+
133
+ The two rosters are asserted apart because they are separately falsifiable: a
134
+ command list that is exactly right can belong to commands whose flags have all
135
+ been renamed, and every flag can be correct on a command that should not
136
+ exist. Measured — perturbing the CLI with one added command *and* one added
137
+ flag failed both scenarios, while an added command alone would have failed
138
+ only the first, since the option scenario iterates the registry's list and
139
+ would never have visited it. Eight child processes, one per help page,
140
+ spawning `process.execPath` with `bin/attest.js` rather than the installed
141
+ shim, so no shell is involved on either platform; both scenarios carry an
142
+ explicit `timeoutMs` because the CLI's start-up cost puts the option scenario
143
+ over Vitest's 5 s default.
144
+
145
+ No engine code changed, no command or flag moved, and no `--json` field or
146
+ issue `code` was added: this is a self-requirement and a spec file.
147
+
148
+ - **`ATX-86` gates the shape of what `.` exports, ahead of the release that
149
+ freezes it.** Nothing in the suite enumerated the public surface, and the
150
+ three things that looked as though they did each answered a different
151
+ question: `tests/consumer.spec.ts` asserts that every specifier in the
152
+ `exports` map *resolves*, which is reachability rather than shape;
153
+ `tests/smoke.spec.ts` asserts four runtime exports are functions, and is
154
+ silent about the eleven type-only names beside them and about an export
155
+ arriving that nobody intended; and the four `*.spec-d.ts` files pin the
156
+ behaviour of particular types against `src/` rather than against the emitted
157
+ declarations. So dropping a type export, or widening `Issue.code` back to
158
+ `string`, broke nothing a contributor runs — on the surface `What 1.0 freezes`
159
+ promises not to move.
160
+
161
+ **Stated over the built package and attested over its two artifacts
162
+ separately**, which is `ATX-79`'s shape and is not an analogy to it. `export
163
+ type` is erased whole at emit, so `Issue` is a name in `dist/index.d.ts` and is
164
+ absent from `dist/index.js`, and a consumer importing it as a value gets
165
+ nothing. That asymmetry is the claim rather than an accident of it, and it is
166
+ invisible to any walk over `src/index.ts`, where both kinds are one export
167
+ list. The declared lists live in `params`, so each scenario compares the
168
+ registry against the artifact and the two terms are independent in both
169
+ directions: a barrel the registry does not follow goes red, and so does the
170
+ reverse.
171
+
172
+ **What it does not close, stated here rather than found later.** An edit that
173
+ removes a name from the barrel *and* the registry moves both terms together
174
+ and stays green. That is a deliberate registry edit, visible as a `SPEC.md`
175
+ diff under `render --check`, and review is the gate for it — the same answer
176
+ the foot of this file reaches for a param whose value moves. The count pinned
177
+ beside each comparison is the independent term against the careless half of
178
+ that case and is not a claim to have closed it.
179
+
180
+ No engine code changed, no command or flag moved, and no `--json` field or
181
+ issue `code` was added: this is a self-requirement and a spec file. The entry
182
+ that proposed it was filed under `Under consideration` on 2026-09-01 from a
183
+ comparative read of `nostics` 1.2.0, promoted to `Planned` the same day once
184
+ re-measuring corrected its cost, and the correction is the reason it was
185
+ buildable immediately — it had priced a build coupling that `self/` already
186
+ carries, having considered only `tests/` and the consumer suite as homes.
187
+
188
+ ### Changed
189
+
190
+ - **`uncovered-requirement` stops advertising an affordance that has never
191
+ existed.** The message read *"Add a scenario, or mark it deferred"*, and
192
+ `deferred` appears nowhere else in the project: not a field of
193
+ `RequirementSchema`, not a flag on any command, not a key in the report. The
194
+ troubleshooting section for the code quoted the message as its symptom and
195
+ then explained only the first half, because there is no second half to
196
+ explain. So the one place a reader looks when they hit this error named an
197
+ escape hatch, in shipped output, in every release to date. It now names the
198
+ two exits that exist — write a scenario, or delete the entry — and
199
+ troubleshooting says why there is no third: an id Attest holds is an
200
+ obligation it holds permanently.
201
+
202
+ **The trace of the affordance having been intended is kept rather than
203
+ deleted**, which is why this waited. `Considered and rejected` gains the
204
+ entry that found it (*Retiring a requirement*), and design §11 gains the
205
+ permanence as a property to design against rather than a gap awaiting a
206
+ mechanism. Rewording the message alone would have removed the only written
207
+ evidence that anybody ever meant to build the thing.
208
+
209
+ `Issue.message` is prose and not API, so no consumer branches on this and no
210
+ exit code moves; there is no `code`, `level` or `--json` field change and
211
+ nothing to bump.
212
+
213
+ - **1.0: the CLI and `.` are frozen, and the freeze ships with the gates that
214
+ enforce it.** The line itself is at the head of this file under `What 1.0
215
+ freezes` — a stability promise a reader cannot find is not one — and this is
216
+ the release that makes it binding. Decided 2026-08-31, cut 2026-09-01.
217
+
218
+ **Frozen: the CLI — its commands, options and exit codes — and the root export
219
+ `.`.** `./define` is frozen with them, on seven releases. Both are frozen on
220
+ evidence that they had stopped moving rather than on intention: the CLI's last
221
+ change of shape was `--apply` on 2026-08-03, twelve releases before the
222
+ decision, and `src/index.ts` has had its export list edited in three commits
223
+ ever.
224
+
225
+ **Not frozen, explicitly: `./inspect`.** It shipped in `[0.9.2]` with two
226
+ releases and one adapter behind it, and freezing on one adapter is the
227
+ objection this project raises against every premature interface and does not
228
+ get to waive for its own. Stating the exclusion is the point: a version number
229
+ alone would have said the opposite.
230
+
231
+ **What the freeze costs is stated rather than discovered later.** Under 1.0 a
232
+ narrowing that closes a security hole takes a major, on whatever schedule a
233
+ hole is found. `[0.11.0]` is what made that price knowable — it recorded that
234
+ such a narrowing takes the bump its definition gives it, and that the
235
+ `callsAuthoringFn` fix would have been a 2.0 twenty-five days after a 1.0 cut
236
+ at the fourth breaking commit.
237
+
238
+ **The line was decided with neither frozen surface enforced by anything, and
239
+ that is no longer true.** A frozen surface with no gate on its shape is a
240
+ promise nothing enforces, and the measurement behind the decision recorded when
241
+ each surface last *moved*, never what would notice if it moved again. `ATX-86`
242
+ and `ATX-87` in this release close both halves: the eleven type-only names and
243
+ four values of the root export, read from the emitted declaration file and the
244
+ emitted JavaScript separately, and the seven commands and seventeen options of
245
+ the CLI, read from the help it prints. Neither existed when the line was
246
+ decided.
247
+
248
+ **The exclusion list is one item longer than the four surfaces, and the fifth
249
+ is the one that moved.** The workflow document `attest init` writes into an
250
+ adopter's repository is deliberately not frozen — it is a function of the
251
+ installed version, so freezing it would freeze the workflow the tool teaches.
252
+ A 0.9.1 → 0.11.0 upgrade report measured it at **110 changed lines over 9
253
+ hunks**, still stating a `never-red` ordering rule `[0.10.0]` withdrew, with
254
+ four gates green and nothing reporting it. What 1.0 does **not** do is make
255
+ that drift detectable; the question is open at the foot of this file, and the
256
+ release is explicit about it rather than quiet.
257
+
258
+ **1.0 settles what an adopter may build on and unblocks nothing else.** The
259
+ judgement schema and any retrieval surface over the archive stay under `Under
260
+ consideration` and are explicitly 1.x. The one adoption crossing both of
261
+ `[0.11.0]`'s breaking changes paid nothing — no file edited, no peer upgrade,
262
+ no gate going red — but that sample dodged both narrowings, so what is measured
263
+ is that the release cost a *conforming* adopter zero, not that either narrowing
264
+ is cheap. And what a release costs its adopters is not known on the day it is
265
+ cut, which the foot of this file records as a general rule and which applies to
266
+ this one: the freeze is cut on the strength of the line being **correct**, not
267
+ on evidence that it is **sufficient**.
268
+
38
269
  ## [0.11.0] - 2026-08-28
39
270
 
40
271
  ### Added
@@ -985,7 +1216,7 @@ paragraph exists to make visible.
985
1216
  one scenario asserting the *discrimination* rather than two asserting each
986
1217
  side, which fails now and still refuses an implementation that warns
987
1218
  unconditionally later. This is the mechanism catching the exact defect class
988
- the tautology entry under *Under consideration* is about, on the first change
1219
+ the tautology entry at the foot of this file is about, on the first change
989
1220
  it was ever pointed at, in the author's own work.
990
1221
 
991
1222
  *A second defect surfaced after `--apply` and is worth recording because
@@ -4579,7 +4810,8 @@ field moved.
4579
4810
  the record would not need to exist at all. So it is the one thing Attest writes
4580
4811
  that cannot be checked by regenerating it, and what stands in for that check is
4581
4812
  a reviewer. (The workflow document has no gate either, but that is a decision —
4582
- `init --check` was rejected, see below not an impossibility.)
4813
+ `init --check` was rejected, and has since been reopened at the foot of this
4814
+ file — not an impossibility.)
4583
4815
  Landed in `troubleshooting.md#never-red` and under `attest archive` in the CLI
4584
4816
  reference (both languages), and in the workflow document `attest init` writes —
4585
4817
  where it also became a fourth entry in "things you must not do", beside editing
@@ -4791,9 +5023,10 @@ field moved.
4791
5023
  the first fix written: it makes both maps resolve, at 195 kB and 148 files, to
4792
5024
  serve stepping into the engine from an adopter's `node_modules` — which
4793
5025
  nothing has asked for, and which anyone auditing the reader would do from the
4794
- repository instead. This file has rejected `init --check`, `divergent-param`
4795
- and countable obligations for costing more than a demonstrated need, and that
4796
- standard does not bend for a packaging decision. Turning the two compiler
5026
+ repository instead. This file has rejected `divergent-param` and countable
5027
+ obligations for costing more than a demonstrated need and rejected
5028
+ `init --check` on the same standard, an entry since reopened on measured
5029
+ drift — and that standard does not bend for a packaging decision. Turning the two compiler
4797
5030
  options off is the cleanest published output, since it leaves no dangling
4798
5031
  `sourceMappingURL` comment, but it also removes the maps from `dist/` here,
4799
5032
  where `verify self` runs through the built CLI and they are the only thing
@@ -5389,9 +5622,10 @@ every platform, which is what makes them work on Windows at all.
5389
5622
  than assumed: there isn't one, and that is the design.** A committed
5390
5623
  instruction file is a function of the installed version, so it does go stale on
5391
5624
  upgrade — but staleness only *costs* anything if the tool also ships a gate to
5392
- turn red about it, and `init --check` is rejected on its own terms under
5393
- "Considered and rejected", together with the exposure that leaves and the
5394
- backstop that keeps it small. What belongs here is what the absence of a gate
5625
+ turn red about it, and `init --check` was rejected on its own terms, together
5626
+ with the exposure that leaves and the backstop that keeps it small — an entry
5627
+ since reopened at the foot of this file, on the first measurement of that
5628
+ exposure and the failure of that backstop. What belongs here is what the absence of a gate
5395
5629
  bought: with none to serve there is no reason to keep the prose out of the file
5396
5630
  either, so it lives in the skill, where an agent reads it with no extra call,
5397
5631
  and a skill body is loaded on demand — a long document costs context only when
@@ -6272,6 +6506,211 @@ re-measuring an entry before building it is at the foot of the file, under
6272
6506
  `How an entry is re-measured` — it applies to all three, and was filed here only
6273
6507
  because the first of it was written here.
6274
6508
 
6509
+ - **An outward-facing form of the judgement tables, so an adopter can run this
6510
+ counter-pressure on their own registry.**
6511
+ `tests/intent-rule-candidates.spec.ts` maintains four hand-judged tables over
6512
+ this repo's registry and enforces their completeness. Design §11 records the
6513
+ properties they defend and that nothing enforces any of them; what the tables
6514
+ add is a counter-pressure holding for **one** registry whose authors agreed to
6515
+ maintain it. An adopter got none of it, and the reason was sharper
6516
+ than "nobody wrote it for them": the package exported `requirement`,
6517
+ `scenario`, `defineRequirements`, `delta` and types, and nothing else.
6518
+ `parseSpecs`, `findFiles` and `readRegistrySource` were internal, so an
6519
+ adopter **could not enumerate their own scenario names** — the table's central
6520
+ rot check, that a claimed scenario is one a spec really declares, was not
6521
+ merely unwritten downstream but unreachable. **That half shipped under
6522
+ `[0.9.2]` as `@am_shork/attest/inspect`** and is recorded at the foot of
6523
+ this entry; what stays here is the schema built on top of it, and the three
6524
+ blockers below are untouched by the export.
6525
+ *The shape that makes it shippable at all is a split, not a feature.* The
6526
+ engine can compute which ids exist, which statements match the universal
6527
+ quantifier regex, which params are list-valued, and whether a named scenario
6528
+ is declared — all four already, all statically. It cannot compute how many
6529
+ obligations a requirement carries, what a quantifier ranges over, or whether a
6530
+ scenario iterates a param. So the shippable object is **the enforcement, never
6531
+ the judgement**: an opt-in `*.judge.ts` read by the same static reader and
6532
+ under the same literal-only rule the registry is (it is metadata about
6533
+ requirements under review, so `check` must execute nothing to read it), whose
6534
+ contents are entirely the adopter's. Opting in is creating the file; once it
6535
+ exists, `requirement-unjudged`, `orphan-judgement`, `unbound-site` and
6536
+ `quantifier-unjudged` make it complete and keep it from rotting. The
6537
+ asymmetry is the only way this can exist without Attest asserting a ground
6538
+ truth it refuses to compute (§0), and it is the same asymmetry `never-red`
6539
+ already runs on: the archive gate would owe judgement only for ids a change
6540
+ ADDs, so a brownfield adoption is not blocked wholesale.
6541
+ *One part of the existing table cannot ship in any form.* `DOMAINS.pin` comes
6542
+ in two kinds — a scenario name, checkable against the AST, and a **source-text
6543
+ fragment** grepped out of the spec file. The second is asserting on the
6544
+ wording of somebody's test, which is the `Issue.message` category error under
6545
+ another name. Only the scenario-name kind is exportable, and what that costs
6546
+ is real: the grep is what caught a deleted pin that `toContain` had passed.
6547
+ *Why no detector ships with it, and this is the part that is already
6548
+ measured.* Three rule families were built and scored against the whole corpus
6549
+ (86 entries, counting a fixture id once per registry file) before any was
6550
+ written into the engine, which is the order `divergent-param` was originally
6551
+ designed in the wrong way round. Every figure below is a quotation of the
6552
+ suite's inline snapshots; the snapshot is the fresh copy, and this paragraph
6553
+ is only ever as fresh as its last edit.
6554
+ **(1) `compound-requirement`** — 34 requirements carry more than one
6555
+ obligation, it flags 16, is **wrong about none**, and misses 18. Never wrong
6556
+ and nearly blind, and the 18 are the form the coverage incentive actually
6557
+ rewards: extra clauses under a single keyword. **(2) The widened variant** —
6558
+ 29 correct, but 14 wrong and still 5 missed, and it is silenced by deleting an
6559
+ "and". A rule whose cheapest fix degrades the artifact it protects does not
6560
+ ship, and its precision falls as the registry grows. **(3) `divergent-param`**
6561
+ — 5 divergences on this repo, **all 5 judged wrong**, zero true positives ever
6562
+ recorded. The `code` group now spans 19 ids, and the failure is structural
6563
+ rather than a matter of naming: the key is built by discarding the evidence of
6564
+ relatedness, so the one genuinely paired divergence in the group is the one it
6565
+ cannot show, and the key has churned three times under `flag` on additions
6566
+ that changed no verdict. A fourth candidate — the echo detector, scored at
6567
+ half precision — belongs to the tautology-detector entry now under
6568
+ *Considered and rejected* and is not re-quoted here, because a figure stated in two places goes stale in one. Taken together
6569
+ these are the measured range of what a token or single-body detector over
6570
+ prose can do here, and they are why the proposal is enforcement-only.
6571
+ *What blocks the decision — three things.* **(1) One adapter.** This
6572
+ repository's table is the only one that exists, and its author wrote both the
6573
+ schema and every answer in it. By this project's own rule a second table is
6574
+ what makes the seam real, and freezing a `Judgement` type now is deciding for
6575
+ everyone from 34 requirements written by one person. **(2) The breaking form
6576
+ of this was already rejected** — see *Countable obligations* below — and one
6577
+ of its three reasons survives the separate-file shape intact: **nothing can
6578
+ force the count to be true.** The other two do not survive, which is what is
6579
+ new here: a separate artifact touches no `--json` field, no delta apply and no
6580
+ `render` output, and completeness *across ids* is computable in a way
6581
+ completeness *within* an obligation array never was. So this is a re-proposal
6582
+ with one of three objections answered and the sharpest one standing.
6583
+ **(3) `quantifier-unjudged` reuses a rejected detector as its trigger.**
6584
+ Firing on the quantifier token was rejected below at 43% reach and 70% wrong;
6585
+ the claim here is that the same regex is acceptable when it asks a question an
6586
+ author discharges in one line (`over: 'values'`) rather than issuing a
6587
+ verdict. That claim is untested on anyone who did not also write the regex.
6588
+ *The half that is not blocked has shipped, under `[0.9.2]`.*
6589
+ `@am_shork/attest/inspect` is one function answering what a project declares —
6590
+ AST-only, no runner peer, landing on the existing static/executing boundary.
6591
+ It ships no schema and decides nothing, which is why it could go ahead of the
6592
+ three blockers rather than waiting on them. What it changes about this entry
6593
+ is the *kind* of thing that is now missing: the mechanism is no longer
6594
+ unreachable, only unwritten, so a second table is something an adopter can
6595
+ build rather than something only this repository could.
6596
+ *What would decide it:* one judgement table maintained by someone who did not
6597
+ design it. If it comes back the same shape, the seam is real and the schema
6598
+ follows; if it comes back different, `inspect` was the whole answer and the
6599
+ schema never needs to exist. *(One came back on 2026-08-31, and the disjunction
6600
+ above is too clean to survive it: partly the same shape, partly not, and the
6601
+ same half is the contaminated half. The close of this entry is where that is
6602
+ read.)*
6603
+ **Nothing currently makes that table arrive, which is a separate problem from
6604
+ the three blockers and the one that gates all of them.** Raised in a product
6605
+ review of `[0.9.1]`–`[0.9.3]` on 2026-08-19. The deciding evidence is an
6606
+ artifact only an adopter can produce, and the path to producing it is: a row in
6607
+ the README's document table, then a 214-line page, then a hand pass over four
6608
+ questions, then writing the two mechanical properties from a prose
6609
+ description, then choosing to report back. Five voluntary steps with no
6610
+ scaffold at any of them, against a decision this entry has been waiting on
6611
+ since `[0.9.2]`. An entry blocked on evidence that no shipped surface asks for
6612
+ is blocked indefinitely, and the wait is not itself evidence of anything.
6613
+ *The proposal is scaffolding the mechanism while still withholding the answer,*
6614
+ which is the same split `[0.9.2]` already made and not a retreat from it: an
6615
+ `examples/` directory holding the completeness and rot-binding assertions with
6616
+ **no columns** — the enumeration that forces a row, and the check that a named
6617
+ scenario is one `inspectProject` really declares — and the judgement columns
6618
+ left for the adopter to add. What the page withholds is this repository's four
6619
+ columns, and that stays withheld; what it currently also withholds is the two
6620
+ properties it names as *mechanical*, and those are the half where a copy is not
6621
+ an echo, because their content is fixed and the fourth finding does not apply
6622
+ to them.
6623
+ *What blocks it.* Whether a skeleton with no columns is still a shape that
6624
+ comes back as a copy — the page's argument is that a template makes the answer
6625
+ return its own question, and it is untested whether an adopter reads an empty
6626
+ frame as an invitation or as a form to fill in. And it is a second artifact
6627
+ claiming to be runnable consumer usage of the public API, which `pre-commit-check`
6628
+ §6 already records as the category no gate reaches: the page's three samples
6629
+ are unasserted today, and a fourth in a directory that looks executable would
6630
+ be worse than a fenced block, not better, unless something runs it.
6631
+ **Built under `[0.11.0]`, and the second of those two blockers was the
6632
+ buildable one.** `examples/judgement-table/` is in the default suite, the
6633
+ specifier is aliased rather than relativised so the file stays copyable
6634
+ verbatim, and both assertions were shown to fail before it was called done.
6635
+ So the category `pre-commit-check` §6 names now has exactly one member that a
6636
+ gate reaches, and the page's three prose samples are still not it.
6637
+ *Two things the build found that the proposal did not contain.* A third
6638
+ assertion — `issues` empty, before either of the other two — which is not a
6639
+ refinement but a hole: an unread registry file and a smaller project are the
6640
+ same value, and the two completeness directions pass over both. It was found
6641
+ by an invalid mutation during the bite test rather than by reasoning, which is
6642
+ `How an entry is re-measured`'s question about what a repair makes reachable,
6643
+ arriving from the other end. And a **type-level** forcing move the runtime
6644
+ assertion cannot make: a required field added to `Judgement` fails every
6645
+ existing row at compile time, so the schema-shaped half of "a row cannot
6646
+ arrive unjudged" costs one line and no engine rule. That is worth noting
6647
+ against blocker **(1)** above, which is about freezing a `Judgement` type:
6648
+ what shipped freezes nothing, because the type is in the adopter's copy.
6649
+ **What is untouched is the first blocker, and it is now the only thing between
6650
+ this entry and its evidence.** Whether an empty frame reads as an invitation
6651
+ or as a form to fill in is still untested, and cannot be tested here — the
6652
+ people who would answer it are the ones who have not arrived. What the build
6653
+ changes is only that the five voluntary steps are now four, and the one
6654
+ removed was the one with a fixed answer. If the next report is still nobody,
6655
+ the scaffold was not what was missing, and that is worth knowing too.
6656
+ **Decided 2026-08-31: build the counting rule, and not the schema.** The
6657
+ blocker was whether an empty frame reads as an invitation or as a form to fill
6658
+ in, it could not be tested here, and a 0.9.1 → 0.11.0 upgrade report from
6659
+ `mine-capablanca` tested it. The answer is **invitation**, and the report names
6660
+ the sentence that did it: not the empty `interface Judgement`, which reads as a
6661
+ stub and sent the reporter looking for what belonged in it, but the comment
6662
+ above it — *"It ships with no columns, and that is the finished state of this
6663
+ file rather than a gap in it."* Declaring the absence finished is what turns a
6664
+ blank into a question, and it is the cheapest sentence in the example.
6665
+ *The forcing move was one the build found rather than one it proposed.* The
6666
+ instruction to add the first column as a **required** field made all 80 rows
6667
+ stop compiling at once, and there was then no path back to green that did not
6668
+ go through having an opinion about all 80 — *"a form gets partially filled in;
6669
+ a type does not"*. That is the type-level forcing move described above,
6670
+ working on somebody who did not write it.
6671
+ *And the frame leaks in one place, which is a defect in the example rather
6672
+ than in the idea.* `pin` is documented as not a judgement column and is the
6673
+ only concrete field in the interface, so the reporter's first act was to
6674
+ populate it for all 80 rows mechanically — *"there is a field, populate it"*.
6675
+ Defensible, since it makes rot-binding bite for 80 rows instead of none, and
6676
+ exactly the form-filling behaviour the frame exists to prevent. Generating
6677
+ `pin` from `inspectProject` rather than leaving a field to be typed is the fix,
6678
+ and it belongs in the example.
6679
+ *What does **not** follow, and the report is why it does not.* Two of the three
6680
+ columns that came back are the same shape as two of this repository's —
6681
+ `clauses` is `OBLIGATIONS`, `quantifier` is a flattened `QUANTIFIED` — and it
6682
+ is tempting to read that as the seam being real. It cannot be read that way:
6683
+ the reporter names the `attest init` workflow document as prior influence, and
6684
+ that document discusses quantifiers and param pinning at length. So the
6685
+ convergence says **something** transmits those two ideas, and the document is
6686
+ at least as likely a channel as the frame. What is uncontaminated is the third
6687
+ column, `venue`, which has no counterpart here: nine of its rows record a
6688
+ promise held by a Playwright suite Attest's scope does not reach. A column this
6689
+ repository could not have written came back, which is a seam behaving like one.
6690
+ *What to build is smaller than a schema, and the three things the reporter
6691
+ wanted and did not get are its specification.* **(1) A counting rule for
6692
+ "obligation" precise enough that two people count the same.** The report
6693
+ distrusts its own `clauses` column above all the others and says why: two
6694
+ tables that count differently are not comparable, which defeats the point of
6695
+ there being a second one. A counting rule is content that does **not** depend
6696
+ on the adopter — the same category as the two mechanical assertions — and it is
6697
+ currently withheld along with the columns, which is the wrong side of the
6698
+ split. **(2) A way to bind a clause to a scenario rather than a row.**
6699
+ Rot-binding binds a row to *a* scenario; nothing binds the sixth obligation of
6700
+ a six-clause requirement to anything, so a six-clauses-three-scenarios row is a
6701
+ number a reader has to notice instead of an assertion that fails. **(3)
6702
+ Somewhere for a row to say the promise is held where Attest cannot see it.**
6703
+ Nine `ui-node` rows and a 902-line Playwright suite, citable only in prose.
6704
+ *So what is planned is (1) and the `pin` fix, and (2) and (3) explicitly are
6705
+ not.* (1) is a paragraph in the example, costs no engine surface, and is the
6706
+ one thing that makes a second table comparable to the first — without it two
6707
+ tables are two vocabularies and the seam cannot be read either way. (2) and (3)
6708
+ are both new schema, and blocker **(1)** of the proposal above — one adapter —
6709
+ is not answered by a report that is the second adapter's first hour. Two hours
6710
+ of one agent is not two adapters. **The `*.judge.ts` schema stays undecided**,
6711
+ and when it moves it will move on a second table that somebody has maintained
6712
+ rather than built, which is a different measurement from the one that arrived.
6713
+
6275
6714
  ## Under consideration
6276
6715
 
6277
6716
  Candidates with **no decision yet**, between the two sections either side of it
@@ -6284,317 +6723,6 @@ deletion. What each entry has to carry is the evidence currently blocking the
6284
6723
  decision, because that is what a re-proposal a year from now would otherwise
6285
6724
  have to gather again.
6286
6725
 
6287
- - **An assertion that recomputes its expectation from the param the code under
6288
- test just read is a tautology, and nothing says so.** Reported by
6289
- `mine-capablanca`, which probed it by mutating the registry and running the
6290
- full gate on each mutation:
6291
-
6292
- | mutation | who reads it | `check` | `verify` |
6293
- |---|---|---|---|
6294
- | `GEN-1.maxCandidatesPerPiece` 64 → 4 | scenario only | ok | **red** |
6295
- | `BOARD-1.boardSize` 10 → 12 | app + fixtures | ok | **red** |
6296
- | `BOARD-2.maxMines` 12 → **40** | app + assertion | ok | **green** |
6297
- | `BOARD-2.minMines` 9 → **1** | app + assertion | ok | **green** |
6298
-
6299
- The mine-density band — the one number deciding whether that game is playable —
6300
- moved by 3.3× with the whole suite green, `check` silent, and `possible-drift`
6301
- correctly not firing, because a covering scenario *does* read the param.
6302
- **Read the third and fourth rows against the second, because that is the
6303
- finding.** The reporter's diagnosis was "app and spec both read it", but row 2
6304
- is app-and-spec and goes red. What distinguishes the green rows is narrower and
6305
- more useful: the assertion **derives its expectation from the same param the
6306
- code under test used**, so both sides move together and the comparison has no
6307
- independent term. Row 2's expectation lives in fixtures, which do not move when
6308
- the param does, and the mechanism works. So the failing condition is a property
6309
- of the assertion, not of who reads the value — which matters, because it means
6310
- the answer is a rule about how to write the assertion, and *not* the detector
6311
- the reporter proposed.
6312
- **What this falsifies is written in this file.** The rejection of a committed
6313
- params snapshot, below, closes on *"the mechanism that guards a param is a
6314
- scenario reading it, not a file recording it"* — a sufficiency claim, and rows
6315
- 3 and 4 are counterexamples to it. A scenario reading the param is necessary
6316
- and not sufficient. That entry stays rejected on its own argument, which was
6317
- never about detection power, but its fallback clause is now known incomplete
6318
- and is annotated accordingly. The README's §"killer move" has the same shape:
6319
- it says noticing a param *moved* is `verify`'s job "only when a scenario asserts
6320
- on the value it read from `params`" — literally a necessary condition, and read
6321
- by an adopter as a sufficient one, with the very next line (*"Read the param
6322
- inside the assertion, not beside it"*) being the instruction that produces the
6323
- tautology.
6324
- **The framing that probably resolves it: this is `never-red` wearing different
6325
- clothes.** A tautological assertion is a scenario that cannot fail, which is
6326
- the exact defect `archive`'s `never-red` gate exists to catch — and it did
6327
- catch two of them in this very repo (`CHECK-2`, `FOG-3`, both confirmed
6328
- correct by the reporter). The reason it did not catch these is structural:
6329
- **changing the value of an existing param goes through no gate at all.**
6330
- `archive` gates a change; a param edit is not a change. That is a cleaner
6331
- statement of the gap than "params can be tautological".
6332
- *Re-read against `evidence-not-ordering`, which separated that gate's
6333
- obligation from the ordering it used to be described by.* Nothing here moves:
6334
- the blocker was never that the observation had to be taken first, it is that a
6335
- param edit produces no change folder for any observation to be recorded
6336
- against. If anything the framing gets sharper — what `never-red` demands is
6337
- evidence that a scenario can fail, which is precisely what is missing for a
6338
- tautological assertion, and the reason it goes undemanded is still the absence
6339
- of a gate rather than the absence of a route to the evidence.
6340
- **That observation is now its own entry at the foot of this section**, split
6341
- out and measured on 2026-08-18, because it asks what happens when a value
6342
- moves rather than whether an assertion is a real comparison, and neither
6343
- answer settles the other. What stays here is the detector.
6344
- *What blocks the decision — three things, all of them real.* **(1)** Every
6345
- mutation above is synthetic. Across 8 commits touching `*.reqs.ts` in that
6346
- repo, **no param value was ever modified** — every `params:` line in every diff
6347
- is an addition. So the hole is proven as a logical property and its frequency is
6348
- entirely unmeasured, from one greenfield repo. **(2)** The reporter's proposed
6349
- mechanism — a WARNING when one param id is read from both a spec and a non-spec
6350
- file — is the wrong shape twice over: it fires on row 2, which is correct usage
6351
- and goes red, and it would fire on the arrangement the README actively
6352
- recommends, which is §9's "usually wrong about staleness" failure mode again.
6353
- It also needs a static sweep of the whole project for `reqs['X'].params.y`,
6354
- far past the two registry readers' current surface. **(3)** No mechanism is yet
6355
- known that separates "the assertion pins this independently" from "the assertion
6356
- echoes it" without executing the scenario twice under different values — which
6357
- is mutation testing, a much larger thing to own. *One has since been built and
6358
- scored, and it does not lift this blocker.* The syntactic candidate — flag a
6359
- scenario whose expectation is derived from the param the code under test
6360
- consumed, decided inside one body without executing anything — flags 9 params
6361
- here, of which **4 are real echoes and 5 are pinned by something outside the
6362
- body it cannot see**. Under half precision, and the failure is the one
6363
- predicted above rather than a tuning problem: what pins a value is often a
6364
- fixture or a second derivation in another file, so a single-body detector is
6365
- looking in the wrong place by construction. The newest false positive is the
6366
- clearest instance — `ATX-79.subpath` is compared against `package.json`, which
6367
- is not the code under test and does not read the registry, so the independent
6368
- term is in another file entirely. Precision has fallen on every addition
6369
- measured so far, which is the direction that matters. The score is quoted from the
6370
- suite's snapshot and re-read with the judgement-table entry above, which is
6371
- where the same measurement is used for a different decision.
6372
- **The guidance half has shipped; this entry is now only about the detector.**
6373
- "Read the param inside the assertion" was insufficient and half-wrong as
6374
- written, and `[0.5.0]` replaces it everywhere it appeared — README, the
6375
- `init` skill, `possible-drift`, and design §11 — with what the evidence
6376
- supports: **the expectation must not be a function of the param the code under
6377
- test consumed**, pinned in a fixture, a literal, or a second independently
6378
- derived value. Row 2 is the worked example, and the reporter's own
6379
- `CHECK-2`/`FOG-3` fixes are the same move. It was held back once because
6380
- rewording the central claim of the tool is not a line appended; that is what
6381
- was then done, deliberately and in one pass.
6382
- **What stays undecided is the mechanism**, and all three blockers above are
6383
- untouched by the rewording: the frequency is still unmeasured from one
6384
- greenfield repo, the reporter's proposed detector is still the wrong shape
6385
- twice over, and nothing short of mutation testing separates an independent pin
6386
- from an echo. What the guidance does not do is close the hole — a tautological
6387
- assertion is still green, still silent, and still reachable — so this stays
6388
- here rather than moving to `Considered and rejected`: documenting a trap is
6389
- not the same as deciding not to detect it.
6390
- **Re-measured after `[0.7.1]` widened `params`, on this registry rather than
6391
- the reporter's — the first time this experiment has been run here.** Twelve
6392
- mutations, each followed by the whole gate. Two results matter, and they point
6393
- opposite ways.
6394
- *The shape is now reachable here, and the widening is why.* `self/render.spec.ts`
6395
- asserts `expect(md).toContain(JSON.stringify(kinds))` over the same
6396
- `payloadKinds` it feeds to the renderer — the expectation is a function of the
6397
- value under test, verbatim row 3, and it could not have been written before
6398
- the widening because the param could not be an object. The mechanism is
6399
- sharper than a frequency count and is the strongest thing this entry has
6400
- gained: **the prescribed remedy costs in proportion to the value's
6401
- complexity.** Same repo, adjacent files, same value shape — `ATX-7` pins two
6402
- numbers with literals and goes red; `ATX-63` would have had to hand-write a
6403
- rendered JSON block, so it echoed. Depth raises the defect rate and the cost
6404
- of avoiding it along one axis.
6405
- *And its cost here is nil, which is the honest other half.* Both composite
6406
- params are fixtures for the renderer and the validator; the numbers stand for
6407
- nothing, so the green is arguably correct. Blocker **(1)** is therefore only
6408
- half retired — the shape is reproduced in a second, mature repo, and the count
6409
- of *harmful* instances is still zero. Blocker **(2)** is reinforced rather than
6410
- weakened: all four green mutations were on params `src/` never reads, so the
6411
- reporter's "read from both a spec and a non-spec file" detector would have
6412
- flagged none of them. Blocker **(3)** is untouched.
6413
- *The frequency is no longer unmeasured, and it points away from building
6414
- anything.* Blocker **(1)** rested on 8 commits of one greenfield repo. Parsed
6415
- at every one of the **38** revisions that have touched this registry — all 38
6416
- readable by the current static reader, so nothing was skipped — the history is
6417
- **57 param keys added, 0 removed, and exactly 1 value changed**: `ATX-16.staticCommands`
6418
- gaining `status`. That single edit is a roster *widening*, the safe direction,
6419
- and it is on the one list that was already pinned, so it could not have been
6420
- made without updating the pin. Two independent codebases, one greenfield and
6421
- one at 64 requirements, and the event this whole entry is about has happened
6422
- zero times in either. What that argues is not that the hole is unreal — it is
6423
- reachable, demonstrated above — but that it is **rare and silent**, which is
6424
- the profile `divergent-param` was rejected on rather than the profile that
6425
- justifies an engine rule.
6426
- *The same number says the split-out fix below has also never been triggered.*
6427
- 0 removals ever means no domain has ever been shortened here. Those pins close
6428
- a live *exposure*, not a live *loss*, and the case for them is that they cost
6429
- one line each against a failure with no symptom — not that anything was
6430
- currently broken.
6431
- *Blocker **(3)** assumed mutation testing was the only separator. A cheaper
6432
- candidate existed, has now been scored, and does not survive it.* The idea was
6433
- a **syntactic** discriminator over one test body — flag an assertion whose
6434
- expected value derives from a param that also reached the arrange/act part —
6435
- which is exactly the difference between the two cases the widening produced:
6436
- `renderMarkdown({params: {payloadKinds: kinds}})` … `toContain(JSON.stringify(kinds))`
6437
- against `expect(kinds.json.maxBytes).toBe(1_048_576)`. Scored the way
6438
- `divergent-param` should have been and was not: implemented in
6439
- `tests/intent-rule-candidates.spec.ts`, run over the specs that attest this
6440
- repo, and then **every param it fires on mutated with the whole gate run —
6441
- fifteen of them, one gate run each. Four went green; eleven went red.**
6442
- *Then the detector was found to be wrong, which is the more useful half.* Its
6443
- first implementation counted an identifier in any position as a read of a
6444
- local with that name, so `i.code` in `issues.map((i) => i.code)` looked like a
6445
- use of a param named `code` — and `code` is this repo's commonest param name.
6446
- Corrected to count reference positions only, it flags **8, not 15**, and the
6447
- seven it drops are exactly seven the mutations had already shown to be pinned
6448
- by `src/`. The honest score is therefore **4 of 8, not 4 of 15** — precision
6449
- 50%, not 27%. The conclusion does not move, because it never rested on the
6450
- precision figure; what moves is how much of the original number was the
6451
- candidate failing and how much was the measurement being wrong, and that is
6452
- worth writing down rather than quietly restating.
6453
- The remaining false positives are not near-misses. They are independent terms
6454
- the detector cannot see: **the runtime is the term** — `ATX-14`'s
6455
- `inheritedKey` is `'toString'`, and a mutated spelling is not a prototype key
6456
- at all, so the behaviour under test changes; **`src/` is the term** —
6457
- `ATX-51`'s `code` is a second copy of a string the engine emits as a literal;
6458
- and **arithmetic is the term** — `threshold + 3` builds a derived expectation
6459
- that mutation breaks.
6460
- And the four true positives fail the second test, which is the one that
6461
- decides it: **none is harmful.** `ATX-10` and `ATX-3` assert that a value was
6462
- substituted or applied, where the identity of the number is not the
6463
- obligation; `ATX-51`'s is a path prefix and `ATX-63`'s a rendering fixture.
6464
- Telling those from a mine-density band that must not move is a judgement about
6465
- whether the value *means* anything — the ground truth §0 refuses to compute
6466
- for someone else's registry. **50% precision on the shape, 0% on the harm.**
6467
- Two limits on that number, stated because a score with an unstated method is
6468
- what this file exists to prevent. The mutation has **one direction per kind**
6469
- — numbers +1, lists drop-last, strings suffixed — and direction can decide the
6470
- verdict: `ATX-40.languages` is red at 2→3 and was green at 2→1 before it was
6471
- pinned, so the true-positive count is a floor. And **recall is unmeasured**:
6472
- the eight params mutated earlier that this detector does not flag all went
6473
- red, which is eight correct negatives and not a recall figure.
6474
- The detector and its verdict table are kept in the measurement suite, with the
6475
- reach recomputed on every run and the table asserted to cover exactly what
6476
- fires — because the reach figure quoted for `compound-requirement` went stale
6477
- in this very file for want of that.
6478
- *A scoping limit on all of the above.* This repo cannot settle blocker **(2)**.
6479
- `src/` reads no param at all — every `reqs[…].params` in it is a comment or a
6480
- doc string — so params here are test vocabulary, while in the reporter's repo
6481
- they are application constants the app itself consumes. The reporter's
6482
- detector keys on exactly that difference, so it is inert here and this corpus
6483
- is evidence neither for nor against it.
6484
- *What the re-measurement did find is a different defect, and it has been split
6485
- out below* — the entry is kept whole here because the two were measured in one
6486
- pass and separating them is the finding.
6487
-
6488
- - **Whether a requirement can ever be retired, and what it costs that none can.**
6489
- The registry is at **85 requirements and 240 scenarios, 2.82 each**, and
6490
- every one is a permanent obligation: `uncovered-requirement` is an ERROR, so a
6491
- requirement without a scenario cannot exist, and nothing ever removes one.
6492
- `delta.removed` exists and `applyDelta` applies it, but `--apply` refuses to
6493
- write it back, deliberately and with the reason recorded — REMOVED "cannot say
6494
- which comments belonged to the entry it deletes". So retiring one is a manual
6495
- registry edit, which is fine as a mechanism and says nothing about whether it
6496
- is ever the right move.
6497
- *What the growth actually costs, measured on this session rather than
6498
- estimated.* One small fix took: a requirement, its scenarios, a hand-judged
6499
- row in the `OBLIGATIONS` table, two inline snapshots, a `[Unreleased]` entry,
6500
- History rows in two languages, and a regenerated `SPEC.md`. That is
6501
- proportionate for a spec framework that has to dogfood — the ceremony *is* the
6502
- product being used on itself — and it is also a number that only goes up.
6503
- *What blocks the decision, and it is not the mechanism.* It is that "retire"
6504
- has at least three meanings this file has never had to separate: a requirement
6505
- that turned out **wrong** (which should leave a record of being wrong, and
6506
- `Considered and rejected` is where that already goes); one still **true** but
6507
- no longer worth a permanently-run scenario; and one **subsumed** by a later
6508
- requirement, where the scenario should move rather than go. Only the second is
6509
- really about cost, and it is the one where deleting the intent to save the
6510
- scenario inverts what the whole tool claims — that intent outlives its tests.
6511
- Nothing here is urgent at 85. It is filed now because the moment to write the
6512
- argument down is while the reasoning is available, and because a project whose
6513
- requirement count only rises should have decided in advance what would make
6514
- one leave.
6515
- *The figures above were restated on 2026-08-08, and how they were wrong is
6516
- itself the section's own warning happening.* As filed on 2026-08-03 this entry
6517
- said "62 requirements and 160 scenarios, roughly 2.6 each" while closing on
6518
- "the number is 61" — an inconsistency inside one entry, and the closing half
6519
- was the right one. Re-measured at the entry's own commit with the current
6520
- parser: **61 requirements, 156 scenarios**, so both opening figures were wrong
6521
- on the day they were written, and only the ratio survived. Measured the same
6522
- way today it is 69 and 192, and the method is the same one `verify self`
6523
- prints — the control is that today's `self/` counted this way returns exactly
6524
- what the runner does, which is what makes the 2026-08-03 number trustworthy. The
6525
- cheap way — `grep -c 'scenario('` — answers 211 against today's true 192, and
6526
- is what would have been reached for: comments and prose count, which is the
6527
- `ATX-15` error again.
6528
- **What moved in five days sharpens the argument rather than dating it.** +8
6529
- requirements and +36 scenarios, concentrated in three sessions rather than
6530
- accrued at a rate, so nothing here is a trend line. The part worth keeping is
6531
- the ratio: 2.56 → 2.78. The cost of a requirement is not flat and it is not
6532
- falling, which is the premise the whole entry rests on, and it is the first
6533
- time this file has had two measurements of it to compare.
6534
- *A third measurement, on 2026-08-18, and it points the other way from the
6535
- second.* **79 requirements and 221 scenarios**, counted the way `verify self`
6536
- prints them, which is the control the 2026-08-08 restatement established. The
6537
- ratio runs 2.56 → 2.78 → **2.80**, so the step that carried the argument was
6538
- the first one and the second is **+0.015**, which is flat. The premise this
6539
- entry rests on now has one measurement for it and one against, and the honest
6540
- statement is the narrower one: per-requirement cost has held at about 2.8
6541
- scenarios across ten days and ten requirements. What is unchanged is the
6542
- absolute total, which only rises — that half of the argument is untouched, and
6543
- it was always the half about permanence rather than about acceleration. What is
6544
- no longer supported is the acceleration. **Reopening on cost needs the ratio to
6545
- move again**; reopening on the three meanings of "retire" needs none of these
6546
- figures, and that is still the part nobody has separated.
6547
- *A fourth measurement, on 2026-08-28, and the cost half is now closed rather
6548
- than merely unsupported.* **85 requirements and 240 scenarios**, counted the
6549
- way `verify self` prints them: the ratio runs 2.56 → 2.78 → 2.80 → **2.82**.
6550
- Three consecutive readings within 0.05 of each other, across twenty-five days
6551
- and twenty-four requirements, are not a curve that is about to move. The
6552
- reopening condition this entry set itself has now failed to trigger twice, so
6553
- the cost argument is not waiting for evidence — it has been answered, in the
6554
- negative, by its own test. What survives untouched is the half that was never
6555
- about the ratio: the absolute total only rises, and permanence is the claim.
6556
-
6557
- **The three meanings, separated — which is what the entry has been asking for
6558
- and needs none of the figures above.** They are not one question, and only one
6559
- of the three is open.
6560
-
6561
- **(1) A requirement that turned out wrong.** Not a retirement at all, and the
6562
- mechanism already exists: the record of being wrong belongs in `Considered and
6563
- rejected`, which is what that section is, and removing the id is a manual
6564
- registry edit nobody has argued against. Nothing is missing here. Filing it
6565
- under "retire" was what made the question look bigger than it is.
6566
-
6567
- **(2) A requirement still true but no longer worth a permanently-run
6568
- scenario.** The only one of the three that is about cost, and the only one with
6569
- no mechanism — and the sharpest thing this separation turned up is that the
6570
- tool has been *advertising* one. `uncovered-requirement` reads "Add a scenario,
6571
- or **mark it deferred**", and `deferred` appears nowhere else in `src/`: not a
6572
- field, not a flag, not a schema key. The troubleshooting section for that code
6573
- quotes the message as its symptom and then explains only the first half,
6574
- because there is no second half to explain. So the escape hatch this meaning
6575
- needs has been named in shipped output, and in the one place a reader looks
6576
- when they hit the error, since before anyone asked the question. That is a
6577
- defect in its own right — the message is prose and free to reword — but it is
6578
- recorded here rather than fixed there, because rewording it would delete the
6579
- only written trace that this affordance was ever intended, which is the thing
6580
- this entry exists to keep.
6581
-
6582
- **(3) A requirement subsumed by a later one**, where the scenario should move
6583
- rather than go. That is a rename, and `--apply` refuses RENAMED outright, with
6584
- the reason recorded in `splice.ts` — deleting an entry has no smaller span to
6585
- fall back to. So it is blocked downstream of a refusal nobody has proposed
6586
- lifting, which is the *same* blocker the backlink-index entry above carries.
6587
- Two open entries now rest on that one refusal, which is worth knowing: it makes
6588
- lifting it a decision with two dependents rather than one, and neither of them
6589
- is the reason it exists.
6590
-
6591
- *So the entry narrows to one meaning.* (1) is answered, (3) is somebody else's
6592
- blocker, and what is actually open is (2) — whether a requirement can stop
6593
- owing a permanently-run scenario while keeping its intent, which is the one
6594
- case where deleting the intent to save the scenario inverts what the whole tool
6595
- claims. Nothing here says it should be built; what changes is that the question
6596
- is now one question.
6597
-
6598
6726
  - **Running `verify` over a subset of the scenarios, and what a partial run may
6599
6727
  be allowed to say.** `verify` is all-or-nothing and `check` executes nothing,
6600
6728
  so there is no middle. Reported by an adoption where `verify` takes **7m33s**
@@ -6727,222 +6855,94 @@ have to gather again.
6727
6855
  and the second report of the same shape, which this measurement does not
6728
6856
  supply and does not replace.
6729
6857
 
6730
- - **An outward-facing form of the judgement tables, so an adopter can run this
6731
- counter-pressure on their own registry.**
6732
- `tests/intent-rule-candidates.spec.ts` maintains four hand-judged tables over
6733
- this repo's registry and enforces their completeness. Design §11 records the
6734
- properties they defend and that nothing enforces any of them; what the tables
6735
- add is a counter-pressure holding for **one** registry whose authors agreed to
6736
- maintain it. An adopter got none of it, and the reason was sharper
6737
- than "nobody wrote it for them": the package exported `requirement`,
6738
- `scenario`, `defineRequirements`, `delta` and types, and nothing else.
6739
- `parseSpecs`, `findFiles` and `readRegistrySource` were internal, so an
6740
- adopter **could not enumerate their own scenario names** the table's central
6741
- rot check, that a claimed scenario is one a spec really declares, was not
6742
- merely unwritten downstream but unreachable. **That half shipped under
6743
- `[0.9.2]` as `@am_shork/attest/inspect`** and is recorded at the foot of
6744
- this entry; what stays here is the schema built on top of it, and the three
6745
- blockers below are untouched by the export.
6746
- *The shape that makes it shippable at all is a split, not a feature.* The
6747
- engine can compute which ids exist, which statements match the universal
6748
- quantifier regex, which params are list-valued, and whether a named scenario
6749
- is declared all four already, all statically. It cannot compute how many
6750
- obligations a requirement carries, what a quantifier ranges over, or whether a
6751
- scenario iterates a param. So the shippable object is **the enforcement, never
6752
- the judgement**: an opt-in `*.judge.ts` read by the same static reader and
6753
- under the same literal-only rule the registry is (it is metadata about
6754
- requirements under review, so `check` must execute nothing to read it), whose
6755
- contents are entirely the adopter's. Opting in is creating the file; once it
6756
- exists, `requirement-unjudged`, `orphan-judgement`, `unbound-site` and
6757
- `quantifier-unjudged` make it complete and keep it from rotting. The
6758
- asymmetry is the only way this can exist without Attest asserting a ground
6759
- truth it refuses to compute (§0), and it is the same asymmetry `never-red`
6760
- already runs on: the archive gate would owe judgement only for ids a change
6761
- ADDs, so a brownfield adoption is not blocked wholesale.
6762
- *One part of the existing table cannot ship in any form.* `DOMAINS.pin` comes
6763
- in two kinds a scenario name, checkable against the AST, and a **source-text
6764
- fragment** grepped out of the spec file. The second is asserting on the
6765
- wording of somebody's test, which is the `Issue.message` category error under
6766
- another name. Only the scenario-name kind is exportable, and what that costs
6767
- is real: the grep is what caught a deleted pin that `toContain` had passed.
6768
- *Why no detector ships with it, and this is the part that is already
6769
- measured.* Three rule families were built and scored against the whole corpus
6770
- (86 entries, counting a fixture id once per registry file) before any was
6771
- written into the engine, which is the order `divergent-param` was originally
6772
- designed in the wrong way round. Every figure below is a quotation of the
6773
- suite's inline snapshots; the snapshot is the fresh copy, and this paragraph
6774
- is only ever as fresh as its last edit.
6775
- **(1) `compound-requirement`** 34 requirements carry more than one
6776
- obligation, it flags 16, is **wrong about none**, and misses 18. Never wrong
6777
- and nearly blind, and the 18 are the form the coverage incentive actually
6778
- rewards: extra clauses under a single keyword. **(2) The widened variant** —
6779
- 29 correct, but 14 wrong and still 5 missed, and it is silenced by deleting an
6780
- "and". A rule whose cheapest fix degrades the artifact it protects does not
6781
- ship, and its precision falls as the registry grows. **(3) `divergent-param`**
6782
- 5 divergences on this repo, **all 5 judged wrong**, zero true positives ever
6783
- recorded. The `code` group now spans 19 ids, and the failure is structural
6784
- rather than a matter of naming: the key is built by discarding the evidence of
6785
- relatedness, so the one genuinely paired divergence in the group is the one it
6786
- cannot show, and the key has churned three times under `flag` on additions
6787
- that changed no verdict. A fourth candidate the echo detector, scored at
6788
- half precision belongs to the tautology entry above and is not re-quoted
6789
- here, because a figure stated in two places goes stale in one. Taken together
6790
- these are the measured range of what a token or single-body detector over
6791
- prose can do here, and they are why the proposal is enforcement-only.
6792
- *What blocks the decision three things.* **(1) One adapter.** This
6793
- repository's table is the only one that exists, and its author wrote both the
6794
- schema and every answer in it. By this project's own rule a second table is
6795
- what makes the seam real, and freezing a `Judgement` type now is deciding for
6796
- everyone from 34 requirements written by one person. **(2) The breaking form
6797
- of this was already rejected** see *Countable obligations* below and one
6798
- of its three reasons survives the separate-file shape intact: **nothing can
6799
- force the count to be true.** The other two do not survive, which is what is
6800
- new here: a separate artifact touches no `--json` field, no delta apply and no
6801
- `render` output, and completeness *across ids* is computable in a way
6802
- completeness *within* an obligation array never was. So this is a re-proposal
6803
- with one of three objections answered and the sharpest one standing.
6804
- **(3) `quantifier-unjudged` reuses a rejected detector as its trigger.**
6805
- Firing on the quantifier token was rejected below at 43% reach and 70% wrong;
6806
- the claim here is that the same regex is acceptable when it asks a question an
6807
- author discharges in one line (`over: 'values'`) rather than issuing a
6808
- verdict. That claim is untested on anyone who did not also write the regex.
6809
- *The half that is not blocked has shipped, under `[0.9.2]`.*
6810
- `@am_shork/attest/inspect` is one function answering what a project declares —
6811
- AST-only, no runner peer, landing on the existing static/executing boundary.
6812
- It ships no schema and decides nothing, which is why it could go ahead of the
6813
- three blockers rather than waiting on them. What it changes about this entry
6814
- is the *kind* of thing that is now missing: the mechanism is no longer
6815
- unreachable, only unwritten, so a second table is something an adopter can
6816
- build rather than something only this repository could.
6817
- *What would decide it:* one judgement table maintained by someone who did not
6818
- design it. If it comes back the same shape, the seam is real and the schema
6819
- follows; if it comes back different, `inspect` was the whole answer and the
6820
- schema never needs to exist.
6821
- **Nothing currently makes that table arrive, which is a separate problem from
6822
- the three blockers and the one that gates all of them.** Raised in a product
6823
- review of `[0.9.1]`–`[0.9.3]` on 2026-08-19. The deciding evidence is an
6824
- artifact only an adopter can produce, and the path to producing it is: a row in
6825
- the README's document table, then a 214-line page, then a hand pass over four
6826
- questions, then writing the two mechanical properties from a prose
6827
- description, then choosing to report back. Five voluntary steps with no
6828
- scaffold at any of them, against a decision this entry has been waiting on
6829
- since `[0.9.2]`. An entry blocked on evidence that no shipped surface asks for
6830
- is blocked indefinitely, and the wait is not itself evidence of anything.
6831
- *The proposal is scaffolding the mechanism while still withholding the answer,*
6832
- which is the same split `[0.9.2]` already made and not a retreat from it: an
6833
- `examples/` directory holding the completeness and rot-binding assertions with
6834
- **no columns** — the enumeration that forces a row, and the check that a named
6835
- scenario is one `inspectProject` really declares — and the judgement columns
6836
- left for the adopter to add. What the page withholds is this repository's four
6837
- columns, and that stays withheld; what it currently also withholds is the two
6838
- properties it names as *mechanical*, and those are the half where a copy is not
6839
- an echo, because their content is fixed and the fourth finding does not apply
6840
- to them.
6841
- *What blocks it.* Whether a skeleton with no columns is still a shape that
6842
- comes back as a copy — the page's argument is that a template makes the answer
6843
- return its own question, and it is untested whether an adopter reads an empty
6844
- frame as an invitation or as a form to fill in. And it is a second artifact
6845
- claiming to be runnable consumer usage of the public API, which `pre-commit-check`
6846
- §6 already records as the category no gate reaches: the page's three samples
6847
- are unasserted today, and a fourth in a directory that looks executable would
6848
- be worse than a fenced block, not better, unless something runs it.
6849
- **Built under `[Unreleased]`, and the second of those two blockers was the
6850
- buildable one.** `examples/judgement-table/` is in the default suite, the
6851
- specifier is aliased rather than relativised so the file stays copyable
6852
- verbatim, and both assertions were shown to fail before it was called done.
6853
- So the category `pre-commit-check` §6 names now has exactly one member that a
6854
- gate reaches, and the page's three prose samples are still not it.
6855
- *Two things the build found that the proposal did not contain.* A third
6856
- assertion — `issues` empty, before either of the other two — which is not a
6857
- refinement but a hole: an unread registry file and a smaller project are the
6858
- same value, and the two completeness directions pass over both. It was found
6859
- by an invalid mutation during the bite test rather than by reasoning, which is
6860
- `How an entry is re-measured`'s question about what a repair makes reachable,
6861
- arriving from the other end. And a **type-level** forcing move the runtime
6862
- assertion cannot make: a required field added to `Judgement` fails every
6863
- existing row at compile time, so the schema-shaped half of "a row cannot
6864
- arrive unjudged" costs one line and no engine rule. That is worth noting
6865
- against blocker **(1)** above, which is about freezing a `Judgement` type:
6866
- what shipped freezes nothing, because the type is in the adopter's copy.
6867
- **What is untouched is the first blocker, and it is now the only thing between
6868
- this entry and its evidence.** Whether an empty frame reads as an invitation
6869
- or as a form to fill in is still untested, and cannot be tested here — the
6870
- people who would answer it are the ones who have not arrived. What the build
6871
- changes is only that the five voluntary steps are now four, and the one
6872
- removed was the one with a fixed answer. If the next report is still nobody,
6873
- the scaffold was not what was missing, and that is worth knowing too.
6874
-
6875
- - **Prefix ownership is inferred rather than declared, and the cost arrives only
6876
- when adoption succeeds.** Design §11 records that the id grammar has no level
6877
- above the requirement: the prefix is a convention, nothing allocates it, and
6878
- `duplicate-prefix` can only report a collision that already happened.
6879
- spec-kit's answer is a roadmap table — a stable id, an intent, a scope
6880
- boundary, dependencies — authored above the specs it decomposes into. That
6881
- comparison is stated here rather than referred to, because the entry that
6882
- introduced the spec-kit reading has since been built and now sits under
6883
- `[0.9.4]` as the `open` field: an entry whose evidence rests on a
6884
- neighbour is one a promotion silently empties.
6885
- *Two things have changed since that reading, and they point opposite ways.*
6886
- The proposal is **smaller** than it looked: the mechanism partly exists.
6887
- `loadRegistry` returns `prefixOwners` — first claim in sorted file order —
6888
- `merge.ts` uses it to decide which file an ADDED id belongs in, and
6889
- `apply-no-prefix-owner` already refuses a prefix nobody owns. So what is
6890
- proposed is declaration replacing inference, not a new layer. And it is
6891
- **weaker**: `/inspect` shipped in `[0.9.2]`, so the cross-repository half —
6892
- the one §11 says no command can see across — is now something an adopter
6893
- computes from two registries in about ten lines. Attest does not have to own
6894
- it to make it possible.
6895
- *What blocks the decision:* nobody has the problem. §11 states the cost
6896
- arrives "exactly when adoption succeeds: two teams sharing a registry have
6897
- someone owning prefix allocation by hand", and no such adoption has been
6898
- reported. Building the declared form now would be deciding prefix semantics
6899
- for everyone from one repository whose five prefixes — `ATX` and the four
6900
- fixture ones — are each declared by exactly one file, so the allocation
6901
- question this would answer has never been asked here.
6902
-
6903
-
6904
- - **A merged requirement carries no trace of the change that produced it, and
6905
- the rename chain is the part actually lost.** `RequirementSchema` is
6906
- `statement`, `rationale`, `params`, `outOfScope`; the splice writes those and
6907
- nothing else, so after `--apply` no field and no sidecar records which change
6908
- introduced an id, which modified it, or that one id used to be another. The
6909
- delta that knew is in `archive/`, which nothing reads. Proposed from
6910
- OpenViking's `.relations.json`: links between memories live in a
6911
- directory-level sidecar, deliberately kept out of the content, and are
6912
- rendered on demand at retrieval time (`docs/design/memory-link-design.md`).
6913
- *The argument for the sidecar over a field is in their competitor analysis
6914
- rather than their design.* The same document reads GBrain, which writes links
6915
- into the page body, and records what that costs: rename a page and every
6916
- inbound link still points at the old slug, with fuzzy matching at the read end
6917
- as the only mitigation. A `supersedes:` or `introducedBy:` field on a
6918
- requirement would be that mistake in this repository's terms — the registry is
6919
- a literal and a single source (§2), and a field naming a folder that a later
6920
- rename can move is a second source with nothing comparing it against the
6921
- first. A derived index has no such copy to go stale: it is recomputed from the
6922
- archived deltas every time it is read.
6923
- *What blocks the decision, restated on 2026-08-19 because half of what was
6924
- filed here is no longer true.* This entry was blocked on the empty archive and
6925
- on the corpus being rename-free, and only the second still holds. `archive/`
6926
- now has a real change in it — the `open` field, put through the workflow the
6927
- same day — so the store this would index exists. Its delta carries `added` and
6928
- nothing else, measured rather than assumed, so a backlink index is still
6929
- testable only against fixtures for the operation it is actually about. Note
6930
- what that says about the odds of the blocker clearing on its own: `--apply`
6931
- refuses RENAMED outright, so no change this repository archives can ever
6932
- exercise a rename until that refusal is lifted, and lifting it is a separate
6933
- decision with its own reason (`splice.ts` — deleting an entry has no smaller
6934
- span to fall back to). The blocker is therefore not "waiting for a rename to
6935
- happen"; it is downstream of a refusal nobody has proposed removing. And one
6936
- question the OpenViking reading does not answer, which is whether the chain is
6937
- wanted at all. §7 applies
6938
- `RENAMED` to a `Map<id, Requirement>` and the old id is then simply gone —
6939
- deliberately, as far as anything written says. The case for recovering it is an
6940
- external reference that outlives the rename, a ticket or a commit message
6941
- naming a retired id, and nobody has reported one.
6858
+ *The second report arrived on 2026-08-31 and it is not the second report this
6859
+ entry asked for*, which is itself the finding. A 0.9.1 → 0.11.0 upgrade report
6860
+ from `mine-capablanca` the same adoption this entry was filed on, six weeks
6861
+ later, now at 46 spec files, 170 scenarios and a `verify` median of **564
6862
+ seconds** against the 7m33s originally reported asks for nothing named here.
6863
+ It does not ask for a selector.
6864
+ *The cost claim reproduces a third time, and the speed argument is finished.*
6865
+ `check` against `verify` on that project is **3,403 ms against 564,279 ms**,
6866
+ and the reporter is careful that this is a lower bound on engine share rather
6867
+ than an estimate of it, since `check` also pays a process start `verify` pays
6868
+ once. Either way the direction is settled on a corpus eighteen times slower
6869
+ than this one: over 99% of a `verify` there is that project's own search engine
6870
+ running. A selector saves no engine work anywhere, and now on two registries.
6871
+ *The parity argument the one this entry called the strongest available —
6872
+ could not be tested there, and why it could not is a fact about it.* That
6873
+ project has **no `vitest.config.ts` and no `vite.config.ts`**, no spec
6874
+ importing through an alias, and no DOM anywhere in its specs, so `config:
6875
+ false` withholds nothing a direct run would have had. `--vitest-config` is
6876
+ passed by no script and no pipeline; it never became necessary. That does not
6877
+ refute the argument, it **bounds** it: the parity gap is real only for an
6878
+ adopter carrying a config, the population is therefore smaller than "adopters",
6879
+ and nobody has yet reported being in it.
6880
+ *What that adoption does have is a divergence, and it is not the one this entry
6881
+ predicted.* On the same tree, `npx vitest run` was 170/170 green while `attest
6882
+ verify` reported `tests-red` on **two runs out of three**. The mechanism the
6883
+ reporter reaches labelled there as the strongest available inference and not
6884
+ as an observation — is one scenario measured at **4,971 ms against Vitest's
6885
+ undeclared 5,000 ms default**: 1.3 s alone, four times slower under suite
6886
+ contention, and the only one of eleven scenarios over four seconds that
6887
+ declares no explicit `timeoutMs`. That is `CLAUDE.md`'s own timing gotcha
6888
+ arriving in somebody else's repository, where no `CLAUDE.md` carries it.
6889
+ *So the residue of this entry is a different entry.* What that reporter needed
6890
+ was not a way to run less; it was a way to learn **which** of 170 scenarios was
6891
+ red, and `verify` cannot say. That is filed separately below rather than folded
6892
+ in here, because a selector and a diagnosis are different artifacts and this
6893
+ entry has already been narrowed twice by refusing to let them merge.
6894
+ *What is left blocking this one, restated:* an adopter who carries a Vitest
6895
+ config, and whose fast loop and verdict actually diverge because of it. That is
6896
+ the whole of it. The stage-2 cost argument above is unchanged and still comes
6897
+ from this repository rather than from a user.
6898
+
6899
+ - **`verify` reports that the suite is red and cannot say which scenario, and
6900
+ the documented way to find out fails in exactly the case that needs it.**
6901
+ Filed 2026-08-31 from a 0.9.1 0.11.0 upgrade report from `mine-capablanca`.
6902
+ `verify` and `archive` run the child suite with `quiet: true` because the CLI
6903
+ renders its own report, so the whole of a failure is one `tests-red` issue with
6904
+ no file, no scenario and no assertion in the human output and in `--json`
6905
+ alike. The standing answer is to run the suite yourself under Vitest's
6906
+ reporter, which is what this repository does with `pnpm debug:self`.
6907
+ *The report is that this answer is circular where it is needed.* On that
6908
+ project `npx vitest run` was green, twice, on the same tree on which `verify`
6909
+ was red two runs in three. Running the suite yourself reproduces nothing,
6910
+ because the disagreement between the two runs **is** the failure. What the
6911
+ reporter did instead was time every scenario in the suite, compare alone
6912
+ against in-suite durations, and cross-reference which of the eleven scenarios
6913
+ over four seconds declared no timeout. That is a defensible inference and the
6914
+ report labels it as one; it is not a thing a tool should require.
6915
+ *What separates this from the entry above is that nothing here is a selector.*
6916
+ No scope, no partial verdict, no `--json` field naming what ran, no
6917
+ `SCHEMA_VERSION` question. The run is exactly the run it already is, and what
6918
+ changes is only how much of the child's output survives into the report.
6919
+ *What blocks it and the first blocker is the reason `quiet: true` is there.*
6920
+ **(1)** The CLI renders its own report, and a child suite printing its own on
6921
+ top of that is two reports disagreeing about their own format. Passing the
6922
+ child's failures through means deciding what a `tests-red` issue carries a
6923
+ file, a scenario name, a message — and each is a new field with a
6924
+ `SCHEMA_VERSION` question attached, which is the cheapest part to price first
6925
+ because it decides whether this is additive at all. **(2)** A failure message
6926
+ is somebody else's assertion text, and this project's rule is that
6927
+ `Issue.message` is prose and never API. Carrying a Vitest assertion into an
6928
+ `Issue` puts a third party's wording in the field consumers are told not to
6929
+ branch on. That is either harmless or a category error and nobody has decided
6930
+ which, and the decision is not obvious in either direction: the wording is not
6931
+ Attest's to keep stable, which is an argument both for carrying it and against.
6932
+ **(3)** The instance that produced this report may not be a diagnosis problem
6933
+ at all. A scenario 29 ms under a default it never declared is a known trap with
6934
+ a known fix, and the cheaper answers are for `verify` to name **the timeout it
6935
+ imposed** when a scenario fails on time, or for the workflow document to say
6936
+ that a scenario anywhere near the default needs an explicit `timeoutMs`. Both
6937
+ are much smaller than passing failures through, and whether either covers the
6938
+ general case is unmeasured, because n=1.
6939
+ *What would move it:* a red `verify` on an adopter's tree whose cause is not a
6940
+ timeout. Every instance anybody has is this one.
6942
6941
 
6943
6942
  - **Changing the value of an existing param passes no gate, and for seven of
6944
- them there is no record either.** Split out of the tautology entry at the
6945
- head of this section on 2026-08-18, where it was one sentence inside the
6943
+ them there is no record either.** Split out of the tautology entry that
6944
+ headed this section on 2026-08-18, and which is now under *Considered and
6945
+ rejected*, where it was one sentence inside the
6946
6946
  reframing paragraph. It is a different question: that entry asks whether an
6947
6947
  assertion is a real comparison, this one asks what happens when the number
6948
6948
  itself moves, and neither answer settles the other. `archive` gates a
@@ -6977,92 +6977,64 @@ have to gather again.
6977
6977
  here disputes that or needs a second copy of the values. The observation is
6978
6978
  narrower: the **edit** is unremarkable to every gate, which is a different
6979
6979
  claim from the values being unguarded.
6980
- *What blocks the decision and the first is the one that blocks the entry this
6981
- came from.* **(1)** Frequency is unmeasured, and the one corpus anybody has
6982
- looked at points at zero: across 8 commits touching `*.reqs.ts` in the
6983
- reporting repository, no param value was ever modified — every `params:` line
6984
- in every diff is an addition. A defect nobody has committed is a hazard rather
6985
- than a report. **(2)** No shape is obvious. A param edit is not a change, so
6986
- `archive` is the wrong place; making `check` notice one needs a previous value
6987
- to compare against, which is the rejected snapshot wearing a different hat. The
6988
- only mechanism needing no stored copy is review, and review already sees the
6989
- `SPEC.md` diff for 82 of the 89. **(3)** What is left after that is seven
6990
- params in one repository, and the right answer for the five that matter may be
6991
- nothing more than a line in `intent-quality.md` the four things no gate is
6992
- looking at already being that page's subject, and this being a fifth of the
6993
- same kind.
6994
-
6995
- - **Nothing is frozen, and the evidence this repository is waiting for requires
6996
- somebody to build on something.** Filed 2026-08-19 from a product review of
6997
- `[0.9.1]`–`[0.9.3]`. Three releases in five days each added public surface, and
6998
- as of `[0.9.3]` the CLI, `./define`, `./inspect`, the archive layout, the
6999
- judgement schema and any retrieval surface are all simultaneously open. Every
7000
- entry in this section that is blocked on an adopter the judgement table above
7001
- being the sharpest is asking someone to invest in a surface that is
7002
- documented as movable. That is a coherent thing to ask under 0.x and it is also
7003
- the reason the ask may never be answered.
7004
- *The proposal is a line rather than a milestone:* 1.0 freezes the CLI, `./define`
7005
- and `./inspect`, and everything currently blocked on an adopter's evidence
7006
- the judgement schema, the backlink index, any archive retrieval is explicitly
7007
- 1.x. What that buys is not a version number; it is the only condition under
7008
- which "build your own counter-pressure on our export" is a reasonable thing to
7009
- ask of somebody.
7010
- *What blocks it.* Freezing `./inspect` five days after shipping it is freezing
7011
- on one adapter, which is the objection this repository raises against every
7012
- other premature interface and does not get to waive for its own. And the
7013
- release-notes discipline cuts both ways here: a 1.0 is a claim about what the
7014
- cost of adoption will be, and `How an entry is re-measured` records that **what
7015
- a release costs its adopters is not known on the day it is cut**. There is no
7016
- measurement to date of anyone adopting either export.
7017
-
7018
- **Measured on 2026-08-28, before deciding anything. The one-adapter objection
7019
- splits rather than holds, and a prerequisite this entry does not name has
7020
- arrived on its own.**
7021
-
7022
- *The surface has not been moving, which is not what "three releases in five
7023
- days each added public surface" suggests.* Each entry point's last change to
7024
- its exported shape, and how many releases have been cut since without touching
7025
- it:
7026
-
7027
- | surface | last shape change | releases since |
7028
- |---|---|---|
7029
- | CLI commands and options | 2026-08-03 (`--apply`) | 12 |
7030
- | `.` the index exports | 2026-07-26 | 19 |
7031
- | `./define` public signatures | 2026-08-08 | 7 |
7032
- | `./inspect` | 2026-08-18 | 2 |
7033
-
7034
- `src/index.ts` has had its export list edited in **three commits ever**, the
7035
- last of them five weeks ago. So "freezing on one adapter" is weak for the CLI
7036
- and `.` nineteen and twelve releases of evidence that they have stopped
7037
- moving and strong exactly where this entry puts it, on `./inspect`, which
7038
- has two releases, one adapter, and whose single post-shipping change was
7039
- additive. That argues for a line this entry does not currently offer:
7040
- **freeze what has stopped moving and explicitly do not freeze `./inspect`.**
7041
-
7042
- *And the cost of any such line was demonstrated this week, unprompted.* The
7043
- Versioning section at the head of this file defines breaking to include
7044
- **rejecting previously-valid input**. There have been four breaking commits in
7045
- this project's history, the last on 2026-08-03 — and a fifth is sitting in
7046
- `[Unreleased]` as of 2026-08-28: the `callsAuthoringFn` binding fix, which is
7047
- breaking *because* it narrows what a static reader accepts, and which says so
7048
- about itself. Under 1.0 that is a **major** bump. A fix closing a wrong-answer
7049
- hole in the four commands documented as safe on an untrusted fork MR would
7050
- have had to ship as 2.0, twenty-five days after 1.0 — or not ship as written.
7051
-
7052
- *So the decision has a prerequisite, and the prerequisite left this section.*
7053
- What a narrowing that closes a security hole costs a consumer is undefined,
7054
- and it is needed whether or not 1.0 is ever cut — so it is under `Planned`
7055
- above rather than here, where it would have been blocked on everything this
7056
- entry is blocked on. Until it exists a 1.0 would be promising something whose
7057
- price nobody has computed; what it does **not** do is unblock the rest of this
7058
- entry, which turns on evidence no measurement here can supply.
7059
-
7060
- *What is still unmeasured is unchanged, and it is the whole of the rest.* No
7061
- adopter of either export, so the thing 1.0 is meant to buy — making "build
7062
- your own counter-pressure on our export" a reasonable ask — has no measurement
7063
- behind it in either direction. The `examples/` scaffold under `[Unreleased]`
7064
- is the other half of that ask and was the cheaper half. Whether the missing
7065
- half is the freeze, or was never the freeze, is what the next report answers.
6980
+ *Blocker **(1)** was answered on 2026-08-31, in the direction that opens the
6981
+ entry rather than closing it.* It read that frequency is unmeasured and that
6982
+ the one corpus anybody had looked at pointed at zero: 8 commits touching
6983
+ `*.reqs.ts` in the reporting repository, every `params:` line in every diff an
6984
+ addition. A 0.9.1 0.11.0 upgrade report from `mine-capablanca` re-ran that
6985
+ count over the **41 revisions** that have touched the same registry, evaluating
6986
+ the file at each revision and comparing key by key rather than reading diffs,
6987
+ and it reads **2 keys added, 1 removed and 7 values changed**. The 8-commit
6988
+ figure was a sample and not an answer; it is withdrawn here and in the
6989
+ tautology entry under `Considered and rejected` that quoted it.
6990
+ *One of the seven landed with nothing capable of objecting, and it is the
6991
+ general case rather than an accident of that commit.* A single commit moved
6992
+ `AI-14.sampledCandidates` from 8 to 3 and `AI-14.samplingTemperature` from 5 to
6993
+ 20, and its entire diff is a proposal, a delta, the registry and the
6994
+ regenerated `SPEC.md`. **No spec file and no implementation file.** Nine
6995
+ implementation modules in that project import the registry and read params out
6996
+ of it at runtime, so the opponent's behaviour on the easy setting changed with
6997
+ the suite green the scenarios read the same two values the implementation
6998
+ reads, so both sides moved together. `render --check` did not object and
6999
+ structurally cannot: it enforces that the rendering *moved*, never that the
7000
+ movement was intended. What caught it was a reviewer on ten lines of `SPEC.md`
7001
+ diff, with a 63-line proposal beside it arguing for exactly that move.
7002
+ *So review is the gate, and the second corpus says it holds for a reason
7003
+ nothing enforces.* That registry is **58 of 58** params interpolated into their
7004
+ own statement, against 82 of 89 92.1% here. Every param edit there moves a
7005
+ line of `SPEC.md`, which is why the commit above was visible at all, and the
7006
+ reporter is explicit that nothing makes this true: `check` is at zero issues,
7007
+ and a declared key that no statement interpolates is not a problem at any
7008
+ severity. A 100% discipline held over 41 revisions with nothing behind it is
7009
+ one careless commit away from being 98%.
7010
+ *What blocks the decision now and **(2)** has become the opposite of what it
7011
+ said.* **(2) A shape is obvious, and it is small.** `check` already reports
7012
+ `unbound-param` for a placeholder with no key; the reverse a declared key no
7013
+ statement interpolates is reported at no severity, and it is the exact static
7014
+ test for "this param's edit leaves no trace outside the line that changed". It
7015
+ needs no previous value, so it is not the rejected snapshot wearing a hat: it
7016
+ is a property of one revision. The nearest precedent is `rationale-placeholder`,
7017
+ a WARNING deliberately below `hasError` so that an existing green registry
7018
+ gains a line of output and keeps its exit code, and that is the level this would
7019
+ take. **(3) What it would fire on here is the objection.** Five of this
7020
+ repository's seven uninterpolated params are **structurally uninterpolatable** —
7021
+ `ATX-7.payloadKinds` and `ATX-63.payloadKinds` are kind → payload tables,
7022
+ `ATX-15.sortMethods`, `ATX-15.bannedApis` and `ATX-72.quotes` are lists, and
7023
+ `non-scalar-interpolation` is the diagnostic that refuses a statement carrying
7024
+ any of them. So the rule must exempt non-scalars, and what is left is
7025
+ `ATX-15.comparator` and `ATX-26.code`: two params, in one repository, that
7026
+ their author simply did not interpolate. A warning firing twice on its own
7027
+ corpus and zero times on the only other one measured is a rule looking for a
7028
+ population. And the exemption is the hole rather than a detail — the five it
7029
+ must skip are precisely the shape §2 widened the params rule to cover
7030
+ **because lists and tables are the most drift-prone constants there are**.
7031
+ *What would decide it:* an adopter whose interpolation rate is below 100% and
7032
+ whose uninterpolated params are scalars. Both corpora sit at the extremes —
7033
+ 58 of 58 there, and here the gap is almost entirely the shape the rule cannot
7034
+ see — so nobody has yet measured the population it would serve. Until then the
7035
+ answer for the five that matter may still be nothing more than a line in
7036
+ `intent-quality.md`, the four things no gate is looking at already being that
7037
+ page's subject and this being a fifth of the same kind.
7066
7038
 
7067
7039
  - **The intent layer is separable from the repository it describes, and the
7068
7040
  verification layer is not — so a third party can audit intent and never
@@ -7112,6 +7084,182 @@ have to gather again.
7112
7084
  or over-read as a behavioural claim. That is the measurement, and it is a
7113
7085
  question about the reader rather than about the code.
7114
7086
 
7087
+ - **A freshness gate on the file `attest init` writes (`init --check`).**
7088
+ Rejected 2026-08-19, and **reopened 2026-08-31 by the first measurement of the
7089
+ exposure it was rejected on.** The rejection is kept below in full because it
7090
+ is still the argument to beat, and what beat one of its two load-bearing claims
7091
+ is a 0.9.1 → 0.11.0 upgrade report from `mine-capablanca`: the committed
7092
+ `.claude/skills/attest/SKILL.md` in that repository, written by `init` at
7093
+ 0.9.1, differs from the 0.11.0 body by **9 hunks and 110 changed lines, 403
7094
+ lines against 458**. It was found only because the reporter wrote a script
7095
+ importing `workflowBody()` out of the installed build and diffed it. `check`,
7096
+ `cover`, `verify` and `render --check` were all green with the document stale.
7097
+ *The backstop is what failed, and it failed on the worst available case.* The
7098
+ rejection's second claim is that every mistake a stale document can cause is
7099
+ already a diagnostic with a fix hint — "a registry written the old way is
7100
+ `registry-not-static`", so the agent corrects itself from the report. The stale
7101
+ text here is `never-red`'s ordering rule, which `[0.10.0]` archived as
7102
+ `evidence-not-ordering` on the grounds that the ordering was never in the
7103
+ engine. An agent reading that document is told to do something the engine does
7104
+ not require. Its behaviour is more conservative, not wrong-shaped, so **no
7105
+ issue fires at any severity** — there is nothing to correct itself from,
7106
+ because nothing it does is an error. The backstop covers a stale document
7107
+ producing bad input. It does not cover a stale document producing unnecessary
7108
+ work, and the second is what six weeks of drift actually produced.
7109
+ *The first claim survives, and it is still the objection.* A gate on this file
7110
+ does turn red on a change the user did not make and is cleared by a commit
7111
+ containing no decision. Nothing in the report disputes that. What the report
7112
+ supplies is the other side of the ledger, which the rejection priced as "one
7113
+ honest and small exposure": 110 lines, over one upgrade, undetectable without
7114
+ writing a script against `dist/`.
7115
+ *And the property that makes `init` idempotent is the same one that makes the
7116
+ drift invisible.* `skillContent()` and `workflowBody()` take no argument — not
7117
+ the version, not the project — deliberately, so that re-running `init` after an
7118
+ upgrade is always safe. The consequence is that the file carries **no version
7119
+ stamp**: nothing in the adopter's repository records which version wrote it,
7120
+ and *"Re-run `attest init` after upgrading"* — which the CLI prints once, at
7121
+ `init` time, to a reader who is not upgrading — is only actionable by somebody
7122
+ who already knows they need to. A diff against the installed build is not one
7123
+ detector among several. It is the only one the file's own design permits.
7124
+ *What is undecided is the shape, and the rejection narrows it rather than being
7125
+ answered by it.* A red `init --check` in CI is the thing that was rejected, and
7126
+ the report does not rehabilitate it: it would fail a fresh clone on somebody
7127
+ else's release, which is the same standard `render --check` was held to when a
7128
+ freshness gate that calls a fresh clone stale was called worse than no gate.
7129
+ What was never proposed, and is not what the heading names, is a **notice on a
7130
+ command the adopter is already running** — the same information at the cost of
7131
+ no gate and no chore commit. Pricing that is the next step; it is not a
7132
+ decision already taken, and it needs its own answer to which command, at what
7133
+ level, and whether a WARNING nobody can clear without running `init` is a
7134
+ chore in a diagnostic's clothes after all.
7135
+ *What is still unmeasured:* whether the drift cost that reporter anything. 110
7136
+ changed lines measures the document, not the work, and the report is explicit
7137
+ that it did not run `init` and did not act on the withdrawn rule. Nobody has
7138
+ yet followed a stale instruction and paid for it, and the rejection below is
7139
+ entitled to that until somebody has.
7140
+
7141
+ The rejection, kept as it was written:
7142
+
7143
+ `render --check` is the obvious precedent and is the opposite case. A rendering
7144
+ is a function of the *user's registry*, so its staleness means the user moved
7145
+ intent without regenerating — a real signal, with the fix in their hands. The
7146
+ skill is a function of the *installed version*, so a gate on it turns red on a
7147
+ change the user did not make, and is cleared by a commit containing no decision.
7148
+ That is a chore wearing a diagnostic's clothes, and a gate that cannot be about
7149
+ anything the reader did is worse than no gate — the same standard 0.1.6 set when
7150
+ it gave `render` no formatting options at all. An earlier draft of `attest init`
7151
+ spent a second command, this gate and a second self-requirement on it. What that
7152
+ would have bought is immunity to one honest and small exposure: an old file
7153
+ describes an old workflow to an agent that trusts it. The backstop is that every
7154
+ mistake it can cause is already a diagnostic with a fix hint — a registry
7155
+ written the old way is `registry-not-static` — so the agent corrects itself from
7156
+ the report, and `init` is idempotent, which makes re-running it the entire
7157
+ update mechanism. Recorded here rather than left in the 0.3.0 entry that shipped
7158
+ `init`, because the queue above depends on it: a gate is the first thing
7159
+ proposed each time a new agent target is considered, and the answer must not
7160
+ need re-deciding per target.
7161
+
7162
+ *One locator moved with the entry.* "The queue above" was written from inside
7163
+ `Considered and rejected` and means the queue of agent targets, not the entries
7164
+ now above this one; the point it makes — that the answer must not need
7165
+ re-deciding per target — is unaffected by where the entry sits.
7166
+
7167
+ - **A `fix` field on `Issue`, derived from `code` the way `docsUrl` is.** From
7168
+ `nostics` 1.2.0 (vercel-labs), read 2026-09-01, whose `Diagnostic` carries
7169
+ `why` / `fix` / `docs` as three fields rather than one prose message. Most of
7170
+ what that library is for is already here and stricter — its docs link is
7171
+ authored per code with a `docs: false` opt-out, ours is derived from the code
7172
+ at render time and cannot be forgotten or disagreed with — so the `why`/`fix`
7173
+ split is the one half of it this file has no answer for.
7174
+ *What the roster says, and it is not what the proposal assumed.* The claim
7175
+ filed was that a fix is either buried in prose or absent. Reading all 41
7176
+ rendered messages in `docs/en/troubleshooting.md`: **11 state an action to
7177
+ take** (`uncovered-requirement`, `never-red`, `orphan-test`, `open-unresolved`
7178
+ and seven more), **5 state the constraint but not the action**
7179
+ (`registry-no-default`, `non-scalar-interpolation`, `unknown-target`,
7180
+ `compiler-unsupported`, `invalid-change-name`), and **25 state only what is
7181
+ wrong** — `registry-not-static` is the whole of "Value is not a literal.", and
7182
+ `tests-red`, `unbound-param`, `duplicate-requirement` and `possible-drift` are
7183
+ the same shape. So the distribution is uneven and unenforced rather than
7184
+ absent, which is a weaker premise than the one filed but a more precise one:
7185
+ what is missing is not the text, it is anything that makes the text obligatory.
7186
+ (Classified by reading. A first pass by pattern undercounted — it missed the
7187
+ lowercase mid-sentence imperatives in `never-red` and `empty-spec` — and its
7188
+ number is deliberately not quoted here.)
7189
+ *The derivation argument holds, and it is the reason to file this at all.* 31
7190
+ of the 41 codes appear as a `code: '…'` literal, across 43 sites; the other 10
7191
+ are built by a ternary or handed through a variable, which is the shape
7192
+ `types.ts` narrows `code` to `IssueCode` for rather than policing with a regex.
7193
+ A `fix` authored at issue sites inherits that split and can be forgotten at any
7194
+ of the 43. A `fix` keyed on `code` and resolved when a report is rendered
7195
+ cannot be — it covers all 41 including the 10, which is exactly the argument
7196
+ `docs.ts` already makes for the link, applied to the other half of the line.
7197
+ *What blocks it is that the text would then exist four times.* Every one of the
7198
+ 41 codes already has a `**Solution**` block in `docs/en/troubleshooting.md` and
7199
+ its counterpart in `docs/zh` — 41 sections each, checked by
7200
+ `docs-consistency.spec.ts` for existence and by nothing for content. A `fix`
7201
+ string makes a fourth statement of the same thing, and no gate can check the
7202
+ four agree. That is the failure mode `docsUrl` was designed to avoid, arriving
7203
+ through the field added next to it.
7204
+ *And a surface the proposal had not counted: 80 Symptom blocks.* Each of the 41
7205
+ sections opens with a fenced block quoting real output, in both languages — 82
7206
+ blocks, of which **80 quote the terminal rendering** and would be dated the
7207
+ moment `formatIssues` gains a `fix:` line. (The other two are `internal-error`,
7208
+ whose Symptom is a `--json` document, so it is the one section a rendering
7209
+ change leaves alone. It is worth naming rather than rounding away: it is the
7210
+ same carve-out `docs-consistency.spec.ts` already makes for that code.) **No
7211
+ test reads any of them** — that suite asserts the code-to-section
7212
+ correspondence and the pinned docs URLs, and has no assertion over a Symptom
7213
+ block at all — so nothing would demand the 80 edits and nothing would catch
7214
+ them being skipped. The documentation cost is larger than the feature.
7215
+ *What would move it.* Either a gate binding a code's `fix` to its `Solution`
7216
+ section, so the fourth copy is checked rather than merely written — which would
7217
+ also retire the 82-block exposure by making one of the two a function of the
7218
+ other — or an adoption report that one of the 25 action-less messages actually
7219
+ cost somebody a round trip. Without the first, this trades a derivable field for
7220
+ an underivable duplicate.
7221
+
7222
+ - **A Stop hook that will not let an agent end a turn while `check self` is red.**
7223
+ From the same read: `nostics` ships `hooks/consume-diagnostic.mjs`, a Claude
7224
+ Code Stop hook that reads the log its dev-server collector writes and returns
7225
+ `{"decision": "block"}` with the diagnostic as the reason, so the agent keeps
7226
+ working rather than stopping on a red application. Three details there are the
7227
+ design and not the plumbing, and are the transferable part: it guards on
7228
+ `stop_hook_active` so a blocked stop cannot loop; it feeds back **one**
7229
+ diagnostic per turn rather than the whole log; and when it consumes one it drops
7230
+ every other entry sharing that code and file, so a fault at twenty call sites is
7231
+ one round trip and not twenty.
7232
+ *Why the shape fits this repository specifically.* `CLAUDE.md`'s "Before
7233
+ committing" and the whole of `pre-commit-check` are discipline: nothing runs
7234
+ `check self` unless a person or an agent remembers to, and the repeat-grouping
7235
+ that hook does by hand is something `formatIssues` already computes. `.claude/`
7236
+ here holds five skills, no hooks and no `settings.json`, so this would be the
7237
+ first of its kind rather than an extension of anything.
7238
+ *Measured cost, 2026-09-01.* `node bin/attest.js check self --json` runs in
7239
+ **1.04–1.57 s** over three consecutive runs; `status self --json` in 0.78 s.
7240
+ That is paid at the end of every turn, which is the number to weigh rather than
7241
+ the wall clock of a single invocation.
7242
+ *What blocks it is the stale-`dist/` trap, and a hook is the worst place to meet
7243
+ it.* `bin/attest.js` loads `dist/`, not `src/` — the Gotcha this repository
7244
+ already keeps — so a Stop hook reports on the last build. After a `src/` edit it
7245
+ blocks on a diagnostic the working tree has already fixed, or clears while the
7246
+ tree is broken, and in both directions it is a gate arguing about a state nobody
7247
+ is in. Either the hook builds first, adding seconds to every turn, or it runs the
7248
+ engine from source and stops being the thing an adopter could copy.
7249
+ *And the larger question it sits in front of.* If this is right here it is right
7250
+ for an adopter, which makes it a question about what `attest init` writes rather
7251
+ than about `.claude/` in this repository. A hook that can block an agent's stop
7252
+ is a much larger commitment than a document, and the rejection quoted in full
7253
+ inside the `init --check` entry in this section — the gate on the installed
7254
+ skill's freshness — turned on exactly the distinction it needs:
7255
+ a signal about work the reader actually did, against a chore the tool imposes. A
7256
+ block on `check` is the first kind, which is the argument for it, and that
7257
+ argument should be made explicitly rather than arrived at by shipping.
7258
+ *What would move it.* Running it here, unshipped and repo-local, for a few weeks,
7259
+ and counting how often it blocks on something that would otherwise have been
7260
+ committed. If that count is zero the discipline was already working and the hook
7261
+ is overhead; if it is not, the number is the case for `init` writing one.
7262
+
7115
7263
  ## Considered and rejected
7116
7264
 
7117
7265
  Decisions **not** to build something, kept where they can be found before the
@@ -7122,6 +7270,611 @@ it sat between 0.2.0 and 0.1.7 for two releases, where standing still meant
7122
7270
  sinking one version deeper each time a release was cut above it, and a rejection
7123
7271
  filed under a version reads as belonging to it.
7124
7272
 
7273
+ ### A backlink index over `archive/`, recording which change introduced or renamed an id
7274
+
7275
+ Proposed from OpenViking's `.relations.json`, restated twice on measurement, and
7276
+ rejected 2026-09-01. It is kept as it was written under *Under consideration*,
7277
+ because what closed it is the argument the entry had already assembled against
7278
+ itself rather than anything that arrived afterwards; the closing paragraphs are
7279
+ at the foot of the entry.
7280
+
7281
+ **A merged requirement carries no trace of the change that produced it, and
7282
+ the rename chain is the part actually lost.** `RequirementSchema` is
7283
+ `statement`, `rationale`, `params`, `outOfScope`; the splice writes those and
7284
+ nothing else, so after `--apply` no field and no sidecar records which change
7285
+ introduced an id, which modified it, or that one id used to be another. The
7286
+ delta that knew is in `archive/`, which nothing reads. Proposed from
7287
+ OpenViking's `.relations.json`: links between memories live in a
7288
+ directory-level sidecar, deliberately kept out of the content, and are
7289
+ rendered on demand at retrieval time (`docs/design/memory-link-design.md`).
7290
+ *The argument for the sidecar over a field is in their competitor analysis
7291
+ rather than their design.* The same document reads GBrain, which writes links
7292
+ into the page body, and records what that costs: rename a page and every
7293
+ inbound link still points at the old slug, with fuzzy matching at the read end
7294
+ as the only mitigation. A `supersedes:` or `introducedBy:` field on a
7295
+ requirement would be that mistake in this repository's terms — the registry is
7296
+ a literal and a single source (§2), and a field naming a folder that a later
7297
+ rename can move is a second source with nothing comparing it against the
7298
+ first. A derived index has no such copy to go stale: it is recomputed from the
7299
+ archived deltas every time it is read.
7300
+ *What blocks the decision, restated on 2026-08-19 because half of what was
7301
+ filed here is no longer true.* This entry was blocked on the empty archive and
7302
+ on the corpus being rename-free, and only the second still holds. `archive/`
7303
+ now has a real change in it — the `open` field, put through the workflow the
7304
+ same day — so the store this would index exists. Its delta carries `added` and
7305
+ nothing else, measured rather than assumed, so a backlink index is still
7306
+ testable only against fixtures for the operation it is actually about. Note
7307
+ what that says about the odds of the blocker clearing on its own: `--apply`
7308
+ refuses RENAMED outright, so no change this repository archives can ever
7309
+ exercise a rename until that refusal is lifted, and lifting it is a separate
7310
+ decision with its own reason (`splice.ts` — deleting an entry has no smaller
7311
+ span to fall back to). The blocker is therefore not "waiting for a rename to
7312
+ happen"; it is downstream of a refusal nobody has proposed removing. And one
7313
+ question the OpenViking reading does not answer, which is whether the chain is
7314
+ wanted at all. §7 applies
7315
+ `RENAMED` to a `Map<id, Requirement>` and the old id is then simply gone —
7316
+ deliberately, as far as anything written says. The case for recovering it is an
7317
+ external reference that outlives the rename, a ticket or a commit message
7318
+ naming a retired id, and nobody has reported one.
7319
+ *Measured against a second corpus on 2026-08-31, and it points the same way.* A
7320
+ 0.9.1 → 0.11.0 upgrade report from `mine-capablanca` walked all 41 revisions of
7321
+ that project's single registry and all 49 of its archived changes: **80 ids have
7322
+ ever appeared in it and 80 are in it now — zero removed, zero renamed** — and
7323
+ neither `removed:` nor `renamed:` appears in non-empty form in any delta.
7324
+ Commit messages on every branch, the whole tree and the issue tracker were
7325
+ searched for id-shaped tokens the registry does not define. The external
7326
+ reference this entry waits for did not turn up in sixteen days and forty-nine
7327
+ changes.
7328
+ *One id did outlive itself, from the other direction, and the reason it was left
7329
+ alone is a mechanism this entry did not have.* `AI-10` has never been in that
7330
+ registry, and two committed documents discuss the gap it leaves. The archived
7331
+ proposal's stated reason for not renumbering to close it is that doing so "would
7332
+ mean re-recording a failing run against an implementation that already exists".
7333
+ So `never-red` prices a rename directly: an id cannot be renumbered without
7334
+ re-earning its evidence, and an author facing that bill chose to write about the
7335
+ hole instead. That is a second reason the corpus is rename-free, independent of
7336
+ `--apply`'s refusal, and it argues the rate **stays** at zero rather than that
7337
+ nobody has got round to it.
7338
+ *What this does to the entry:* nothing is refuted, and the blocker is now
7339
+ blocked twice over. It was already downstream of a refusal nobody has proposed
7340
+ removing; it is also downstream of an obligation that makes the operation
7341
+ expensive on purpose. An index over an event that two independent mechanisms
7342
+ discourage is an index over an empty set.
7343
+ *Rejected 2026-09-01, on the entry's own last sentence rather than on anything
7344
+ new.* What closes it is that the two mechanisms are not a coincidence of one
7345
+ corpus and not a stage adoption passes through. `--apply` refuses RENAMED for a
7346
+ reason recorded in `splice.ts` — deleting an entry has no smaller span to fall
7347
+ back to — and `never-red` prices the operation independently, since an id cannot
7348
+ be renumbered without re-earning its evidence. The second is the stronger of the
7349
+ two because it does not depend on a refusal anybody could lift: an author who
7350
+ faced that bill wrote about the hole `AI-10` leaves instead of closing it, which
7351
+ is the mechanism arguing the rate **stays** at zero rather than that nobody has
7352
+ got round to it. An index whose subject two independent mechanisms discourage is
7353
+ not waiting for evidence; it is waiting for a decision nobody has proposed, taken
7354
+ for reasons that have nothing to do with this.
7355
+
7356
+ *What was measured, and it is the whole of the case against.* Across the second
7357
+ corpus: 80 ids have ever appeared and 80 are in it now, over 49 archived changes
7358
+ — zero removed, zero renamed — with neither `removed:` nor `renamed:` appearing
7359
+ in non-empty form in any delta. Commit messages on every branch, the whole tree
7360
+ and the issue tracker were searched for id-shaped tokens the registry does not
7361
+ define. The external reference this entry existed to serve is the one thing that
7362
+ would have justified it, and sixteen days and forty-nine changes produced none.
7363
+
7364
+ *One structural consequence, recorded because it is a change to somebody else's
7365
+ entry.* Two open entries rested on `--apply`'s refusal of RENAMED; closing this
7366
+ one leaves **one**, the third meaning under *Retiring a requirement* below. That
7367
+ matters in the direction of not lifting the refusal: a decision with two
7368
+ dependents is easier to justify than a decision with one, and the count is now
7369
+ one. Both cross-references have been corrected rather than left to be inferred.
7370
+
7371
+ *What would reopen it.* Not a report of the drift this entry describes — that
7372
+ report is what sixteen days and two corpora failed to produce. It reopens if
7373
+ `--apply`'s refusal of RENAMED is lifted for its own reason, because the
7374
+ operation then becomes available and the corpus stops being evidence about a
7375
+ population that cannot exist. That is the one event that changes the argument,
7376
+ and it is not this entry's to trigger.
7377
+
7378
+ ### A detector for an assertion that recomputes its expectation from the param under test
7379
+
7380
+ Proposed 2026-08-03 from `mine-capablanca`'s report, carried through three
7381
+ re-measurements, and rejected 2026-08-31. The defect it describes is real and
7382
+ stays undetected: an assertion deriving its expectation from the same param the
7383
+ code under test consumed has no independent term, so the value can move with the
7384
+ suite green, `check` silent and `possible-drift` correctly not firing. *What
7385
+ killed it is the detector's own score, and that is now the whole of the case.*
7386
+ The one anybody built scores **50% on the shape and 0% on the harm**: of 8 flags, 4 are real echoes and
7387
+ none of the 4 is harmful, while the false positives are pinned by a term in
7388
+ another file that a single-body detector cannot see by construction. Precision
7389
+ fell on every addition measured.
7390
+
7391
+ *The frequency half of this rejection was wrong on the day it was written, and it
7392
+ is withdrawn.* It read that frequency is "approximately zero across two
7393
+ codebases", on 8 commits in the reporting repository across which no param value
7394
+ was ever modified. A 0.9.1 → 0.11.0 upgrade report from `mine-capablanca` on
7395
+ 2026-08-31 re-ran that count properly — all **41 revisions** of the same
7396
+ registry, the file evaluated at each and compared key by key rather than read out
7397
+ of diff hunks — and it comes back with **seven values changed**. Not
7398
+ approximately zero. The breakdown belongs to the param-value entry under `Under
7399
+ consideration` and is not re-quoted here, because a figure stated in two places
7400
+ goes stale in one. The 8-commit sample was what was reachable on the day and not
7401
+ an answer to the question, and quoting it as one is the failure this file's own
7402
+ method section warns about. 38 revisions of *this* registry with 57 keys added, 0
7403
+ removed and exactly 1 value changed still stands, and is now the outlier rather
7404
+ than the confirmation.
7405
+
7406
+ *What that leaves is a rejection standing on one leg, deliberately.* A defect
7407
+ occurring seven times in six weeks in one adopting repository is not a hazard
7408
+ nobody has committed; it is a thing that happens. The detector is still refused,
7409
+ because scoring 0% on harm is a property of the mechanism that no frequency
7410
+ improves — a rule that flags four true echoes and is right about the danger of
7411
+ none of them is not made useful by there being more echoes to flag. Where the
7412
+ frequency finding goes instead is the param-value entry under `Under
7413
+ consideration`, which was always the entry about the move rather than about the
7414
+ assertion, and which the same report reinforces rather than settles.
7415
+
7416
+ *And the reopening condition was very nearly met, which is worth recording
7417
+ precisely.* It asks for a param whose value moved, whose assertion echoed it, and
7418
+ where the move was **harmful**. The report supplies the first two in one commit:
7419
+ two `AI-14` params changed, the scenarios reading the same values the
7420
+ implementation reads, the whole suite green, and a diff containing no spec file
7421
+ and no implementation file at all. The third leg fails, and fails cleanly — that
7422
+ move was the entire point of the change, argued for in a 63-line proposal
7423
+ committed beside it. So the closest instance anybody has is a **correct** edit
7424
+ that no gate could have objected to, which is evidence about the gate and not
7425
+ about the assertion.
7426
+
7427
+ *The guidance half is not what is rejected here.* `[0.5.0]` replaced "read the
7428
+ param inside the assertion" everywhere it appeared with what the evidence
7429
+ supports — the expectation must not be a function of the param the code under
7430
+ test consumed — and that shipped. What is rejected is the mechanism, and the
7431
+ hole stays open: a tautological assertion is still green and still silent.
7432
+ Documenting a trap is not detecting it, and this section is where that
7433
+ difference is recorded rather than hidden.
7434
+
7435
+ *Two things this leaves standing, neither of them a mechanism.* Design §11
7436
+ carries the property in the reader's form, so an adopter meets it where the
7437
+ method is taught. And the detector stays in
7438
+ `tests/intent-rule-candidates.spec.ts` with its verdict table recomputed on
7439
+ every run: it is the evidence that killed this, and deleting it would leave the
7440
+ rejection resting on a figure nothing checks — the exact failure this file has
7441
+ already had once, when the reach quoted for `compound-requirement` went stale in
7442
+ it.
7443
+
7444
+ *Reopening needs the one thing neither corpus supplied:* a param whose value
7445
+ moved, whose assertion echoed it, and where the move was **harmful**. Precision
7446
+ on the shape is a tuning question. Precision on the harm is the whole question,
7447
+ and telling a rendering fixture from a mine-density band is a judgement about
7448
+ whether the value *means* anything — which is the ground truth §0 refuses to
7449
+ compute for somebody else's registry. Nothing short of running each scenario
7450
+ twice under different values separates an independent pin from an echo, and that
7451
+ is mutation testing, a much larger thing to own.
7452
+
7453
+ The claim and the measurements, kept as they were taken:
7454
+
7455
+ **An assertion that recomputes its expectation from the param the code under
7456
+ test just read is a tautology, and nothing says so.** Reported by
7457
+ `mine-capablanca`, which probed it by mutating the registry and running the
7458
+ full gate on each mutation:
7459
+
7460
+ | mutation | who reads it | `check` | `verify` |
7461
+ |---|---|---|---|
7462
+ | `GEN-1.maxCandidatesPerPiece` 64 → 4 | scenario only | ok | **red** |
7463
+ | `BOARD-1.boardSize` 10 → 12 | app + fixtures | ok | **red** |
7464
+ | `BOARD-2.maxMines` 12 → **40** | app + assertion | ok | **green** |
7465
+ | `BOARD-2.minMines` 9 → **1** | app + assertion | ok | **green** |
7466
+
7467
+ The mine-density band — the one number deciding whether that game is playable —
7468
+ moved by 3.3× with the whole suite green, `check` silent, and `possible-drift`
7469
+ correctly not firing, because a covering scenario *does* read the param.
7470
+ **Read the third and fourth rows against the second, because that is the
7471
+ finding.** The reporter's diagnosis was "app and spec both read it", but row 2
7472
+ is app-and-spec and goes red. What distinguishes the green rows is narrower and
7473
+ more useful: the assertion **derives its expectation from the same param the
7474
+ code under test used**, so both sides move together and the comparison has no
7475
+ independent term. Row 2's expectation lives in fixtures, which do not move when
7476
+ the param does, and the mechanism works. So the failing condition is a property
7477
+ of the assertion, not of who reads the value — which matters, because it means
7478
+ the answer is a rule about how to write the assertion, and *not* the detector
7479
+ the reporter proposed.
7480
+ **What this falsifies is written in this file.** The rejection of a committed
7481
+ params snapshot, below, closes on *"the mechanism that guards a param is a
7482
+ scenario reading it, not a file recording it"* — a sufficiency claim, and rows
7483
+ 3 and 4 are counterexamples to it. A scenario reading the param is necessary
7484
+ and not sufficient. That entry stays rejected on its own argument, which was
7485
+ never about detection power, but its fallback clause is now known incomplete
7486
+ and is annotated accordingly. The README's §"killer move" has the same shape:
7487
+ it says noticing a param *moved* is `verify`'s job "only when a scenario asserts
7488
+ on the value it read from `params`" — literally a necessary condition, and read
7489
+ by an adopter as a sufficient one, with the very next line (*"Read the param
7490
+ inside the assertion, not beside it"*) being the instruction that produces the
7491
+ tautology.
7492
+ **The framing that probably resolves it: this is `never-red` wearing different
7493
+ clothes.** A tautological assertion is a scenario that cannot fail, which is
7494
+ the exact defect `archive`'s `never-red` gate exists to catch — and it did
7495
+ catch two of them in this very repo (`CHECK-2`, `FOG-3`, both confirmed
7496
+ correct by the reporter). The reason it did not catch these is structural:
7497
+ **changing the value of an existing param goes through no gate at all.**
7498
+ `archive` gates a change; a param edit is not a change. That is a cleaner
7499
+ statement of the gap than "params can be tautological".
7500
+ *Re-read against `evidence-not-ordering`, which separated that gate's
7501
+ obligation from the ordering it used to be described by.* Nothing here moves:
7502
+ the blocker was never that the observation had to be taken first, it is that a
7503
+ param edit produces no change folder for any observation to be recorded
7504
+ against. If anything the framing gets sharper — what `never-red` demands is
7505
+ evidence that a scenario can fail, which is precisely what is missing for a
7506
+ tautological assertion, and the reason it goes undemanded is still the absence
7507
+ of a gate rather than the absence of a route to the evidence.
7508
+ **That observation is now its own entry at the foot of this section**, split
7509
+ out and measured on 2026-08-18, because it asks what happens when a value
7510
+ moves rather than whether an assertion is a real comparison, and neither
7511
+ answer settles the other. What stays here is the detector.
7512
+ *What blocks the decision — three things, all of them real.* **(1)** Every
7513
+ mutation above is synthetic. Across 8 commits touching `*.reqs.ts` in that
7514
+ repo, **no param value was ever modified** — every `params:` line in every diff
7515
+ is an addition. So the hole is proven as a logical property and its frequency is
7516
+ entirely unmeasured, from one greenfield repo. **(2)** The reporter's proposed
7517
+ mechanism — a WARNING when one param id is read from both a spec and a non-spec
7518
+ file — is the wrong shape twice over: it fires on row 2, which is correct usage
7519
+ and goes red, and it would fire on the arrangement the README actively
7520
+ recommends, which is §9's "usually wrong about staleness" failure mode again.
7521
+ It also needs a static sweep of the whole project for `reqs['X'].params.y`,
7522
+ far past the two registry readers' current surface. **(3)** No mechanism is yet
7523
+ known that separates "the assertion pins this independently" from "the assertion
7524
+ echoes it" without executing the scenario twice under different values — which
7525
+ is mutation testing, a much larger thing to own. *One has since been built and
7526
+ scored, and it does not lift this blocker.* The syntactic candidate — flag a
7527
+ scenario whose expectation is derived from the param the code under test
7528
+ consumed, decided inside one body without executing anything — flags 9 params
7529
+ here, of which **4 are real echoes and 5 are pinned by something outside the
7530
+ body it cannot see**. Under half precision, and the failure is the one
7531
+ predicted above rather than a tuning problem: what pins a value is often a
7532
+ fixture or a second derivation in another file, so a single-body detector is
7533
+ looking in the wrong place by construction. The newest false positive is the
7534
+ clearest instance — `ATX-79.subpath` is compared against `package.json`, which
7535
+ is not the code under test and does not read the registry, so the independent
7536
+ term is in another file entirely. Precision has fallen on every addition
7537
+ measured so far, which is the direction that matters. The score is quoted from the
7538
+ suite's snapshot and re-read with the judgement-table entry above, which is
7539
+ where the same measurement is used for a different decision.
7540
+ **The guidance half has shipped; this entry is now only about the detector.**
7541
+ "Read the param inside the assertion" was insufficient and half-wrong as
7542
+ written, and `[0.5.0]` replaces it everywhere it appeared — README, the
7543
+ `init` skill, `possible-drift`, and design §11 — with what the evidence
7544
+ supports: **the expectation must not be a function of the param the code under
7545
+ test consumed**, pinned in a fixture, a literal, or a second independently
7546
+ derived value. Row 2 is the worked example, and the reporter's own
7547
+ `CHECK-2`/`FOG-3` fixes are the same move. It was held back once because
7548
+ rewording the central claim of the tool is not a line appended; that is what
7549
+ was then done, deliberately and in one pass.
7550
+ **What stays undecided is the mechanism**, and all three blockers above are
7551
+ untouched by the rewording: the frequency is still unmeasured from one
7552
+ greenfield repo, the reporter's proposed detector is still the wrong shape
7553
+ twice over, and nothing short of mutation testing separates an independent pin
7554
+ from an echo. What the guidance does not do is close the hole — a tautological
7555
+ assertion is still green, still silent, and still reachable — so this stays
7556
+ here rather than moving to `Considered and rejected`: documenting a trap is
7557
+ not the same as deciding not to detect it.
7558
+ **Re-measured after `[0.7.1]` widened `params`, on this registry rather than
7559
+ the reporter's — the first time this experiment has been run here.** Twelve
7560
+ mutations, each followed by the whole gate. Two results matter, and they point
7561
+ opposite ways.
7562
+ *The shape is now reachable here, and the widening is why.* `self/render.spec.ts`
7563
+ asserts `expect(md).toContain(JSON.stringify(kinds))` over the same
7564
+ `payloadKinds` it feeds to the renderer — the expectation is a function of the
7565
+ value under test, verbatim row 3, and it could not have been written before
7566
+ the widening because the param could not be an object. The mechanism is
7567
+ sharper than a frequency count and is the strongest thing this entry has
7568
+ gained: **the prescribed remedy costs in proportion to the value's
7569
+ complexity.** Same repo, adjacent files, same value shape — `ATX-7` pins two
7570
+ numbers with literals and goes red; `ATX-63` would have had to hand-write a
7571
+ rendered JSON block, so it echoed. Depth raises the defect rate and the cost
7572
+ of avoiding it along one axis.
7573
+ *And its cost here is nil, which is the honest other half.* Both composite
7574
+ params are fixtures for the renderer and the validator; the numbers stand for
7575
+ nothing, so the green is arguably correct. Blocker **(1)** is therefore only
7576
+ half retired — the shape is reproduced in a second, mature repo, and the count
7577
+ of *harmful* instances is still zero. Blocker **(2)** is reinforced rather than
7578
+ weakened: all four green mutations were on params `src/` never reads, so the
7579
+ reporter's "read from both a spec and a non-spec file" detector would have
7580
+ flagged none of them. Blocker **(3)** is untouched.
7581
+ *The frequency is no longer unmeasured, and it points away from building
7582
+ anything.* Blocker **(1)** rested on 8 commits of one greenfield repo. Parsed
7583
+ at every one of the **38** revisions that have touched this registry — all 38
7584
+ readable by the current static reader, so nothing was skipped — the history is
7585
+ **57 param keys added, 0 removed, and exactly 1 value changed**: `ATX-16.staticCommands`
7586
+ gaining `status`. That single edit is a roster *widening*, the safe direction,
7587
+ and it is on the one list that was already pinned, so it could not have been
7588
+ made without updating the pin. Two independent codebases, one greenfield and
7589
+ one at 64 requirements, and the event this whole entry is about has happened
7590
+ zero times in either. What that argues is not that the hole is unreal — it is
7591
+ reachable, demonstrated above — but that it is **rare and silent**, which is
7592
+ the profile `divergent-param` was rejected on rather than the profile that
7593
+ justifies an engine rule.
7594
+ *The same number says the split-out fix below has also never been triggered.*
7595
+ 0 removals ever means no domain has ever been shortened here. Those pins close
7596
+ a live *exposure*, not a live *loss*, and the case for them is that they cost
7597
+ one line each against a failure with no symptom — not that anything was
7598
+ currently broken.
7599
+ *Blocker **(3)** assumed mutation testing was the only separator. A cheaper
7600
+ candidate existed, has now been scored, and does not survive it.* The idea was
7601
+ a **syntactic** discriminator over one test body — flag an assertion whose
7602
+ expected value derives from a param that also reached the arrange/act part —
7603
+ which is exactly the difference between the two cases the widening produced:
7604
+ `renderMarkdown({params: {payloadKinds: kinds}})` … `toContain(JSON.stringify(kinds))`
7605
+ against `expect(kinds.json.maxBytes).toBe(1_048_576)`. Scored the way
7606
+ `divergent-param` should have been and was not: implemented in
7607
+ `tests/intent-rule-candidates.spec.ts`, run over the specs that attest this
7608
+ repo, and then **every param it fires on mutated with the whole gate run —
7609
+ fifteen of them, one gate run each. Four went green; eleven went red.**
7610
+ *Then the detector was found to be wrong, which is the more useful half.* Its
7611
+ first implementation counted an identifier in any position as a read of a
7612
+ local with that name, so `i.code` in `issues.map((i) => i.code)` looked like a
7613
+ use of a param named `code` — and `code` is this repo's commonest param name.
7614
+ Corrected to count reference positions only, it flags **8, not 15**, and the
7615
+ seven it drops are exactly seven the mutations had already shown to be pinned
7616
+ by `src/`. The honest score is therefore **4 of 8, not 4 of 15** — precision
7617
+ 50%, not 27%. The conclusion does not move, because it never rested on the
7618
+ precision figure; what moves is how much of the original number was the
7619
+ candidate failing and how much was the measurement being wrong, and that is
7620
+ worth writing down rather than quietly restating.
7621
+ The remaining false positives are not near-misses. They are independent terms
7622
+ the detector cannot see: **the runtime is the term** — `ATX-14`'s
7623
+ `inheritedKey` is `'toString'`, and a mutated spelling is not a prototype key
7624
+ at all, so the behaviour under test changes; **`src/` is the term** —
7625
+ `ATX-51`'s `code` is a second copy of a string the engine emits as a literal;
7626
+ and **arithmetic is the term** — `threshold + 3` builds a derived expectation
7627
+ that mutation breaks.
7628
+ And the four true positives fail the second test, which is the one that
7629
+ decides it: **none is harmful.** `ATX-10` and `ATX-3` assert that a value was
7630
+ substituted or applied, where the identity of the number is not the
7631
+ obligation; `ATX-51`'s is a path prefix and `ATX-63`'s a rendering fixture.
7632
+ Telling those from a mine-density band that must not move is a judgement about
7633
+ whether the value *means* anything — the ground truth §0 refuses to compute
7634
+ for someone else's registry. **50% precision on the shape, 0% on the harm.**
7635
+ Two limits on that number, stated because a score with an unstated method is
7636
+ what this file exists to prevent. The mutation has **one direction per kind**
7637
+ — numbers +1, lists drop-last, strings suffixed — and direction can decide the
7638
+ verdict: `ATX-40.languages` is red at 2→3 and was green at 2→1 before it was
7639
+ pinned, so the true-positive count is a floor. And **recall is unmeasured**:
7640
+ the eight params mutated earlier that this detector does not flag all went
7641
+ red, which is eight correct negatives and not a recall figure.
7642
+ The detector and its verdict table are kept in the measurement suite, with the
7643
+ reach recomputed on every run and the table asserted to cover exactly what
7644
+ fires — because the reach figure quoted for `compound-requirement` went stale
7645
+ in this very file for want of that.
7646
+ *A scoping limit on all of the above.* This repo cannot settle blocker **(2)**.
7647
+ `src/` reads no param at all — every `reqs[…].params` in it is a comment or a
7648
+ doc string — so params here are test vocabulary, while in the reporter's repo
7649
+ they are application constants the app itself consumes. The reporter's
7650
+ detector keys on exactly that difference, so it is inert here and this corpus
7651
+ is evidence neither for nor against it.
7652
+ *What the re-measurement did find is a different defect, and it has been split
7653
+ out below* — the entry is kept whole here because the two were measured in one
7654
+ pass and separating them is the finding.
7655
+
7656
+ ### Retiring a requirement
7657
+
7658
+ Filed 2026-08-03 on the cost of permanence, re-measured four times, and rejected
7659
+ 2026-08-31 **by its own reopening condition**. The entry nominated the test —
7660
+ reopen if the cost per requirement moves — and the ratio has read 2.56 → 2.78
7661
+ → 2.80 → 2.82 scenarios per requirement across twenty-five days and
7662
+ twenty-four requirements. Three consecutive readings within 0.05 of each other
7663
+ are not a curve about to move, and the condition has now failed to trigger
7664
+ twice. The cost argument is therefore not waiting for evidence; it was answered
7665
+ in the negative by the thing the entry itself chose to be judged on.
7666
+
7667
+ *The three meanings it ended by separating are why the rest closes rather than
7668
+ merely goes quiet.* **(1) A requirement that turned out wrong** was never a
7669
+ retirement — the record of being wrong belongs in this section, which is what
7670
+ this section is, and removing the id is a manual registry edit nobody has argued
7671
+ against. **(3) A requirement subsumed by a later one** is a rename, blocked
7672
+ downstream of `--apply`'s refusal of RENAMED (`splice.ts`: deleting an entry has
7673
+ no smaller span to fall back to). That is somebody else's blocker, and lifting it
7674
+ is a decision which is not this one. It was shared with the backlink-index entry
7675
+ until 2026-09-01, when that entry was rejected; this is now the only thing
7676
+ resting on the refusal. **(2) A requirement still true and no
7677
+ longer worth a permanently-run scenario** was the only one ever about cost, and
7678
+ it is the one the ratio answers.
7679
+
7680
+ *What (2) left behind was a defect rather than a mechanism, and it is fixed.*
7681
+ `uncovered-requirement` read "Add a scenario, or **mark it deferred**", and
7682
+ `deferred` appeared nowhere else in the project — not a field, not a flag, not
7683
+ a schema key — so the escape hatch this meaning needs had been named in shipped
7684
+ output, in the one place a reader looks when they hit the error, since before
7685
+ anybody asked the question. `[Unreleased]` rewords it, troubleshooting now says
7686
+ what the second exit actually is, and design §11 records the permanence as a
7687
+ property to design against rather than a gap awaiting a mechanism. **This entry
7688
+ is where the trace of the affordance having been intended now lives**, which is
7689
+ why the rewording waited for the rejection instead of going in as a stray fix:
7690
+ changing the message first would have deleted the only written evidence that
7691
+ anybody ever meant to build it.
7692
+
7693
+ *The permanence itself is not rejected and is not a defect.* Deleting an intent
7694
+ in order to save the scenario attesting it inverts the claim the whole tool
7695
+ exists to make. Reopening needs the case (2) names — a requirement whose intent
7696
+ is still true and whose scenario is genuinely not worth running — together with
7697
+ a mechanism that keeps the first while dropping the second. Nobody has proposed
7698
+ one, and the message that implied there already was one is the reason the
7699
+ question looked bigger than it is.
7700
+
7701
+ The measurements, kept as they were taken:
7702
+
7703
+ **Whether a requirement can ever be retired, and what it costs that none can.**
7704
+ The registry is at **85 requirements and 240 scenarios, 2.82 each**, and
7705
+ every one is a permanent obligation: `uncovered-requirement` is an ERROR, so a
7706
+ requirement without a scenario cannot exist, and nothing ever removes one.
7707
+ `delta.removed` exists and `applyDelta` applies it, but `--apply` refuses to
7708
+ write it back, deliberately and with the reason recorded — REMOVED "cannot say
7709
+ which comments belonged to the entry it deletes". So retiring one is a manual
7710
+ registry edit, which is fine as a mechanism and says nothing about whether it
7711
+ is ever the right move.
7712
+ *What the growth actually costs, measured on this session rather than
7713
+ estimated.* One small fix took: a requirement, its scenarios, a hand-judged
7714
+ row in the `OBLIGATIONS` table, two inline snapshots, a `[Unreleased]` entry,
7715
+ History rows in two languages, and a regenerated `SPEC.md`. That is
7716
+ proportionate for a spec framework that has to dogfood — the ceremony *is* the
7717
+ product being used on itself — and it is also a number that only goes up.
7718
+ *What blocks the decision, and it is not the mechanism.* It is that "retire"
7719
+ has at least three meanings this file has never had to separate: a requirement
7720
+ that turned out **wrong** (which should leave a record of being wrong, and
7721
+ `Considered and rejected` is where that already goes); one still **true** but
7722
+ no longer worth a permanently-run scenario; and one **subsumed** by a later
7723
+ requirement, where the scenario should move rather than go. Only the second is
7724
+ really about cost, and it is the one where deleting the intent to save the
7725
+ scenario inverts what the whole tool claims — that intent outlives its tests.
7726
+ Nothing here is urgent at 85. It is filed now because the moment to write the
7727
+ argument down is while the reasoning is available, and because a project whose
7728
+ requirement count only rises should have decided in advance what would make
7729
+ one leave.
7730
+ *The figures above were restated on 2026-08-08, and how they were wrong is
7731
+ itself the section's own warning happening.* As filed on 2026-08-03 this entry
7732
+ said "62 requirements and 160 scenarios, roughly 2.6 each" while closing on
7733
+ "the number is 61" — an inconsistency inside one entry, and the closing half
7734
+ was the right one. Re-measured at the entry's own commit with the current
7735
+ parser: **61 requirements, 156 scenarios**, so both opening figures were wrong
7736
+ on the day they were written, and only the ratio survived. Measured the same
7737
+ way today it is 69 and 192, and the method is the same one `verify self`
7738
+ prints — the control is that today's `self/` counted this way returns exactly
7739
+ what the runner does, which is what makes the 2026-08-03 number trustworthy. The
7740
+ cheap way — `grep -c 'scenario('` — answers 211 against today's true 192, and
7741
+ is what would have been reached for: comments and prose count, which is the
7742
+ `ATX-15` error again.
7743
+ **What moved in five days sharpens the argument rather than dating it.** +8
7744
+ requirements and +36 scenarios, concentrated in three sessions rather than
7745
+ accrued at a rate, so nothing here is a trend line. The part worth keeping is
7746
+ the ratio: 2.56 → 2.78. The cost of a requirement is not flat and it is not
7747
+ falling, which is the premise the whole entry rests on, and it is the first
7748
+ time this file has had two measurements of it to compare.
7749
+ *A third measurement, on 2026-08-18, and it points the other way from the
7750
+ second.* **79 requirements and 221 scenarios**, counted the way `verify self`
7751
+ prints them, which is the control the 2026-08-08 restatement established. The
7752
+ ratio runs 2.56 → 2.78 → **2.80**, so the step that carried the argument was
7753
+ the first one and the second is **+0.015**, which is flat. The premise this
7754
+ entry rests on now has one measurement for it and one against, and the honest
7755
+ statement is the narrower one: per-requirement cost has held at about 2.8
7756
+ scenarios across ten days and ten requirements. What is unchanged is the
7757
+ absolute total, which only rises — that half of the argument is untouched, and
7758
+ it was always the half about permanence rather than about acceleration. What is
7759
+ no longer supported is the acceleration. **Reopening on cost needs the ratio to
7760
+ move again**; reopening on the three meanings of "retire" needs none of these
7761
+ figures, and that is still the part nobody has separated.
7762
+ *A fourth measurement, on 2026-08-28, and the cost half is now closed rather
7763
+ than merely unsupported.* **85 requirements and 240 scenarios**, counted the
7764
+ way `verify self` prints them: the ratio runs 2.56 → 2.78 → 2.80 → **2.82**.
7765
+ Three consecutive readings within 0.05 of each other, across twenty-five days
7766
+ and twenty-four requirements, are not a curve that is about to move. The
7767
+ reopening condition this entry set itself has now failed to trigger twice, so
7768
+ the cost argument is not waiting for evidence — it has been answered, in the
7769
+ negative, by its own test. What survives untouched is the half that was never
7770
+ about the ratio: the absolute total only rises, and permanence is the claim.
7771
+
7772
+ **The three meanings, separated — which is what the entry has been asking for
7773
+ and needs none of the figures above.** They are not one question, and only one
7774
+ of the three is open.
7775
+
7776
+ **(1) A requirement that turned out wrong.** Not a retirement at all, and the
7777
+ mechanism already exists: the record of being wrong belongs in `Considered and
7778
+ rejected`, which is what that section is, and removing the id is a manual
7779
+ registry edit nobody has argued against. Nothing is missing here. Filing it
7780
+ under "retire" was what made the question look bigger than it is.
7781
+
7782
+ **(2) A requirement still true but no longer worth a permanently-run
7783
+ scenario.** The only one of the three that is about cost, and the only one with
7784
+ no mechanism — and the sharpest thing this separation turned up is that the
7785
+ tool has been *advertising* one. `uncovered-requirement` reads "Add a scenario,
7786
+ or **mark it deferred**", and `deferred` appears nowhere else in `src/`: not a
7787
+ field, not a flag, not a schema key. The troubleshooting section for that code
7788
+ quotes the message as its symptom and then explains only the first half,
7789
+ because there is no second half to explain. So the escape hatch this meaning
7790
+ needs has been named in shipped output, and in the one place a reader looks
7791
+ when they hit the error, since before anyone asked the question. That is a
7792
+ defect in its own right — the message is prose and free to reword — but it is
7793
+ recorded here rather than fixed there, because rewording it would delete the
7794
+ only written trace that this affordance was ever intended, which is the thing
7795
+ this entry exists to keep. *That held until the entry itself closed, and no
7796
+ longer applies:* the trace is the paragraph above, the rejection is what makes
7797
+ it durable, and `[Unreleased]` rewords the message. The condition on the
7798
+ rewording was never that it stay wrong — it was that somewhere else had to say
7799
+ the affordance had been meant first.
7800
+
7801
+ **(3) A requirement subsumed by a later one**, where the scenario should move
7802
+ rather than go. That is a rename, and `--apply` refuses RENAMED outright, with
7803
+ the reason recorded in `splice.ts` — deleting an entry has no smaller span to
7804
+ fall back to. So it is blocked downstream of a refusal nobody has proposed
7805
+ lifting, which is the *same* blocker the backlink-index entry above carried until
7806
+ that entry was rejected on 2026-09-01. Two open entries rested on that one
7807
+ refusal and now **one** does, which is worth knowing and cuts the other way from
7808
+ how it was first written: a decision with two dependents is easier to justify
7809
+ than a decision with one, so the case for ever lifting the refusal got weaker
7810
+ rather than the blocker getting closer to clearing. This entry is not the reason
7811
+ the refusal exists, and it is now the only thing waiting on it.
7812
+
7813
+ *So the entry narrows to one meaning.* (1) is answered, (3) is somebody else's
7814
+ blocker, and what is actually open is (2) — whether a requirement can stop
7815
+ owing a permanently-run scenario while keeping its intent, which is the one
7816
+ case where deleting the intent to save the scenario inverts what the whole tool
7817
+ claims. Nothing here says it should be built; what changes is that the question
7818
+ is now one question.
7819
+
7820
+ ### Declared prefix ownership instead of inferred
7821
+
7822
+ Filed 2026-08-19 from a spec-kit reading, rejected 2026-08-31 on the two things
7823
+ that moved under it. *What rejected it: the proposal is smaller than it looked,
7824
+ weaker than it looked, and answers a question nobody here has asked.*
7825
+ `loadRegistry` already returns `prefixOwners`, `merge.ts` already uses it to
7826
+ place an ADDED id, and `apply-no-prefix-owner` already refuses a prefix nobody
7827
+ owns — so what was proposed is declaration replacing inference, not a new
7828
+ layer. And `/inspect` shipped in `[0.9.2]`, so the cross-repository half — the
7829
+ one §11 says no command can see across — is now about ten lines of an
7830
+ adopter's own code over two registries. Attest does not have to own it to make
7831
+ it possible.
7832
+
7833
+ The third is what decides it. This repository's five prefixes — `ATX` and the
7834
+ four fixture ones — are each declared by exactly one file, so the allocation
7835
+ question a declaration would answer has never arisen here. Building it now is
7836
+ deciding prefix semantics for everyone from a corpus in which those semantics
7837
+ are unexercised, which is the objection this project raises against every
7838
+ premature interface.
7839
+
7840
+ §11 keeps the property in place unchanged: one flat global id space, the
7841
+ prefix a convention rather than a scope, and `duplicate-prefix` able only to
7842
+ report a collision that already happened. **The cost still arrives exactly when
7843
+ adoption succeeds** — two teams sharing a registry have somebody allocating
7844
+ prefixes by hand — and that is the reopening condition. It is also the
7845
+ condition the two-party audit entry under consideration would satisfy, so the
7846
+ first adopter who shares a registry decides both.
7847
+
7848
+ The reading this was filed from, kept as it was written:
7849
+
7850
+ **Prefix ownership is inferred rather than declared, and the cost arrives only
7851
+ when adoption succeeds.** Design §11 records that the id grammar has no level
7852
+ above the requirement: the prefix is a convention, nothing allocates it, and
7853
+ `duplicate-prefix` can only report a collision that already happened.
7854
+ spec-kit's answer is a roadmap table — a stable id, an intent, a scope
7855
+ boundary, dependencies — authored above the specs it decomposes into. That
7856
+ comparison is stated here rather than referred to, because the entry that
7857
+ introduced the spec-kit reading has since been built and now sits under
7858
+ `[0.9.4]` as the `open` field: an entry whose evidence rests on a
7859
+ neighbour is one a promotion silently empties.
7860
+ *Two things have changed since that reading, and they point opposite ways.*
7861
+ The proposal is **smaller** than it looked: the mechanism partly exists.
7862
+ `loadRegistry` returns `prefixOwners` — first claim in sorted file order —
7863
+ `merge.ts` uses it to decide which file an ADDED id belongs in, and
7864
+ `apply-no-prefix-owner` already refuses a prefix nobody owns. So what is
7865
+ proposed is declaration replacing inference, not a new layer. And it is
7866
+ **weaker**: `/inspect` shipped in `[0.9.2]`, so the cross-repository half —
7867
+ the one §11 says no command can see across — is now something an adopter
7868
+ computes from two registries in about ten lines. Attest does not have to own
7869
+ it to make it possible.
7870
+ *What blocks the decision:* nobody has the problem. §11 states the cost
7871
+ arrives "exactly when adoption succeeds: two teams sharing a registry have
7872
+ someone owning prefix allocation by hand", and no such adoption has been
7873
+ reported. Building the declared form now would be deciding prefix semantics
7874
+ for everyone from one repository whose five prefixes — `ATX` and the four
7875
+ fixture ones — are each declared by exactly one file, so the allocation
7876
+ question this would answer has never been asked here.
7877
+
7125
7878
  ### An id-keyed retrieval bundle over `archive/` and this file
7126
7879
 
7127
7880
  Proposed 2026-08-19 inside the entry now titled *The decisions worth retrieving
@@ -7237,6 +7990,17 @@ for the section most likely to be read before a proposal. Nothing was changed
7237
7990
  about that here: restructuring twelve open entries is a larger edit than the
7238
7991
  finding justifies, and the finding is only four events old.
7239
7992
 
7993
+ *The counts above are the 2026-08-28 measurement and have since moved; recounted
7994
+ 2026-08-31.* `Considered and rejected` holds **27 `###` entries**, not 24 —
7995
+ entries were decided into it in the meantime, and `init --check` left it,
7996
+ reopened — and `Under consideration` holds **six** bullets, not twelve. The
7997
+ heading ratio is deliberately not recounted: judging a heading artifact-shaped is
7998
+ a hand call, so re-running it would be a new measurement rather than a repair of
7999
+ this one, and the entry rests on the argument rather than on the ratio. What the
8000
+ recount does settle is the aside above about the section next door — at six
8001
+ bullets the case for giving it headings is weaker than it was at twelve, not
8002
+ stronger.
8003
+
7240
8004
  ### A per-scenario axis recording which interface a scenario crosses
7241
8005
 
7242
8006
  Prompted by an external write-up (`columns.chicken-house.net`, 2025-11-10, "AI-First
@@ -7907,27 +8671,6 @@ is the "cleared by deleting the word that triggered it" failure arriving without
7907
8671
  anyone trying to clear it, which strengthens the rejection rather than
7908
8672
  qualifying it.
7909
8673
 
7910
- ### A freshness gate on the file `attest init` writes (`init --check`)
7911
-
7912
- `render --check` is the obvious precedent and is the opposite case. A rendering
7913
- is a function of the *user's registry*, so its staleness means the user moved
7914
- intent without regenerating — a real signal, with the fix in their hands. The
7915
- skill is a function of the *installed version*, so a gate on it turns red on a
7916
- change the user did not make, and is cleared by a commit containing no decision.
7917
- That is a chore wearing a diagnostic's clothes, and a gate that cannot be about
7918
- anything the reader did is worse than no gate — the same standard 0.1.6 set when
7919
- it gave `render` no formatting options at all. An earlier draft of `attest init`
7920
- spent a second command, this gate and a second self-requirement on it. What that
7921
- would have bought is immunity to one honest and small exposure: an old file
7922
- describes an old workflow to an agent that trusts it. The backstop is that every
7923
- mistake it can cause is already a diagnostic with a fix hint — a registry
7924
- written the old way is `registry-not-static` — so the agent corrects itself from
7925
- the report, and `init` is idempotent, which makes re-running it the entire
7926
- update mechanism. Recorded here rather than left in the 0.3.0 entry that shipped
7927
- `init`, because the queue above depends on it: a gate is the first thing
7928
- proposed each time a new agent target is considered, and the answer must not
7929
- need re-deciding per target.
7930
-
7931
8674
  ### A marked block in the adopter's `AGENTS.md` / `CLAUDE.md`
7932
8675
 
7933
8676
  The cross-tool convention Codex, Cursor and Copilot read, and the reason it
@@ -8057,9 +8800,13 @@ was written expecting the uncovered case and the demonstrated one has a covering
8057
8800
  scenario. **What that changes here is nothing**: this entry was rejected on the
8058
8801
  population a snapshot fires on — every legitimate param edit, fix a commit with
8059
8802
  no decision in it — and that objection is untouched by the new evidence, which
8060
- concerns detection the snapshot was never denied having. The live question moved
8061
- to `Under consideration` above, where it belongs, because the mechanism now in
8062
- question is a rule about assertions rather than a file beside the registry.
8803
+ concerns detection the snapshot was never denied having. The live question
8804
+ moved to `Under consideration`, because the mechanism then in question was a
8805
+ rule about assertions rather than a file beside the registry, and it has since
8806
+ been rejected in its own right — above in this section, on a frequency of about
8807
+ zero across two codebases and a detector scoring 0% on harm. So the hole both
8808
+ entries describe is now recorded as open with nothing watching it, which is a
8809
+ weaker position than either entry claimed on its own and is the honest one.
8063
8810
 
8064
8811
  ### Grouping `render`'s output by prefix
8065
8812
 
@@ -8251,8 +8998,9 @@ fallback to evaluation. The same answer applies here for the same reason.
8251
8998
 
8252
8999
  Worth reopening if an adopter's registry reaches a size where prefix grouping
8253
9000
  and `grep` genuinely stop working — but the first thing to try then is the
8254
- declared prefix ownership already under consideration above, which addresses,
8255
- rather than a scorer, which ranks.
9001
+ declared prefix ownership recorded above in this section — rejected on there
9002
+ being no adoption that needs it, which is the same condition this sentence
9003
+ supposes — which addresses, rather than a scorer, which ranks.
8256
9004
 
8257
9005
  ## How an entry is re-measured
8258
9006
 
@@ -8331,6 +9079,7 @@ path the repair makes reachable; it is about **everything the repair would make
8331
9079
 
8332
9080
  ## Releases
8333
9081
 
9082
+ [1.0.0]: https://gitlab.com/Pseudorca/attest/-/tags/v1.0.0
8334
9083
  [0.11.0]: https://gitlab.com/Pseudorca/attest/-/tags/v0.11.0
8335
9084
  [0.10.0]: https://gitlab.com/Pseudorca/attest/-/tags/v0.10.0
8336
9085
  [0.9.4]: https://gitlab.com/Pseudorca/attest/-/tags/v0.9.4