@am_shork/attest 0.10.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,16 +7,620 @@ 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.
28
+
29
+ **A narrowing that closes a security hole is breaking by that definition, and
30
+ takes the bump that definition gives it — including a major.** There is no
31
+ carve-out for it and no flag. The two alternatives were priced and both cost
32
+ more than the version number: an exemption needs a test for what counts as a
33
+ security narrowing, and nothing here can hold a prose rule to one, so the label
34
+ would be available to every inconvenient narrowing and judged by the person
35
+ making it. Shipping the new refusal behind an opt-in that defaults off is worse
36
+ for the case it is meant to serve — the four commands documented as safe on an
37
+ untrusted fork MR are the ones a reviewer runs on a file they have not read, and
38
+ a default that keeps answering wrong unless the reviewer knows to pass a flag
39
+ has not been fixed.
40
+
41
+ *What that costs is stated rather than hidden.* Under 0.x it costs nothing: the
42
+ same fix is a minor either way, which is why this could go undecided for so
43
+ long. Under 1.0 it is a major on whatever schedule a hole is found — the
44
+ `callsAuthoringFn` binding fix, the fifth breaking commit in this project's
45
+ history, would have been a 2.0 twenty-five days after a 1.0 cut at the fourth.
46
+ The pressure that creates lands on the version number, and the failure it
47
+ invites is the other one: a fix weakened until it fits a bump somebody
48
+ preferred. A narrowing that ships smaller than the defect it closes is what this
49
+ paragraph exists to make visible.
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
+
269
+ ## [0.11.0] - 2026-08-28
270
+
271
+ ### Added
272
+
273
+ - **`examples/judgement-table/` — the enforcement half of the intent-quality
274
+ method, as one runnable file with no columns.** `intent-quality.md` has told
275
+ an adopter to build a judgement table since `[0.9.2]` shipped `/inspect`, and
276
+ the path to producing one was five voluntary steps with a scaffold at none of
277
+ them: a row in a document table, a 214-line page, a hand pass over four
278
+ questions, then writing the two *mechanical* properties from a prose
279
+ description. The last of those is the step that did not need to be voluntary.
280
+ Completeness and rot-binding do not depend on what the adopter decided to
281
+ measure — their content is fixed — so they are the half where a copy is not an
282
+ echo, and the four judgement columns stay withheld for the reason `[0.9.2]`
283
+ gave: a second table built by copying ours is evidence about nothing.
284
+
285
+ **The columns are absent by construction rather than left blank.**
286
+ `Judgement` ships one optional `pin` — the anchor rot-binding needs, holding a
287
+ scenario name rather than a judgement about one — and adding a required field
288
+ to it makes **every existing row fail to compile**, measured rather than
289
+ claimed: one added field, `TS2741` on both rows. So the type does at author
290
+ time what the completeness assertion does at run time, and a row cannot be
291
+ added without a decision.
292
+
293
+ **It runs in the default suite, and that is the point rather than a detail.**
294
+ `pre-commit-check` §6 already records that the three usage samples on
295
+ `intent-quality.md` are consumer usage of the public API that no gate reaches;
296
+ a fourth copy of them in a directory that *looks* executable would be worse
297
+ than a fenced block, not better. So `vitest.config.ts` includes
298
+ `examples/**/*.spec.ts`, the specifier `@am_shork/attest/inspect` is aliased to
299
+ source there and in `tsconfig.typecheck.json`, and both assertions were shown
300
+ to bite before this was called done — a third requirement added to the sample
301
+ fails completeness with `[ 'EX-3' ]`, and renaming the pinned scenario fails
302
+ rot-binding by name. What the alias deliberately does not check is that the
303
+ specifier resolves in an installed package; `tests/consumer.spec.ts` owns that
304
+ against a real `npm install`, and the two together are the whole claim.
305
+
306
+ A third assertion is in the skeleton that is in neither the page nor this
307
+ entry's proposal: `expect(issues).toEqual([])`, **before** the other two. A
308
+ registry file that will not read statically contributes nothing to `registry`,
309
+ so an unread file is indistinguishable from a project with fewer requirements
310
+ and both completeness directions pass over it. It was not written from
311
+ reasoning — an invalid mutation during the bite test produced exactly that
312
+ state, `registry: {}` with one `registry-invalid` issue, and the guard is what
313
+ caught it.
314
+
315
+ *What it cost elsewhere, because it is the part that does not announce
316
+ itself.* The sample project is a registry, and `tests/intent-rule-candidates.spec.ts`
317
+ walks every `*.reqs.ts` in the tree — so the corpus moved **95 → 97** and the
318
+ `compound-requirement` snapshot was re-read rather than re-baselined:
319
+ `compound`, `correct` and `missed` are all unchanged, so no rate quoted
320
+ anywhere moved and only the denominator did. Those two ids are deliberately
321
+ dull, which is a biased sample, and it is admitted in the snapshot's comment
322
+ rather than held out — one example directory is one adapter, and a hold-out is
323
+ a seam this repository does not buy until something varies across it.
324
+
325
+ - **This repository now carries the workflow document it ships (`ATX-84`).**
326
+ `attest init` has written `.claude/skills/attest/SKILL.md` since 0.6.0, and this
327
+ repository had never run it: `.claude/skills/` held three hand-written skills and
328
+ no `attest/`, so the workflow half was reachable here only by opening
329
+ `src/core/skill.ts` — the exact gap `ATX-27` exists to close, open in the project
330
+ that ships the fix. Nothing could report it, because an absent file is
331
+ indistinguishable from a repository that has not adopted Attest. `.gitattributes`
332
+ has named the file among the generated files it pins since `fcd6140`, a sentence
333
+ false on the day it was written.
334
+
335
+ The requirement holds the committed file byte-identical to `skillContent()`, so
336
+ editing the workflow text turns `attest verify self` red until `attest init` is
337
+ re-run. That is deliberately *not* the freshness gate `src/core/skill.ts` argues
338
+ against shipping: that argument is about adopters, whose copy goes stale only at
339
+ an upgrade and whose every resulting mistake is a diagnostic carrying a fix hint.
340
+ This is the one place where `skillContent()` changes while the file exists, so
341
+ staleness is an ordinary edit rather than an upgrade event, and the gate costs one
342
+ string comparison. Byte equality rather than `ATX-29`'s document comparison for
343
+ the same reason the distinction runs the other way for `render --check`: that gate
344
+ runs in repositories where Attest does not own `.gitattributes`, this one only
345
+ here, where `eol=lf` is committed.
346
+
347
+ No engine behaviour changes. The document is unchanged, `init` is unchanged, and
348
+ nothing an adopter runs is affected.
349
+
350
+ ### Changed
351
+
352
+ - **`delta()` no longer offers the requirements it adds under `added`, because
353
+ that was the one spelling stage 1 could not tell from the right one
354
+ (`ATX-85`).** `delta()` returns the delta *and* the registry of what it
355
+ proposes, so `d.added['AUTH-7'].params.x` and `d['AUTH-7'].params.x` are both
356
+ correct while a change is being authored — and only the second survives
357
+ `--apply`, which repoints the spec's import at the merged registry, and a
358
+ registry has no `added`. Nothing in the pipeline could separate them: the
359
+ archive gate runs *before* the rename, so the spec it graded is not the spec
360
+ the merge commits; `check` executes nothing, so it never evaluates the
361
+ accessor; and the compiler is satisfied on either side because it only ever
362
+ sees whichever half is on disk. The failure arrived one command later as a
363
+ `spec-load-failed` naming the file and not the cause.
364
+
365
+ **The guidance already existed and did not prevent it, which is why this is a
366
+ type and not a warning.** `registry.ts` documents the intended expression and
367
+ the rejected one on the function itself, and the document `init` writes shows
368
+ the by-id form in its worked example; the reach-through was written anyway, by
369
+ the author of the workflow tooling, on the first change this repository put
370
+ through its own flow. The alternative priced against it was a `check`
371
+ WARNING — which would have had to be a warning, since enumerating what a
372
+ change adds is legitimate, and a warning is exactly the strength that already
373
+ failed. `DefinedDelta` withholds the key instead: no diagnostic, no roster
374
+ entry, no runtime cost.
375
+
376
+ **`added?: never` rather than a bare `Omit`, and the difference is nine call
377
+ sites.** `RegistryDelta`'s four fields are all optional, so an added-only delta
378
+ with `added` removed shares *no* property with it and TypeScript's weak-type
379
+ check refuses `applyDelta(base, delta({ added: … }))` outright — measured at 11
380
+ errors, of which 9 are that shape and every one correct. An optional `never`
381
+ restores the overlap and still refuses the read, past both escape hatches:
382
+ `d.added!` and `d.added?.[…]` bottom out in `never`, and only an explicit
383
+ `as any` compiles. That last property is the whole reason this beats the
384
+ WARNING, so it is pinned in `tests/delta-types.spec-d.ts` rather than left to
385
+ hold by luck — a type has no runtime footprint, so a regression here would
386
+ otherwise ship green.
387
+
388
+ **Breaking, and it takes the bump this file's Versioning section gives it.** A
389
+ stage-1 spec reading through `added` stops compiling. The type also now
390
+ deliberately understates its own value — `withProposedRequirements` still
391
+ spreads `added`, and `applyDelta` and the loader still read it through
392
+ `RegistryDelta` — so what is withheld is the authoring surface and not the
393
+ data; `self/registry.spec.ts` keeps asserting exactly that, through a cast that
394
+ is itself the assertion.
395
+
396
+ *Re-measured before implementing, per `How an entry is re-measured`, and the
397
+ filed figures reproduced exactly* — 11 errors in the same two kinds, 11 → 2
398
+ under the variant, `typecheck` clean throughout, all three escape-hatch results
399
+ as recorded. **What the re-measure added was a surface the entry had not
400
+ counted, and it falsified this requirement's first draft before it shipped.**
401
+ `modified`, `removed` and `renamed` vanish at the merge too, and reading any of
402
+ them compiles on both sides — so the statement first drafted for `ATX-85`, that
403
+ withholding `added` leaves the by-id read "the only expression that resolves",
404
+ was false. They are left readable on purpose: none has a by-id equivalent, so
405
+ there is no correct spelling to redirect an author to, and a spec reading one
406
+ is reading delta-only data rather than misspelling a right thing. The narrowed
407
+ statement claims only what the refusal actually buys.
408
+
409
+ *And the exposure claim was one notch too wide.* The entry recorded the window
410
+ between `--apply` and the next `verify` as the whole exposure. In this
411
+ repository it is not: post-merge, reading through any of the four fields fails
412
+ to compile under `noUncheckedIndexedAccess`, so `typecheck:all` bounds it. The
413
+ unbounded form is an adopter's, whose spec files need be in no `tsc` project at
414
+ all — which is the case the refusal is for.
415
+
416
+ *Two things this change made the existing counter-pressure say.* The judgement
417
+ tables refused the new id until it carried a hand-judged row, which is that
418
+ mechanism working on its own author rather than on a hypothetical adopter — and
419
+ the corpus moved 97 → 98 with no rate disturbed. And `divergent-param` churned
420
+ its `field` key a **fourth** time — `ATX-85`'s `added` joining ATX-20, ATX-82
421
+ and ATX-83 — changing no verdict again, which is one more measurement for the
422
+ entry recording that this rule keys on the evidence it discards.
423
+
424
+ ### Security
425
+
426
+ - **The static reader matched the authoring call by spelling, so a file could
427
+ shadow `defineRequirements` and hand the two readers different registries.**
428
+ `callsAuthoringFn` accepted any identifier named `defineRequirements` — the
429
+ bare name was added unconditionally as a fallback for a global or a harness —
430
+ and any property access whose *last* segment was that name. So a
431
+ `*.reqs.ts` opening with `const defineRequirements = (r) => ({ ...r,
432
+ 'ADMIN-1': … })`, or with a local object carrying a method of that name, was
433
+ read by parsing it as the literal argument in front of the call, while the
434
+ evaluator answered with whatever that function returned.
435
+
436
+ Not a refusal but a **wrong answer**, and pointed at under-reporting: `check`
437
+ called the registry covered, `cover` counted rows that were not the rows, and
438
+ `render` wrote a document with an id missing from it. The reach is what makes
439
+ it worth a section — `check`, `cover`, `render` and `status` are the commands
440
+ documented as safe on an untrusted fork MR *because they execute nothing*
441
+ (`ATX-16`), so they are exactly the ones a reviewer runs on a file they have
442
+ not read, and the registry they report is the one nothing else will mention.
443
+ Two readers of one file disagreeing is the condition the differential suite
444
+ exists to prevent (design §5.2), and it could not have caught this: every
445
+ registry it runs over calls the real function.
446
+
447
+ The rule is now about the **binding** rather than the spelling. An import
448
+ binding is trusted, a name the file never binds is trusted — that is what the
449
+ bare-name fallback has always been for — and a `const` / `let` / `var` /
450
+ `function` / `class` in the file itself is not, whether the callee is that
451
+ name or an object property reached through it. A file that contradicts itself
452
+ becomes `registry-not-static`, with `--eval` named as the way back, which is
453
+ `ATX-17` unchanged rather than a new promise.
454
+
455
+ **What this still does not see is the module graph, and it is not written
456
+ to.** `import { defineRequirements } from './wrapper.js'` binds the name by an
457
+ import and is accepted; what that module exports is not a question the source
458
+ text of *this* file answers, and resolving it would mean reading and then
459
+ trusting another file, which is the evaluator's job. The line drawn is what
460
+ the file contradicts about itself.
461
+
462
+ Rejects input that was previously accepted, so it is breaking under this
463
+ project's 0.x rule — though the shapes it rejects are ones no working registry
464
+ has: the four accepted spellings (the import, an alias, a namespace or default
465
+ import's property, and the bare name) all still read, and each is pinned.
466
+
467
+ - **`repointImport` wrote back at offsets taken from a source it never checked
468
+ compiles.** `ATX-69` states that a source that does not compile must be
469
+ refused rather than read out of whatever the parser recovered, and names the
470
+ three readers that open with `createSourceFile`. This was the fourth site and
471
+ the only one that *writes*: `--apply` repoints a proposed spec's import of the
472
+ delta at the registry, and it opened the compiler itself, so it asked nobody.
473
+ `ts.createSourceFile` recovers — a half-saved spec still yields an import
474
+ declaration and a set of offsets — and those offsets were then written over
475
+ the bytes of a `*.spec.ts` the user cannot regenerate.
476
+
477
+ Unreachable through the command today, because the gate refuses a spec it
478
+ could not parse before a merge starts, and fixed for that reason rather than
479
+ in spite of it: a defence that holds only because something upstream holds is
480
+ not a defence, which is the standard `keySource` in the same file is written
481
+ to. A source that does not compile now comes back unchanged, so `applyMerge`
482
+ — which writes only when the text moved — renames the spec with the import it
483
+ already had, and a re-run finds and reports that state instead of a file
484
+ rewritten at offsets nobody can trust.
485
+
486
+ ### Documentation
487
+
488
+ - **`## Versioning` now says what a security narrowing costs a consumer, which
489
+ was undefined and which 0.x was the only reason nobody had to ask.** The
490
+ definition at the head of this file counts *rejecting previously-valid input*
491
+ as breaking and says nothing about security, so under 0.x the question was
492
+ invisible: the same fix is a minor either way. It stopped being hypothetical in
493
+ the same week it was measured — the `callsAuthoringFn` binding fix below is
494
+ breaking precisely *because* it narrows what a static reader accepts, and it
495
+ closes a wrong-answer hole in the four commands documented as safe to run on an
496
+ untrusted fork MR.
497
+
498
+ **Decided: such a fix takes the bump the existing definition gives it,
499
+ including a major.** The three candidate answers are not equivalent and only
500
+ one needed no new machinery. A carve-out naming security narrowings as exempt
501
+ requires a test for what counts as one, and this repository has already
502
+ rejected that shape under another name — *Countable obligations*, killed on the
503
+ argument that **nothing can force the count to be true**; a label nothing can
504
+ hold is available to every inconvenient narrowing and judged by the person
505
+ making it. Reaching the same end additively — the new refusal behind a flag,
506
+ defaulting off until the next major — is the only answer that costs the
507
+ consumer nothing, and it is disqualified by the case it would have to serve
508
+ first: a default that keeps answering wrong unless a reviewer knows to pass a
509
+ flag has not fixed a hole in the commands a reviewer runs on a file they have
510
+ not read.
511
+
512
+ *What it is not.* Not a vulnerability-disclosure policy and not a security
513
+ section for the README — one paragraph in `## Versioning`, in the vocabulary
514
+ that section already uses. And it was needed either way: if 1.0 is never cut
515
+ the paragraph documents a real property of the tool, and if it is, the
516
+ paragraph is a precondition, because a 1.0 without it promises something whose
517
+ price nobody has computed. That is why this could be settled while the 1.0
518
+ entry below stays open — it is decidable from this repository alone, which is
519
+ the property that entry is missing.
520
+
521
+ - **`intent-quality.md` tells people to build on `/inspect` and never said
522
+ whether the shapes move.** `inspect.ts` exports five of them and the page
523
+ carries three samples destructuring them; `SCHEMA_VERSION` governs the `--json`
524
+ envelope and governs none of these. **The policy was not missing — its
525
+ statement was.** The Versioning section already binds them: removing or
526
+ renaming a public API is breaking, and under 0.x that is the minor bump. The
527
+ gap is that the reader this export was written for arrives through a method
528
+ page reached from the README's document table, and nothing on that path said
529
+ so.
530
+
531
+ **Which of two sentences to write was the whole decision, and it turned on
532
+ 1.0.** Restating the existing promise reads as a stability guarantee and is the
533
+ honest summary of current policy; saying the shape may move before 1.0 is the
534
+ more useful warning and discourages exactly the adopter whose judgement table
535
+ would settle the schema entry below. With 1.0 not being cut for this release,
536
+ the second is the true one, and the cost is accepted rather than hidden: the
537
+ page now warns off the reader it most needs. It is written as a promise about
538
+ being *told* rather than about nothing changing, which is what the versioning
539
+ policy actually offers.
540
+
541
+ - **`tests-red` told you to read the failures out of a report that does not carry
542
+ them.** The troubleshooting page said "`attest verify` gives you the same run
543
+ with the individual failures". It does not, and never did: `verify` and
544
+ `archive` both run the child suite with `quiet: true` because the CLI renders
545
+ its own report, so the whole output is one `tests-red` — no file, no scenario
546
+ name, no assertion. A reader following that sentence runs the command it names
547
+ and learns nothing new. Both languages now say to run the spec directly with
548
+ your own test command, and say why that is safe (a direct run writes nothing,
549
+ so it cannot touch `first-run.json`) and why it is not a verdict.
550
+
551
+ Found by dogfooding `ATX-84`: making the installed workflow document stale on
552
+ purpose, to check that the scenario's failure carries a fix hint, produced a
553
+ `verify self` output with no failure in it at all.
15
554
 
16
555
  ## [0.10.0] - 2026-08-22
17
556
 
18
557
  ### Changed
19
558
 
559
+ - **`never-red` no longer claims to be a rule about writing the test first.**
560
+ Archived as `evidence-not-ordering`. The obligation is unchanged in every
561
+ observable way — same code, same scoping to the ids a change ADDs, same
562
+ blocking condition — and what moved is the registry's account of it, plus
563
+ every document that had turned "red before you implement" into the rule.
564
+
565
+ **The finding is that the ordering was never in the engine.** `keepOutcome` is
566
+ monotonic toward failure in one direction only: a recorded fail is permanent, a
567
+ recorded **pass is provisional**. Its motive was to let an author fix a
568
+ scenario that passed on its first run rather than hand-delete the evidence, but
569
+ the consequence is larger than the motive — a failure observed *after* the
570
+ implementation exists replaces the pass and satisfies `neverRedIssues`
571
+ identically to one observed before it. `self/red-record.spec.ts` has asserted
572
+ that at the record level since the mechanism shipped. So the gate has always
573
+ taken the evidence without caring when it was obtained, and three pieces of
574
+ prose said otherwise: `ATX-30`'s statement stopped at "its first run",
575
+ `ATX-31`'s rationale named running the gate first as *the* remedy, and the
576
+ workflow document said "Run this before you implement anything", which is
577
+ false as written.
578
+
579
+ **Removing it closes nothing, which is the argument for removing it.** A
580
+ stage-1 red can come from a typo in the spec, a missing import, or a fixture
581
+ that is not there yet — none of which is the requirement being unimplemented.
582
+ A failure observed before the implementation was therefore never *better*
583
+ evidence than one observed after it, only earlier. What the gate can actually
584
+ demand is that the scenario was seen to fail with its subject absent, and both
585
+ routes to that produce the same fact by the same command: run the gate before
586
+ writing the implementation, or remove the implementation, run the gate, and
587
+ restore it. The second route is now documented, with the `git stash` shape
588
+ spelled out in `troubleshooting.md`.
589
+
590
+ **Why this is worth the churn rather than a footnote.** The rule as written
591
+ declared malformed the one workflow that is becoming ordinary — intent,
592
+ scenario and implementation authored in one pass — and told those authors to
593
+ redo finished work in a different order to satisfy a gate that would have
594
+ accepted their evidence anyway. Separating the discipline (*an assertion has
595
+ to be able to fail*) from the habit (*write it first*) is what keeps the
596
+ strong half enforceable and stops the weak half from being enforced by
597
+ documentation the engine does not back.
598
+
599
+ `ATX-30` gains the clause the suite already checked — a recorded pass is
600
+ replaced by any later failure — and stops calling the record a snapshot of the
601
+ first run. `ATX-31` keeps its statement, which was always order-free, and
602
+ loses the ordering from its rationale. `ATX-48`, `ATX-53` and `ATX-56` carry a
603
+ one-phrase rationale patch each, because all three named it "the
604
+ red-before-implement gate" and a registry that contradicts itself is the drift
605
+ this tool exists to report. **`first-run.json` keeps its name**: archived
606
+ records already carry it, `inspectRedEvidence` reads them back, and spending
607
+ that to correct a word is the wrong trade — the sentence is where the meaning
608
+ lives.
609
+
610
+ One side effect worth reading: the reworded `ATX-30` statement moved from
611
+ `correct` to `missed` in the compound-requirement measurement, on both
612
+ variants, and the snapshot in `tests/intent-rule-candidates.spec.ts` carries
613
+ it. The requirement did not get less compound — the second clause became a
614
+ participle instead of a second `MUST` — so this is new evidence about *why*
615
+ that rule sees almost nothing, filed with the rejection it belongs to.
616
+
617
+ Two diagnostic messages are reworded and no `code` moves, so `schemaVersion`
618
+ does not move either; `Issue.message` is prose, and this is exactly the kind of
619
+ rewording that contract exists to allow. One assertion in `tests/skill.spec.ts`
620
+ moved from the phrase `must be red` to the code `never-red` — a test pinned to
621
+ a sentence the document is free to reword, which is the same mistake one level
622
+ down.
623
+
20
624
  - **The same mutation pass over the verdict and its evidence: 24 mutations of
21
625
  `gate.ts` and `red-record.ts`, 21 dead, and two of the three survivors closed.**
22
626
  Every step of `evaluateGate` dies when removed, including scoping
@@ -612,7 +1216,7 @@ not API.
612
1216
  one scenario asserting the *discrimination* rather than two asserting each
613
1217
  side, which fails now and still refuses an implementation that warns
614
1218
  unconditionally later. This is the mechanism catching the exact defect class
615
- 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
616
1220
  it was ever pointed at, in the author's own work.
617
1221
 
618
1222
  *A second defect surfaced after `--apply` and is worth recording because
@@ -4206,7 +4810,8 @@ field moved.
4206
4810
  the record would not need to exist at all. So it is the one thing Attest writes
4207
4811
  that cannot be checked by regenerating it, and what stands in for that check is
4208
4812
  a reviewer. (The workflow document has no gate either, but that is a decision —
4209
- `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.)
4210
4815
  Landed in `troubleshooting.md#never-red` and under `attest archive` in the CLI
4211
4816
  reference (both languages), and in the workflow document `attest init` writes —
4212
4817
  where it also became a fourth entry in "things you must not do", beside editing
@@ -4418,9 +5023,10 @@ field moved.
4418
5023
  the first fix written: it makes both maps resolve, at 195 kB and 148 files, to
4419
5024
  serve stepping into the engine from an adopter's `node_modules` — which
4420
5025
  nothing has asked for, and which anyone auditing the reader would do from the
4421
- repository instead. This file has rejected `init --check`, `divergent-param`
4422
- and countable obligations for costing more than a demonstrated need, and that
4423
- 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
4424
5030
  options off is the cleanest published output, since it leaves no dangling
4425
5031
  `sourceMappingURL` comment, but it also removes the maps from `dist/` here,
4426
5032
  where `verify self` runs through the built CLI and they are the only thing
@@ -5016,9 +5622,10 @@ every platform, which is what makes them work on Windows at all.
5016
5622
  than assumed: there isn't one, and that is the design.** A committed
5017
5623
  instruction file is a function of the installed version, so it does go stale on
5018
5624
  upgrade — but staleness only *costs* anything if the tool also ships a gate to
5019
- turn red about it, and `init --check` is rejected on its own terms under
5020
- "Considered and rejected", together with the exposure that leaves and the
5021
- 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
5022
5629
  bought: with none to serve there is no reason to keep the prose out of the file
5023
5630
  either, so it lives in the skill, where an agent reads it with no extra call,
5024
5631
  and a skill body is loaded on demand — a long document costs context only when
@@ -5894,69 +6501,215 @@ losing the argument would cost more than losing the task.
5894
6501
  **Where an entry went is recorded under the version that shipped it.** This
5895
6502
  section keeps no ledger of its own departures — one was maintained here by hand
5896
6503
  for several releases, and a count kept in prose with nothing to check it is a
5897
- claim that goes wrong quietly. What is worth keeping is what the section learned
5898
- about itself, which is three things.
6504
+ claim that goes wrong quietly. What these sections have learned about
6505
+ re-measuring an entry before building it is at the foot of the file, under
6506
+ `How an entry is re-measured` — it applies to all three, and was filed here only
6507
+ because the first of it was written here.
5899
6508
 
5900
- **An entry can sit here for releases on the strength of a figure nobody has
5901
- checked.** The discipline aims at keeping the *argument*, not at re-testing the
5902
- evidence under it, so the first act of building one is re-measuring what it
5903
- claims rather than implementing what it proposes. That has paid three times:
5904
- once killing an entry outright, when the spans it rested on turned out not to be
5905
- spans; four times correcting an entry's own numbers or scope before a line of it
5906
- was written a call-site count that a regex had inflated, a cost filed as "a
5907
- fixture per diagnostic" that the corpus put at one fixture, an injection whose
5908
- payload turned out to need the target file's *own* quote character, which is the
5909
- difference between escaping one quote and escaping the right one, and a fix
5910
- filed as a map over `issues` that the report's own shape moved to the
5911
- serialiser, four fields and one nesting level further out. **What a release costs its adopters is not known on the day it is cut**
5912
- several entries arrived from one adoption report, about shapes the tool made
5913
- someone work around without ever erroring. And **the fixes with no observable
5914
- symptom are worth batching**: individually none justifies a commit, together they
5915
- cost about an hour, and they are the group no gate could have found, since having
5916
- no symptom is the definition of what a gate cannot see.
5917
-
5918
- *The five entries this section carried before the ones below* were built and are
5919
- recorded under `[0.8.0]`. The second is where this preamble's own
5920
- discipline paid again: it went in on an entry whose headline type
5921
- measurement instantiations unchanged was false on re-measure, and the
5922
- re-measure is the only reason the release notes do not repeat it. The fourth is
5923
- the other outcome the discipline has, and worth a line because the first three
5924
- were all corrections: the `render --out` symlink measurement reproduced exactly
5925
- as filed, down to the leaf being safe for the reason given. What re-measuring
5926
- added was not a correction but a **surface the entry had not counted** —
5927
- `--check` reads the same destination, so the same link makes a freshness gate
5928
- date a document outside the project. Re-measuring an entry is therefore not only
5929
- a test of the number it quotes; walking the code the claim is about is what
5930
- shows the claim's own edges. The fifth is that lesson arriving where an entry
5931
- had already named its own fix: the C1 measurement reproduced exactly, and the
5932
- repair it proposed a map over `issues` was still the wrong half, because
5933
- four more fields and a nested scenario name carry the same untrusted text and
5934
- never pass the envelope. An entry that ends with a fix is the one least likely
5935
- to have its fix re-examined, which is the case worth naming.
5936
-
5937
- *That has now happened twice, so it is a pattern rather than an incident.* The
5938
- extensionless-import entry likewise reproduced exactly on its reported half and
5939
- was wrong about its own repair it recorded that the writing half needed
5940
- nothing, and the writing half was guarded by a condition that was correct only
5941
- while the reading half stayed broken. Both times the error had the same shape: an
5942
- entry reasons about the code path it can see failing, and the repair makes a
5943
- second path reachable that nobody has looked at because nothing could reach it.
5944
- So the re-measure that matters is not "does the reported symptom reproduce" —
5945
- both did, exactly but **what does the proposed fix make reachable for the first
5946
- time**.
5947
-
5948
- *The first time that question was asked in advance, it paid, and how it paid is
5949
- worth a line because it is not what the two cases above would predict.* The
5950
- `check` `empty-spec` entry `[0.9.0]`, the third entry to reproduce exactly
5951
- on its reported half was not wrong about its own repair. Asking the question
5952
- anyway found the defect one step to the side: the fix it proposed was correct,
5953
- and the *obvious implementation of it* make `check` agree with `verify`
5954
- would have copied a misdiagnosis `verify` had been emitting unnoticed, because
5955
- the reference implementation an entry points at is not itself re-read. So the
5956
- question generalises past the shape it was learned from. It is not only about a
5957
- path the repair makes reachable; it is about **everything the repair would make
5958
- `check` agree with**, and an entry that names the right fix can still be
5959
- implemented wrongly by copying the half nobody has audited.
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.
5960
6713
 
5961
6714
  ## Under consideration
5962
6715
 
@@ -5970,259 +6723,6 @@ deletion. What each entry has to carry is the evidence currently blocking the
5970
6723
  decision, because that is what a re-proposal a year from now would otherwise
5971
6724
  have to gather again.
5972
6725
 
5973
- - **An assertion that recomputes its expectation from the param the code under
5974
- test just read is a tautology, and nothing says so.** Reported by
5975
- `mine-capablanca`, which probed it by mutating the registry and running the
5976
- full gate on each mutation:
5977
-
5978
- | mutation | who reads it | `check` | `verify` |
5979
- |---|---|---|---|
5980
- | `GEN-1.maxCandidatesPerPiece` 64 → 4 | scenario only | ok | **red** |
5981
- | `BOARD-1.boardSize` 10 → 12 | app + fixtures | ok | **red** |
5982
- | `BOARD-2.maxMines` 12 → **40** | app + assertion | ok | **green** |
5983
- | `BOARD-2.minMines` 9 → **1** | app + assertion | ok | **green** |
5984
-
5985
- The mine-density band — the one number deciding whether that game is playable —
5986
- moved by 3.3× with the whole suite green, `check` silent, and `possible-drift`
5987
- correctly not firing, because a covering scenario *does* read the param.
5988
- **Read the third and fourth rows against the second, because that is the
5989
- finding.** The reporter's diagnosis was "app and spec both read it", but row 2
5990
- is app-and-spec and goes red. What distinguishes the green rows is narrower and
5991
- more useful: the assertion **derives its expectation from the same param the
5992
- code under test used**, so both sides move together and the comparison has no
5993
- independent term. Row 2's expectation lives in fixtures, which do not move when
5994
- the param does, and the mechanism works. So the failing condition is a property
5995
- of the assertion, not of who reads the value — which matters, because it means
5996
- the answer is a rule about how to write the assertion, and *not* the detector
5997
- the reporter proposed.
5998
- **What this falsifies is written in this file.** The rejection of a committed
5999
- params snapshot, below, closes on *"the mechanism that guards a param is a
6000
- scenario reading it, not a file recording it"* — a sufficiency claim, and rows
6001
- 3 and 4 are counterexamples to it. A scenario reading the param is necessary
6002
- and not sufficient. That entry stays rejected on its own argument, which was
6003
- never about detection power, but its fallback clause is now known incomplete
6004
- and is annotated accordingly. The README's §"killer move" has the same shape:
6005
- it says noticing a param *moved* is `verify`'s job "only when a scenario asserts
6006
- on the value it read from `params`" — literally a necessary condition, and read
6007
- by an adopter as a sufficient one, with the very next line (*"Read the param
6008
- inside the assertion, not beside it"*) being the instruction that produces the
6009
- tautology.
6010
- **The framing that probably resolves it: this is `never-red` wearing different
6011
- clothes.** A tautological assertion is a scenario that cannot fail, which is
6012
- the exact defect `archive`'s red-before-implement gate exists to catch — and it
6013
- did catch two of them in this very repo (`CHECK-2`, `FOG-3`, both confirmed
6014
- correct by the reporter). The reason it did not catch these is structural:
6015
- **changing the value of an existing param goes through no gate at all.**
6016
- `archive` gates a change; a param edit is not a change. That is a cleaner
6017
- statement of the gap than "params can be tautological".
6018
- **That observation is now its own entry at the foot of this section**, split
6019
- out and measured on 2026-08-18, because it asks what happens when a value
6020
- moves rather than whether an assertion is a real comparison, and neither
6021
- answer settles the other. What stays here is the detector.
6022
- *What blocks the decision — three things, all of them real.* **(1)** Every
6023
- mutation above is synthetic. Across 8 commits touching `*.reqs.ts` in that
6024
- repo, **no param value was ever modified** — every `params:` line in every diff
6025
- is an addition. So the hole is proven as a logical property and its frequency is
6026
- entirely unmeasured, from one greenfield repo. **(2)** The reporter's proposed
6027
- mechanism — a WARNING when one param id is read from both a spec and a non-spec
6028
- file — is the wrong shape twice over: it fires on row 2, which is correct usage
6029
- and goes red, and it would fire on the arrangement the README actively
6030
- recommends, which is §9's "usually wrong about staleness" failure mode again.
6031
- It also needs a static sweep of the whole project for `reqs['X'].params.y`,
6032
- far past the two registry readers' current surface. **(3)** No mechanism is yet
6033
- known that separates "the assertion pins this independently" from "the assertion
6034
- echoes it" without executing the scenario twice under different values — which
6035
- is mutation testing, a much larger thing to own. *One has since been built and
6036
- scored, and it does not lift this blocker.* The syntactic candidate — flag a
6037
- scenario whose expectation is derived from the param the code under test
6038
- consumed, decided inside one body without executing anything — flags 9 params
6039
- here, of which **4 are real echoes and 5 are pinned by something outside the
6040
- body it cannot see**. Under half precision, and the failure is the one
6041
- predicted above rather than a tuning problem: what pins a value is often a
6042
- fixture or a second derivation in another file, so a single-body detector is
6043
- looking in the wrong place by construction. The newest false positive is the
6044
- clearest instance — `ATX-79.subpath` is compared against `package.json`, which
6045
- is not the code under test and does not read the registry, so the independent
6046
- term is in another file entirely. Precision has fallen on every addition
6047
- measured so far, which is the direction that matters. The score is quoted from the
6048
- suite's snapshot and re-read with the judgement-table entry above, which is
6049
- where the same measurement is used for a different decision.
6050
- **The guidance half has shipped; this entry is now only about the detector.**
6051
- "Read the param inside the assertion" was insufficient and half-wrong as
6052
- written, and `[0.5.0]` replaces it everywhere it appeared — README, the
6053
- `init` skill, `possible-drift`, and design §11 — with what the evidence
6054
- supports: **the expectation must not be a function of the param the code under
6055
- test consumed**, pinned in a fixture, a literal, or a second independently
6056
- derived value. Row 2 is the worked example, and the reporter's own
6057
- `CHECK-2`/`FOG-3` fixes are the same move. It was held back once because
6058
- rewording the central claim of the tool is not a line appended; that is what
6059
- was then done, deliberately and in one pass.
6060
- **What stays undecided is the mechanism**, and all three blockers above are
6061
- untouched by the rewording: the frequency is still unmeasured from one
6062
- greenfield repo, the reporter's proposed detector is still the wrong shape
6063
- twice over, and nothing short of mutation testing separates an independent pin
6064
- from an echo. What the guidance does not do is close the hole — a tautological
6065
- assertion is still green, still silent, and still reachable — so this stays
6066
- here rather than moving to `Considered and rejected`: documenting a trap is
6067
- not the same as deciding not to detect it.
6068
- **Re-measured after `[0.7.1]` widened `params`, on this registry rather than
6069
- the reporter's — the first time this experiment has been run here.** Twelve
6070
- mutations, each followed by the whole gate. Two results matter, and they point
6071
- opposite ways.
6072
- *The shape is now reachable here, and the widening is why.* `self/render.spec.ts`
6073
- asserts `expect(md).toContain(JSON.stringify(kinds))` over the same
6074
- `payloadKinds` it feeds to the renderer — the expectation is a function of the
6075
- value under test, verbatim row 3, and it could not have been written before
6076
- the widening because the param could not be an object. The mechanism is
6077
- sharper than a frequency count and is the strongest thing this entry has
6078
- gained: **the prescribed remedy costs in proportion to the value's
6079
- complexity.** Same repo, adjacent files, same value shape — `ATX-7` pins two
6080
- numbers with literals and goes red; `ATX-63` would have had to hand-write a
6081
- rendered JSON block, so it echoed. Depth raises the defect rate and the cost
6082
- of avoiding it along one axis.
6083
- *And its cost here is nil, which is the honest other half.* Both composite
6084
- params are fixtures for the renderer and the validator; the numbers stand for
6085
- nothing, so the green is arguably correct. Blocker **(1)** is therefore only
6086
- half retired — the shape is reproduced in a second, mature repo, and the count
6087
- of *harmful* instances is still zero. Blocker **(2)** is reinforced rather than
6088
- weakened: all four green mutations were on params `src/` never reads, so the
6089
- reporter's "read from both a spec and a non-spec file" detector would have
6090
- flagged none of them. Blocker **(3)** is untouched.
6091
- *The frequency is no longer unmeasured, and it points away from building
6092
- anything.* Blocker **(1)** rested on 8 commits of one greenfield repo. Parsed
6093
- at every one of the **38** revisions that have touched this registry — all 38
6094
- readable by the current static reader, so nothing was skipped — the history is
6095
- **57 param keys added, 0 removed, and exactly 1 value changed**: `ATX-16.staticCommands`
6096
- gaining `status`. That single edit is a roster *widening*, the safe direction,
6097
- and it is on the one list that was already pinned, so it could not have been
6098
- made without updating the pin. Two independent codebases, one greenfield and
6099
- one at 64 requirements, and the event this whole entry is about has happened
6100
- zero times in either. What that argues is not that the hole is unreal — it is
6101
- reachable, demonstrated above — but that it is **rare and silent**, which is
6102
- the profile `divergent-param` was rejected on rather than the profile that
6103
- justifies an engine rule.
6104
- *The same number says the split-out fix below has also never been triggered.*
6105
- 0 removals ever means no domain has ever been shortened here. Those pins close
6106
- a live *exposure*, not a live *loss*, and the case for them is that they cost
6107
- one line each against a failure with no symptom — not that anything was
6108
- currently broken.
6109
- *Blocker **(3)** assumed mutation testing was the only separator. A cheaper
6110
- candidate existed, has now been scored, and does not survive it.* The idea was
6111
- a **syntactic** discriminator over one test body — flag an assertion whose
6112
- expected value derives from a param that also reached the arrange/act part —
6113
- which is exactly the difference between the two cases the widening produced:
6114
- `renderMarkdown({params: {payloadKinds: kinds}})` … `toContain(JSON.stringify(kinds))`
6115
- against `expect(kinds.json.maxBytes).toBe(1_048_576)`. Scored the way
6116
- `divergent-param` should have been and was not: implemented in
6117
- `tests/intent-rule-candidates.spec.ts`, run over the specs that attest this
6118
- repo, and then **every param it fires on mutated with the whole gate run —
6119
- fifteen of them, one gate run each. Four went green; eleven went red.**
6120
- *Then the detector was found to be wrong, which is the more useful half.* Its
6121
- first implementation counted an identifier in any position as a read of a
6122
- local with that name, so `i.code` in `issues.map((i) => i.code)` looked like a
6123
- use of a param named `code` — and `code` is this repo's commonest param name.
6124
- Corrected to count reference positions only, it flags **8, not 15**, and the
6125
- seven it drops are exactly seven the mutations had already shown to be pinned
6126
- by `src/`. The honest score is therefore **4 of 8, not 4 of 15** — precision
6127
- 50%, not 27%. The conclusion does not move, because it never rested on the
6128
- precision figure; what moves is how much of the original number was the
6129
- candidate failing and how much was the measurement being wrong, and that is
6130
- worth writing down rather than quietly restating.
6131
- The remaining false positives are not near-misses. They are independent terms
6132
- the detector cannot see: **the runtime is the term** — `ATX-14`'s
6133
- `inheritedKey` is `'toString'`, and a mutated spelling is not a prototype key
6134
- at all, so the behaviour under test changes; **`src/` is the term** —
6135
- `ATX-51`'s `code` is a second copy of a string the engine emits as a literal;
6136
- and **arithmetic is the term** — `threshold + 3` builds a derived expectation
6137
- that mutation breaks.
6138
- And the four true positives fail the second test, which is the one that
6139
- decides it: **none is harmful.** `ATX-10` and `ATX-3` assert that a value was
6140
- substituted or applied, where the identity of the number is not the
6141
- obligation; `ATX-51`'s is a path prefix and `ATX-63`'s a rendering fixture.
6142
- Telling those from a mine-density band that must not move is a judgement about
6143
- whether the value *means* anything — the ground truth §0 refuses to compute
6144
- for someone else's registry. **50% precision on the shape, 0% on the harm.**
6145
- Two limits on that number, stated because a score with an unstated method is
6146
- what this file exists to prevent. The mutation has **one direction per kind**
6147
- — numbers +1, lists drop-last, strings suffixed — and direction can decide the
6148
- verdict: `ATX-40.languages` is red at 2→3 and was green at 2→1 before it was
6149
- pinned, so the true-positive count is a floor. And **recall is unmeasured**:
6150
- the eight params mutated earlier that this detector does not flag all went
6151
- red, which is eight correct negatives and not a recall figure.
6152
- The detector and its verdict table are kept in the measurement suite, with the
6153
- reach recomputed on every run and the table asserted to cover exactly what
6154
- fires — because the reach figure quoted for `compound-requirement` went stale
6155
- in this very file for want of that.
6156
- *A scoping limit on all of the above.* This repo cannot settle blocker **(2)**.
6157
- `src/` reads no param at all — every `reqs[…].params` in it is a comment or a
6158
- doc string — so params here are test vocabulary, while in the reporter's repo
6159
- they are application constants the app itself consumes. The reporter's
6160
- detector keys on exactly that difference, so it is inert here and this corpus
6161
- is evidence neither for nor against it.
6162
- *What the re-measurement did find is a different defect, and it has been split
6163
- out below* — the entry is kept whole here because the two were measured in one
6164
- pass and separating them is the finding.
6165
-
6166
- - **Whether a requirement can ever be retired, and what it costs that none can.**
6167
- The registry is at **79 requirements and 221 scenarios, 2.80 each**, and
6168
- every one is a permanent obligation: `uncovered-requirement` is an ERROR, so a
6169
- requirement without a scenario cannot exist, and nothing ever removes one.
6170
- `delta.removed` exists and `applyDelta` applies it, but `--apply` refuses to
6171
- write it back, deliberately and with the reason recorded — REMOVED "cannot say
6172
- which comments belonged to the entry it deletes". So retiring one is a manual
6173
- registry edit, which is fine as a mechanism and says nothing about whether it
6174
- is ever the right move.
6175
- *What the growth actually costs, measured on this session rather than
6176
- estimated.* One small fix took: a requirement, its scenarios, a hand-judged
6177
- row in the `OBLIGATIONS` table, two inline snapshots, a `[Unreleased]` entry,
6178
- History rows in two languages, and a regenerated `SPEC.md`. That is
6179
- proportionate for a spec framework that has to dogfood — the ceremony *is* the
6180
- product being used on itself — and it is also a number that only goes up.
6181
- *What blocks the decision, and it is not the mechanism.* It is that "retire"
6182
- has at least three meanings this file has never had to separate: a requirement
6183
- that turned out **wrong** (which should leave a record of being wrong, and
6184
- `Considered and rejected` is where that already goes); one still **true** but
6185
- no longer worth a permanently-run scenario; and one **subsumed** by a later
6186
- requirement, where the scenario should move rather than go. Only the second is
6187
- really about cost, and it is the one where deleting the intent to save the
6188
- scenario inverts what the whole tool claims — that intent outlives its tests.
6189
- Nothing here is urgent at 69. It is filed now because the moment to write the
6190
- argument down is while the reasoning is available, and because a project whose
6191
- requirement count only rises should have decided in advance what would make
6192
- one leave.
6193
- *The figures above were restated on 2026-08-08, and how they were wrong is
6194
- itself the section's own warning happening.* As filed on 2026-08-03 this entry
6195
- said "62 requirements and 160 scenarios, roughly 2.6 each" while closing on
6196
- "the number is 61" — an inconsistency inside one entry, and the closing half
6197
- was the right one. Re-measured at the entry's own commit with the current
6198
- parser: **61 requirements, 156 scenarios**, so both opening figures were wrong
6199
- on the day they were written, and only the ratio survived. Measured the same
6200
- way today it is 69 and 192, and the method is the same one `verify self`
6201
- prints — the control is that today's `self/` counted this way returns exactly
6202
- what the runner does, which is what makes the 2026-08-03 number trustworthy. The
6203
- cheap way — `grep -c 'scenario('` — answers 211 against today's true 192, and
6204
- is what would have been reached for: comments and prose count, which is the
6205
- `ATX-15` error again.
6206
- **What moved in five days sharpens the argument rather than dating it.** +8
6207
- requirements and +36 scenarios, concentrated in three sessions rather than
6208
- accrued at a rate, so nothing here is a trend line. The part worth keeping is
6209
- the ratio: 2.56 → 2.78. The cost of a requirement is not flat and it is not
6210
- falling, which is the premise the whole entry rests on, and it is the first
6211
- time this file has had two measurements of it to compare.
6212
- *A third measurement, on 2026-08-18, and it points the other way from the
6213
- second.* **79 requirements and 221 scenarios**, counted the way `verify self`
6214
- prints them, which is the control the 2026-08-08 restatement established. The
6215
- ratio runs 2.56 → 2.78 → **2.80**, so the step that carried the argument was
6216
- the first one and the second is **+0.015**, which is flat. The premise this
6217
- entry rests on now has one measurement for it and one against, and the honest
6218
- statement is the narrower one: per-requirement cost has held at about 2.8
6219
- scenarios across ten days and ten requirements. What is unchanged is the
6220
- absolute total, which only rises — that half of the argument is untouched, and
6221
- it was always the half about permanence rather than about acceleration. What is
6222
- no longer supported is the acceleration. **Reopening on cost needs the ratio to
6223
- move again**; reopening on the three meanings of "retire" needs none of these
6224
- figures, and that is still the part nobody has separated.
6225
-
6226
6726
  - **Running `verify` over a subset of the scenarios, and what a partial run may
6227
6727
  be allowed to say.** `verify` is all-or-nothing and `check` executes nothing,
6228
6728
  so there is no middle. Reported by an adoption where `verify` takes **7m33s**
@@ -6256,8 +6756,8 @@ have to gather again.
6256
6756
  slow half is smaller, so the question is a selector's ergonomics rather than
6257
6757
  one project's gates; and a scored answer to whether a scope-carrying report can
6258
6758
  be stated in a way that a `--json` consumer written before it cannot misread.
6259
- **Measured on 2026-08-12, before building anything, per this section's
6260
- discipline. The second of those two is now answered, and the answer is a
6759
+ **Measured on 2026-08-12, before building anything, per `How an entry is
6760
+ re-measured`. The second of those two is now answered, and the answer is a
6261
6761
  refusal; the first blocker was wrong; and the whole feature turns out to be a
6262
6762
  different, much cheaper one.**
6263
6763
  *The cost claim reproduces on a second corpus.* `verify self`: the static half
@@ -6355,197 +6855,94 @@ have to gather again.
6355
6855
  and the second report of the same shape, which this measurement does not
6356
6856
  supply and does not replace.
6357
6857
 
6358
- - **An outward-facing form of the judgement tables, so an adopter can run this
6359
- counter-pressure on their own registry.**
6360
- `tests/intent-rule-candidates.spec.ts` maintains four hand-judged tables over
6361
- this repo's registry and enforces their completeness. Design §11 records the
6362
- properties they defend and that nothing enforces any of them; what the tables
6363
- add is a counter-pressure holding for **one** registry whose authors agreed to
6364
- maintain it. An adopter got none of it, and the reason was sharper
6365
- than "nobody wrote it for them": the package exported `requirement`,
6366
- `scenario`, `defineRequirements`, `delta` and types, and nothing else.
6367
- `parseSpecs`, `findFiles` and `readRegistrySource` were internal, so an
6368
- adopter **could not enumerate their own scenario names** the table's central
6369
- rot check, that a claimed scenario is one a spec really declares, was not
6370
- merely unwritten downstream but unreachable. **That half shipped under
6371
- `[0.9.2]` as `@am_shork/attest/inspect`** and is recorded at the foot of
6372
- this entry; what stays here is the schema built on top of it, and the three
6373
- blockers below are untouched by the export.
6374
- *The shape that makes it shippable at all is a split, not a feature.* The
6375
- engine can compute which ids exist, which statements match the universal
6376
- quantifier regex, which params are list-valued, and whether a named scenario
6377
- is declared all four already, all statically. It cannot compute how many
6378
- obligations a requirement carries, what a quantifier ranges over, or whether a
6379
- scenario iterates a param. So the shippable object is **the enforcement, never
6380
- the judgement**: an opt-in `*.judge.ts` read by the same static reader and
6381
- under the same literal-only rule the registry is (it is metadata about
6382
- requirements under review, so `check` must execute nothing to read it), whose
6383
- contents are entirely the adopter's. Opting in is creating the file; once it
6384
- exists, `requirement-unjudged`, `orphan-judgement`, `unbound-site` and
6385
- `quantifier-unjudged` make it complete and keep it from rotting. The
6386
- asymmetry is the only way this can exist without Attest asserting a ground
6387
- truth it refuses to compute (§0), and it is the same asymmetry `never-red`
6388
- already runs on: the archive gate would owe judgement only for ids a change
6389
- ADDs, so a brownfield adoption is not blocked wholesale.
6390
- *One part of the existing table cannot ship in any form.* `DOMAINS.pin` comes
6391
- in two kinds a scenario name, checkable against the AST, and a **source-text
6392
- fragment** grepped out of the spec file. The second is asserting on the
6393
- wording of somebody's test, which is the `Issue.message` category error under
6394
- another name. Only the scenario-name kind is exportable, and what that costs
6395
- is real: the grep is what caught a deleted pin that `toContain` had passed.
6396
- *Why no detector ships with it, and this is the part that is already
6397
- measured.* Three rule families were built and scored against the whole corpus
6398
- (86 entries, counting a fixture id once per registry file) before any was
6399
- written into the engine, which is the order `divergent-param` was originally
6400
- designed in the wrong way round. Every figure below is a quotation of the
6401
- suite's inline snapshots; the snapshot is the fresh copy, and this paragraph
6402
- is only ever as fresh as its last edit.
6403
- **(1) `compound-requirement`** 34 requirements carry more than one
6404
- obligation, it flags 16, is **wrong about none**, and misses 18. Never wrong
6405
- and nearly blind, and the 18 are the form the coverage incentive actually
6406
- rewards: extra clauses under a single keyword. **(2) The widened variant** —
6407
- 29 correct, but 14 wrong and still 5 missed, and it is silenced by deleting an
6408
- "and". A rule whose cheapest fix degrades the artifact it protects does not
6409
- ship, and its precision falls as the registry grows. **(3) `divergent-param`**
6410
- 5 divergences on this repo, **all 5 judged wrong**, zero true positives ever
6411
- recorded. The `code` group now spans 19 ids, and the failure is structural
6412
- rather than a matter of naming: the key is built by discarding the evidence of
6413
- relatedness, so the one genuinely paired divergence in the group is the one it
6414
- cannot show, and the key has churned three times under `flag` on additions
6415
- that changed no verdict. A fourth candidate the echo detector, scored at
6416
- half precision belongs to the tautology entry above and is not re-quoted
6417
- here, because a figure stated in two places goes stale in one. Taken together
6418
- these are the measured range of what a token or single-body detector over
6419
- prose can do here, and they are why the proposal is enforcement-only.
6420
- *What blocks the decision three things.* **(1) One adapter.** This
6421
- repository's table is the only one that exists, and its author wrote both the
6422
- schema and every answer in it. By this project's own rule a second table is
6423
- what makes the seam real, and freezing a `Judgement` type now is deciding for
6424
- everyone from 34 requirements written by one person. **(2) The breaking form
6425
- of this was already rejected** see *Countable obligations* below and one
6426
- of its three reasons survives the separate-file shape intact: **nothing can
6427
- force the count to be true.** The other two do not survive, which is what is
6428
- new here: a separate artifact touches no `--json` field, no delta apply and no
6429
- `render` output, and completeness *across ids* is computable in a way
6430
- completeness *within* an obligation array never was. So this is a re-proposal
6431
- with one of three objections answered and the sharpest one standing.
6432
- **(3) `quantifier-unjudged` reuses a rejected detector as its trigger.**
6433
- Firing on the quantifier token was rejected below at 43% reach and 70% wrong;
6434
- the claim here is that the same regex is acceptable when it asks a question an
6435
- author discharges in one line (`over: 'values'`) rather than issuing a
6436
- verdict. That claim is untested on anyone who did not also write the regex.
6437
- *The half that is not blocked has shipped, under `[0.9.2]`.*
6438
- `@am_shork/attest/inspect` is one function answering what a project declares —
6439
- AST-only, no runner peer, landing on the existing static/executing boundary.
6440
- It ships no schema and decides nothing, which is why it could go ahead of the
6441
- three blockers rather than waiting on them. What it changes about this entry
6442
- is the *kind* of thing that is now missing: the mechanism is no longer
6443
- unreachable, only unwritten, so a second table is something an adopter can
6444
- build rather than something only this repository could.
6445
- *What would decide it:* one judgement table maintained by someone who did not
6446
- design it. If it comes back the same shape, the seam is real and the schema
6447
- follows; if it comes back different, `inspect` was the whole answer and the
6448
- schema never needs to exist.
6449
- **Nothing currently makes that table arrive, which is a separate problem from
6450
- the three blockers and the one that gates all of them.** Raised in a product
6451
- review of `[0.9.1]`–`[0.9.3]` on 2026-08-19. The deciding evidence is an
6452
- artifact only an adopter can produce, and the path to producing it is: a row in
6453
- the README's document table, then a 214-line page, then a hand pass over four
6454
- questions, then writing the two mechanical properties from a prose
6455
- description, then choosing to report back. Five voluntary steps with no
6456
- scaffold at any of them, against a decision this entry has been waiting on
6457
- since `[0.9.2]`. An entry blocked on evidence that no shipped surface asks for
6458
- is blocked indefinitely, and the wait is not itself evidence of anything.
6459
- *The proposal is scaffolding the mechanism while still withholding the answer,*
6460
- which is the same split `[0.9.2]` already made and not a retreat from it: an
6461
- `examples/` directory holding the completeness and rot-binding assertions with
6462
- **no columns** — the enumeration that forces a row, and the check that a named
6463
- scenario is one `inspectProject` really declares — and the judgement columns
6464
- left for the adopter to add. What the page withholds is this repository's four
6465
- columns, and that stays withheld; what it currently also withholds is the two
6466
- properties it names as *mechanical*, and those are the half where a copy is not
6467
- an echo, because their content is fixed and the fourth finding does not apply
6468
- to them.
6469
- *What blocks it.* Whether a skeleton with no columns is still a shape that
6470
- comes back as a copy — the page's argument is that a template makes the answer
6471
- return its own question, and it is untested whether an adopter reads an empty
6472
- frame as an invitation or as a form to fill in. And it is a second artifact
6473
- claiming to be runnable consumer usage of the public API, which `pre-commit-check`
6474
- §6 already records as the category no gate reaches: the page's three samples
6475
- are unasserted today, and a fourth in a directory that looks executable would
6476
- be worse than a fenced block, not better, unless something runs it.
6477
-
6478
- - **Prefix ownership is inferred rather than declared, and the cost arrives only
6479
- when adoption succeeds.** Design §11 records that the id grammar has no level
6480
- above the requirement: the prefix is a convention, nothing allocates it, and
6481
- `duplicate-prefix` can only report a collision that already happened.
6482
- spec-kit's answer is a roadmap table — a stable id, an intent, a scope
6483
- boundary, dependencies — authored above the specs it decomposes into. That
6484
- comparison is stated here rather than referred to, because the entry that
6485
- introduced the spec-kit reading has since been built and now sits under
6486
- `[0.9.4]` as the `open` field: an entry whose evidence rests on a
6487
- neighbour is one a promotion silently empties.
6488
- *Two things have changed since that reading, and they point opposite ways.*
6489
- The proposal is **smaller** than it looked: the mechanism partly exists.
6490
- `loadRegistry` returns `prefixOwners` — first claim in sorted file order —
6491
- `merge.ts` uses it to decide which file an ADDED id belongs in, and
6492
- `apply-no-prefix-owner` already refuses a prefix nobody owns. So what is
6493
- proposed is declaration replacing inference, not a new layer. And it is
6494
- **weaker**: `/inspect` shipped in `[0.9.2]`, so the cross-repository half —
6495
- the one §11 says no command can see across — is now something an adopter
6496
- computes from two registries in about ten lines. Attest does not have to own
6497
- it to make it possible.
6498
- *What blocks the decision:* nobody has the problem. §11 states the cost
6499
- arrives "exactly when adoption succeeds: two teams sharing a registry have
6500
- someone owning prefix allocation by hand", and no such adoption has been
6501
- reported. Building the declared form now would be deciding prefix semantics
6502
- for everyone from one repository whose five prefixes — `ATX` and the four
6503
- fixture ones — are each declared by exactly one file, so the allocation
6504
- question this would answer has never been asked here.
6505
-
6506
-
6507
- - **A merged requirement carries no trace of the change that produced it, and
6508
- the rename chain is the part actually lost.** `RequirementSchema` is
6509
- `statement`, `rationale`, `params`, `outOfScope`; the splice writes those and
6510
- nothing else, so after `--apply` no field and no sidecar records which change
6511
- introduced an id, which modified it, or that one id used to be another. The
6512
- delta that knew is in `archive/`, which nothing reads. Proposed from
6513
- OpenViking's `.relations.json`: links between memories live in a
6514
- directory-level sidecar, deliberately kept out of the content, and are
6515
- rendered on demand at retrieval time (`docs/design/memory-link-design.md`).
6516
- *The argument for the sidecar over a field is in their competitor analysis
6517
- rather than their design.* The same document reads GBrain, which writes links
6518
- into the page body, and records what that costs: rename a page and every
6519
- inbound link still points at the old slug, with fuzzy matching at the read end
6520
- as the only mitigation. A `supersedes:` or `introducedBy:` field on a
6521
- requirement would be that mistake in this repository's terms — the registry is
6522
- a literal and a single source (§2), and a field naming a folder that a later
6523
- rename can move is a second source with nothing comparing it against the
6524
- first. A derived index has no such copy to go stale: it is recomputed from the
6525
- archived deltas every time it is read.
6526
- *What blocks the decision, restated on 2026-08-19 because half of what was
6527
- filed here is no longer true.* This entry was blocked on the empty archive and
6528
- on the corpus being rename-free, and only the second still holds. `archive/`
6529
- now has a real change in it — the `open` field, put through the workflow the
6530
- same day — so the store this would index exists. Its delta carries `added` and
6531
- nothing else, measured rather than assumed, so a backlink index is still
6532
- testable only against fixtures for the operation it is actually about. Note
6533
- what that says about the odds of the blocker clearing on its own: `--apply`
6534
- refuses RENAMED outright, so no change this repository archives can ever
6535
- exercise a rename until that refusal is lifted, and lifting it is a separate
6536
- decision with its own reason (`splice.ts` — deleting an entry has no smaller
6537
- span to fall back to). The blocker is therefore not "waiting for a rename to
6538
- happen"; it is downstream of a refusal nobody has proposed removing. And one
6539
- question the OpenViking reading does not answer, which is whether the chain is
6540
- wanted at all. §7 applies
6541
- `RENAMED` to a `Map<id, Requirement>` and the old id is then simply gone —
6542
- deliberately, as far as anything written says. The case for recovering it is an
6543
- external reference that outlives the rename, a ticket or a commit message
6544
- 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.
6545
6941
 
6546
6942
  - **Changing the value of an existing param passes no gate, and for seven of
6547
- them there is no record either.** Split out of the tautology entry at the
6548
- 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
6549
6946
  reframing paragraph. It is a different question: that entry asks whether an
6550
6947
  assertion is a real comparison, this one asks what happens when the number
6551
6948
  itself moves, and neither answer settles the other. `archive` gates a
@@ -6580,203 +6977,288 @@ have to gather again.
6580
6977
  here disputes that or needs a second copy of the values. The observation is
6581
6978
  narrower: the **edit** is unremarkable to every gate, which is a different
6582
6979
  claim from the values being unguarded.
6583
- *What blocks the decision and the first is the one that blocks the entry this
6584
- came from.* **(1)** Frequency is unmeasured, and the one corpus anybody has
6585
- looked at points at zero: across 8 commits touching `*.reqs.ts` in the
6586
- reporting repository, no param value was ever modified — every `params:` line
6587
- in every diff is an addition. A defect nobody has committed is a hazard rather
6588
- than a report. **(2)** No shape is obvious. A param edit is not a change, so
6589
- `archive` is the wrong place; making `check` notice one needs a previous value
6590
- to compare against, which is the rejected snapshot wearing a different hat. The
6591
- only mechanism needing no stored copy is review, and review already sees the
6592
- `SPEC.md` diff for 82 of the 89. **(3)** What is left after that is seven
6593
- params in one repository, and the right answer for the five that matter may be
6594
- nothing more than a line in `intent-quality.md` the four things no gate is
6595
- looking at already being that page's subject, and this being a fifth of the
6596
- same kind.
6597
-
6598
- - **`/inspect` returns five exported shapes and the page telling people to build
6599
- on them says nothing about whether they move.** Filed 2026-08-19 from a product
6600
- review of `[0.9.1]`–`[0.9.3]`. `inspect.ts` exports `Inspection`,
6601
- `ScenarioEvidence`, `EvidenceRow`, `ArchivedChangeRef` and
6602
- `RedEvidenceInspection`, and `intent-quality.md` carries three samples
6603
- destructuring them. `SCHEMA_VERSION` governs the `--json` envelope and nothing
6604
- governs these.
6605
- *The policy is not missing its statement is.* The Versioning section at the
6606
- head of this file already binds them: removing or renaming a public API bumps
6607
- the minor, and under 0.x that is the breaking bump. So a reader who finds that
6608
- section is already told what they need. The gap is that the reader this export
6609
- was written for arrives through `intent-quality.md`, which is a method page
6610
- reached from the README's document table, and nothing on the path between the
6611
- two states it.
6612
- *Why it is worth a decision rather than a one-line edit.* The two candidate
6613
- sentences say opposite things to the person this needs. Restating the existing
6614
- promise reads as a stability guarantee and is the honest summary of current
6615
- policy; saying the shape may move before 1.0 is the more useful warning and
6616
- discourages exactly the adopter whose table would decide the judgement-schema
6617
- entry above. Which one is right depends on the 1.0 entry below, which is why
6618
- this is filed rather than fixed.
6619
- *One thing already measured, in the other direction.* `pre-commit-check` §6
6620
- records that changing what `/inspect` returns breaks three usage samples that
6621
- no gate can see. The internal awareness of this surface's fragility therefore
6622
- exists and is written down; the outward-facing half is what does not.
6623
-
6624
- - **The store worth retrieving is this file, not `archive/`, and no shape has
6625
- been proposed for either.** Filed 2026-08-19 from a product review of
6626
- `[0.9.1]`–`[0.9.3]`, and it is the OpenViking reading turned back on the
6627
- repository that did the reading. Everything that came out of it is about
6628
- `archive/`, and the entries are no longer counted here — the number was wrong
6629
- on the day it was written, was corrected on 2026-08-19, and was wrong again by
6630
- the next commit, when the precedence rule left for design §11. Three strikes on
6631
- one figure is the section's own preamble arriving in the first person: a count
6632
- kept in prose with nothing to check it goes wrong quietly, and the repair is to
6633
- stop keeping one rather than to keep fixing it. But `archive/` holds `first-run.json` — typed,
6634
- low-information, and now read beside a `proposal.md` that in this repository
6635
- carries a link, for the reason `[0.9.3]` gives: `CLAUDE.md` makes this file the
6636
- single decision record. **So the store that actually holds decisions with their
6637
- evidence attached is `CHANGELOG.md`, at 7,100 lines with the three foot
6638
- sections past 1,700 of them,** and the property that makes it valuable a
6639
- design evaluated, priced and killed, with the reasoning that killed it — is
6640
- exactly the property that makes it unreadable in one pass. It is append-only
6641
- with no retrieval, which is the shape the OpenViking reading was about.
6642
- *What an agent would actually consume is a composition, not a new store.*
6643
- Given an `ATX-n`: its statement and params, the scenarios covering it, the
6644
- first-run evidence for each, and the paths to the change that introduced it.
6645
- Four of the five are already computable `inspectProject` answers two,
6646
- `inspectRedEvidence` the third and the archived refs the fourth and the fifth,
6647
- the reasoning, is the one with no index and no key. That asymmetry is the
6648
- finding: the retrieval problem is not in the half that was just made readable.
6649
- *One thing to unmerge before deciding.* `[0.9.3]` settled a question against
6650
- the fifth static command, and it settled it correctly and on the right
6651
- grounds `first-run.json` is JSON, and `ATX-16`'s subject is reading
6652
- TypeScript without evaluating it, so joining that roster would turn a security
6653
- promise into a list of commands that do not run the suite. But that is a
6654
- ruling about **roster membership**, and it is being carried as though it also
6655
- ruled on whether such a command should exist. Those are separate, and only the
6656
- first was argued.
6657
- *What blocks the decision, and what would cheaply unblock it.* No measurement
6658
- exists of whether a bundle like this changes what an agent does — the one datum
6659
- quoted in `[0.9.3]` measured demand to read the archive **as prose**, which is
6660
- a third question again. The proposal is therefore a probe and not a build,
6661
- per this section's own discipline of re-measuring before implementing: assemble
6662
- the bundle by hand for one requirement, run one real change against it, and
6663
- record whether it was used, ignored, or used wrongly. One session, and it
6664
- decides between a command, a skill surface, and nothing. Building any of the
6665
- three before that measurement is the order `divergent-param` was designed in
6666
- and the order this file has already paid for once.
6667
-
6668
- - **A stage-1 spec can read its params through `delta.added` instead of by id,
6669
- and the merge that repoints its import cannot repoint that.** Filed 2026-08-19
6670
- from the first change this repository put through its own `changes/` workflow,
6671
- where it was written by the author of the workflow's own tooling which is
6672
- most of why it is worth an entry rather than a fix.
6673
- `delta()` returns the delta *and* the registry of what it adds
6674
- (`withProposedRequirements`), so `reqs['ATX-81'].params.field` reads a proposed
6675
- requirement before the merge and a merged one after it. That is the whole
6676
- reason merging a spec changes its import and nothing else. Reaching through
6677
- `added` `d.added['ATX-81'].params.field` is equally correct during stage 1
6678
- and throws the moment `--apply` repoints the specifier at the registry, because
6679
- a registry has no `added`.
6680
- **Nothing catches it, and the reason is structural rather than an oversight.**
6681
- The archive gate runs *before* the rename, so the spec it graded is not the
6682
- spec the merge commits; `check` executes nothing, so it never evaluates the
6683
- accessor; and the type checker is satisfied on both sides of the merge only
6684
- because it is run against whichever half is currently on disk. It surfaced at
6685
- the next `verify`, as `spec-load-failed` a correct diagnosis of the symptom
6686
- that names the file and not the cause. The window between `--apply` and the
6687
- next `verify` is the whole exposure, and in a workflow that ends at `--apply`
6688
- it is unbounded.
6689
- **The guidance already exists and did not prevent it, which is the finding.**
6690
- `registry.ts`'s doc comment on `delta` states the intended expression and the
6691
- rejected one, in those terms, on the function itself; the document `init`
6692
- writes shows the by-id form in its worked example. So this is not a gap in what
6693
- is written. It is a case of the correct form and an incorrect one being
6694
- *indistinguishable at the moment of writing* both compile, both pass, both go
6695
- red for the right reason with the difference appearing one command later.
6696
- *Two shapes, and they point opposite ways, which is why this is filed rather
6697
- than fixed.* **(1)** A `check` diagnostic: `parser.ts` already walks property
6698
- accesses inside spec bodies to find `.params`, so finding `.added` on a binding
6699
- imported from a `requirements.delta` path is the same walk and the same file.
6700
- It has to be a WARNING, because enumerating what a change adds is a legitimate
6701
- thing for a spec to do, and a WARNING is exactly the strength that did not stop
6702
- the author here. **(2)** A type-level refusal: have `delta()` return a type
6703
- without `added`, so `typecheck:all` refuses it with no new diagnostic, no
6704
- roster entry and no runtime cost. `DefinedDelta<T> = T & ProposedRequirements<T>`
6705
- carries `added` because `T` does, so this means `Omit<T, 'added'>` and that
6706
- breaks the evaluating loader, which hands the same default export to
6707
- `applyDelta` typed as `RegistryDelta`. Whether that break is real or just needs
6708
- the internal path to name a different type is **unmeasured, and is the first
6709
- thing to establish**; it decides between the two shapes rather than being a
6710
- detail of one.
6711
- *What blocks the decision:* frequency, and it is the same blocker the `open`
6712
- entry carried before it one occurrence, in one repository, on the first
6713
- change ever run through the workflow. That is a report rather than a
6714
- hypothetical, which is more than most entries here start with, and it is still
6715
- n=1. What would settle it cheaply is the next change: if the by-id form is
6716
- written without prompting, the guidance is sufficient and this is an anecdote;
6717
- if the reach-through appears again, the two shapes above are worth pricing.
6718
-
6719
- - **Nothing is frozen, and the evidence this repository is waiting for requires
6720
- somebody to build on something.** Filed 2026-08-19 from a product review of
6721
- `[0.9.1]`–`[0.9.3]`. Three releases in five days each added public surface, and
6722
- as of `[0.9.3]` the CLI, `./define`, `./inspect`, the archive layout, the
6723
- judgement schema and any retrieval surface are all simultaneously open. Every
6724
- entry in this section that is blocked on an adopter the judgement table above
6725
- being the sharpest is asking someone to invest in a surface that is
6726
- documented as movable. That is a coherent thing to ask under 0.x and it is also
6727
- the reason the ask may never be answered.
6728
- *The proposal is a line rather than a milestone:* 1.0 freezes the CLI, `./define`
6729
- and `./inspect`, and everything currently blocked on an adopter's evidence
6730
- the judgement schema, the backlink index, any archive retrieval is explicitly
6731
- 1.x. What that buys is not a version number; it is the only condition under
6732
- which "build your own counter-pressure on our export" is a reasonable thing to
6733
- ask of somebody.
6734
- *What blocks it.* Freezing `./inspect` five days after shipping it is freezing
6735
- on one adapter, which is the objection this repository raises against every
6736
- other premature interface and does not get to waive for its own. And the
6737
- release-notes discipline cuts both ways here: a 1.0 is a claim about what the
6738
- cost of adoption will be, and `Planned`'s own preamble records that **what a
6739
- release costs its adopters is not known on the day it is cut**. There is no
6740
- measurement to date of anyone adopting either export.
6741
-
6742
- - **When one requirement carries several scenarios, some of those sets are
6743
- differential evidence and nothing in the model says which.** Prompted by an
6744
- external write-up (`columns.chicken-house.net`, 2025-11-10, "AI-First Testing
6745
- Workflow"), which ran one test case against a system's REST API and against its
6746
- web UI and found the empty-basket check implemented in the UI and absent from
6747
- the API. Neither run failed against its own expectation; the finding was that
6748
- two runs of the *same* case disagreed.
6749
-
6750
- Attest already permits the arrangement a `reqId` takes any number of
6751
- attesting scenarios, and `cover` asks only for ≥1. What it cannot see is the
6752
- difference between three scenarios covering three edges of one requirement and
6753
- three scenarios crossing three interfaces at the *same* edge. The second set
6754
- can be asserted to agree; the first cannot, and there is nothing to compare.
6755
- This repository runs the pattern on itself `tests/static-registry.spec.ts`
6756
- asserts the two registry readers agree so the structure is known to work
6757
- where the two adapters are known to be two adapters.
6758
-
6759
- *What blocks it, in the order that decides it.* **The seam is unmeasured.** One
6760
- adapter is a hypothetical seam and two is a real one; nothing has been walked
6761
- to establish whether any adopted registry has a `reqId` whose scenarios
6762
- genuinely cross interfaces rather than covering one surface repeatedly. Until
6763
- that is counted this is a pattern observed in somebody else's codebase, and it
6764
- is cheap to count. **The fact is not in the model.** Which interface a scenario
6765
- crosses is recorded nowhere the engine reads, so supplying it means new
6766
- registry or spec surface a per-scenario axis paid for by every adopter
6767
- including all of the ones with a single interface, in the shape of a thing they
6768
- must learn not to write. Whatever carried it would also have to be a literal in
6769
- the AST, because `cover` executes nothing and that is not negotiable for it.
6770
- **There is no verdict to emit.** The engine cannot perform the comparison;
6771
- agreement between two runs is an assertion and assertions belong to the test.
6772
- The most it could report is that a requirement has scenarios on more than one
6773
- axis, which is information with no pass or fail attached a new output
6774
- category rather than a new check. **And the prose alternative is untried.**
6775
- Apply the deletion test before the field: if the workflow document `init`
6776
- writes (§9) simply says that two scenarios crossing one requirement at
6777
- different interfaces should assert the same expectations, does the practice
6778
- happen anyway? If it does, the engine gained nothing and charged everyone a
6779
- field for it.
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.
7038
+
7039
+ - **The intent layer is separable from the repository it describes, and the
7040
+ verification layer is not so a third party can audit intent and never
7041
+ obtain a verdict.** Filed 2026-08-22 from a design evaluation of using Attest
7042
+ as an external audit tool that touches none of the auditee's implementation.
7043
+ The separability is already bought and is stronger than it looks: §2 requires
7044
+ a registry to be a *literal*, no imported constant and no computed value, so
7045
+ `*.reqs.ts` names nothing in the project it is about and is a file another
7046
+ party could own outright. The four static commands execute none of the
7047
+ auditee's code, which is the posture an auditor needs and is already the
7048
+ documented reason for the split. The arrangement that follows is a two-party
7049
+ contract with `check` as its interface: the auditing party writes the SHALL,
7050
+ the developing party owes each id a scenario, and `uncovered-requirement` and
7051
+ `orphan-test` are the two directions that contract can be broken in.
7052
+
7053
+ *What blocks it, in the order that decides it.* **The verdict half cannot
7054
+ cross the boundary, and that is §1 rather than a gap.** `verify` and `archive`
7055
+ run the suite, the suite's specs import the implementation, and the environment
7056
+ those specs need is the auditee's reachable only by trusting their
7057
+ `--vitest-config`. So what a decoupled auditor can obtain is the readable
7058
+ obligations and never `tests-red`, which is the same asymmetry `status` already
7059
+ carries and states about itself. That would be tolerable if the static report
7060
+ read as procedural, and it does not: an audit reader takes a full-coverage
7061
+ `check` as a claim about behaviour, while §11 records that coverage is binary,
7062
+ that an assertion can echo the param it is measuring, that shortening a
7063
+ list-valued param silently removes cases, that a quantifier is covered only as
7064
+ far as hand-written scenarios reach, and that two contradictory ids under
7065
+ different prefixes both pass. **The mechanism assumes one tree.** `locate.ts`
7066
+ scans a single project root, and mechanism 1 needs the spec to `import` the
7067
+ registry, so the externally-owned file has to be physically in the tree before
7068
+ any command runs a checkout step, not a flag. Naming a registry outside the
7069
+ root would be a second answer to where intent lives, which is the shape this
7070
+ repository refuses elsewhere. **The flat ID space bills exactly here.** §11
7071
+ already records that nothing allocates a prefix and that the cost arrives when
7072
+ adoption succeeds; two parties sharing one registry *is* that case, and
7073
+ `duplicate-prefix` can only report a collision after both files were read.
7074
+ **And `--apply` writes into the other party's file.** The merge splices ADDED
7075
+ requirements into the registry, so the one command that edits a file the user
7076
+ cannot regenerate would be editing a document whose owner is not in the
7077
+ repository. Nothing about that is unsafe; it is that the ownership the
7078
+ arrangement exists to establish is the thing the merge step quietly crosses.
7079
+
7080
+ *What would settle it, and what it costs to try.* The whole arrangement is
7081
+ derivable from the design and has been run by nobody, so this is n=0 and cheap
7082
+ to move: the checkout-step form needs no engine change at all, and one
7083
+ adopter running it would say whether the two-party report is read as procedural
7084
+ or over-read as a behavioural claim. That is the measurement, and it is a
7085
+ question about the reader rather than about the code.
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 stricterits 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.
6780
7262
 
6781
7263
  ## Considered and rejected
6782
7264
 
@@ -6788,6 +7270,814 @@ it sat between 0.2.0 and 0.1.7 for two releases, where standing still meant
6788
7270
  sinking one version deeper each time a release was cut above it, and a rejection
6789
7271
  filed under a version reads as belonging to it.
6790
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
+
7878
+ ### An id-keyed retrieval bundle over `archive/` and this file
7879
+
7880
+ Proposed 2026-08-19 inside the entry now titled *The decisions worth retrieving
7881
+ are in this file*: given an `ATX-n`, assemble its statement and params, the
7882
+ scenarios covering it, the first-run evidence for each, and the paths to the
7883
+ change that introduced it — the reasoning being the fifth part and, as filed,
7884
+ "the one with no index and no key". The entry declined to build it and specified
7885
+ a probe instead: assemble the bundle by hand for one requirement, run one real
7886
+ change against it, and record whether it was used, ignored, or used wrongly.
7887
+ **One session, and it decides between a command, a skill surface, and nothing.**
7888
+
7889
+ Run on 2026-08-28 on `ATX-79`, against the `examples/judgement-table/` change
7890
+ shipped in the same pass. **The answer is nothing.**
7891
+
7892
+ **Four of the five parts came back in one script**, as the entry predicted:
7893
+ statement, params and `outOfScope` from `inspectProject`; three scenarios with
7894
+ their evidence states and four archived change refs from `inspectRedEvidence`.
7895
+ No new surface was needed to assemble it. A command would therefore be a wrapper
7896
+ over two functions that already ship, which is the shallow-module case rather
7897
+ than a retrieval mechanism.
7898
+
7899
+ **The row that did the work is a plain field, and not the one this was about.**
7900
+ `outOfScope: ['judging the quality of what it reports', …]` settled the change's
7901
+ central design question — ship the enforcement, withhold the columns — and it
7902
+ has been a field of `inspectProject` since `[0.9.2]`. What the composition added
7903
+ was that it was *present at the moment of the decision*, which is a claim about
7904
+ assembly rather than about a missing store, and assembly is what a caller with
7905
+ two function calls already has.
7906
+
7907
+ **The evidence half is structurally empty for most ids here.** All three rows
7908
+ came back `unarchived`, which is correct — `ATX-79` was written straight into
7909
+ the registry — and which `ATX-80` defines as **not a finding**. So for any
7910
+ brownfield id that quarter of the bundle carries nothing, and brownfield is most
7911
+ of this registry. The bundle's information content varies enormously by which id
7912
+ is asked about, which nothing in the proposal anticipated.
7913
+
7914
+ **And the fifth part is not where the proposal placed it.** The reasoning *for a
7915
+ requirement* is keyed twice over: `rationale` is a field of `inspectProject`, so
7916
+ it was already part one, and design §10 carries the argument under a section
7917
+ number. What `CHANGELOG.md` contributed to this bundle was **two grep hits for
7918
+ `ATX-79`, neither of them about `ATX-79`** — one a parenthetical inside
7919
+ `[0.9.2]` about a different requirement's import closure, the other a false
7920
+ positive inside the tautology entry, where `ATX-79.subpath` appears as an
7921
+ example of a detector firing wrongly. Zero useful rows and two misleading ones.
7922
+
7923
+ So what this rejects is not only the command but the **key**. A design that was
7924
+ evaluated and killed has no `ATX-n`, so an id-keyed index over the decision
7925
+ store returns the entries that were not rejected — the opposite of the content
7926
+ the original entry correctly identified as the valuable part. That half is not
7927
+ rejected and stays open above, without a key and without a proposed mechanism.
7928
+
7929
+ *Labelled for what it is.* n=1, one requirement, one change, and the person who
7930
+ assembled the bundle is the person who made the change — so "was it used" is a
7931
+ self-report, and the honest reading is that one row was load-bearing and one
7932
+ quarter was empty by construction. That is a thin basis for building something
7933
+ and an adequate one for not building it, which is the asymmetry the probe was
7934
+ specified to exploit.
7935
+
7936
+ ### A retrieval mechanism over this file's decision sections
7937
+
7938
+ Proposed inside *The decisions worth retrieving are in this file*, which stayed
7939
+ open on the grounds that **no key had been proposed, and that was the whole of
7940
+ the problem rather than a step before it**. The entry specified the inverse of
7941
+ the probe that killed the id-keyed bundle above: take an entry from this section
7942
+ that was re-proposed and caught here, and ask what somebody would have had to
7943
+ type to find it before proposing it again. One session, no build. It was run on
7944
+ 2026-08-28 and it produced a key — which already exists, in this file, and is the
7945
+ reason nothing needs building.
7946
+
7947
+ *The store is 24 entries, not 8,300 lines.* That is the measurement the entry's
7948
+ premise was missing. `Considered and rejected` holds **24 `###` entries over
7949
+ 1,086 lines**, and every plausible search term a re-proposer would reach for
7950
+ narrows to a handful: `obligations` → 2 entries, `judge` → 5, `compound` → 2,
7951
+ `countable` → 1. Grep over one section already retrieves. An index would be a
7952
+ second copy of a table of contents the headings are.
7953
+
7954
+ ***And 23 of the 24 headings name the artifact or the mechanism*** — `zod/v4-mini`,
7955
+ `isSafeChangeName`, `typescript` as a peer dependency, a freshness gate on the
7956
+ file `init` writes, grouping `render`'s output by prefix. A proposer arrives
7957
+ holding the artifact they are about to touch, which is why the heading is the
7958
+ key: it is the one field written in the vocabulary the next person will have.
7959
+
7960
+ *The exception is the finding.* The single heading that names a **problem**
7961
+ rather than an artifact — *Nothing resists a bloated requirement, and nothing
7962
+ resists a duplicated one* — is the entry that was re-proposed and caught, as the
7963
+ judgement-schema entry's blocker (2). The second documented catch, the quantifier
7964
+ detector reused as `quantifier-unjudged`, is filed under *An engine rule for a
7965
+ statement whose scenarios do not span its quantifier*, which is problem-shaped
7966
+ too. The one artifact-shaped heading anybody has been recorded checking against —
7967
+ *A committed snapshot of the params* — was found by its re-proposer unaided, and
7968
+ they said so in the entry. Four events, so this is a correlation and not a rate;
7969
+ what makes it worth recording is that it points at a rule with no mechanism
7970
+ behind it.
7971
+
7972
+ *So what replaces the proposal is a sentence about headings, not a feature.* A
7973
+ rejection is filed under the name of the thing it declined to build. That is
7974
+ already true of 23 of 24 and needs enforcing by nobody, because it is what an
7975
+ author writing a heading naturally does when the thing has a name — the
7976
+ exception is the entry that declined **three** mechanisms at once and had no
7977
+ single artifact to name, which is the case to watch rather than a rule to state.
7978
+
7979
+ *What the probe also confirmed, in passing, is the claim the entry rested on.*
7980
+ **15 of the 24 rejected entries carry no `ATX-n` at all**; the nine that do
7981
+ mention one as evidence inside an argument about something else. A design that
7982
+ was evaluated and killed has no id because nothing was built to carry one, so an
7983
+ id-keyed index retrieves precisely the entries that were *not* rejected. That
7984
+ half of the entry was right and is now measured rather than reasoned.
7985
+
7986
+ *What is not solved, and it is next door.* `Under consideration` has no headings
7987
+ at all — its twelve entries are bullets whose first bolded sentence is the only
7988
+ handle — so the key found here exists for one of the three foot sections and not
7989
+ for the section most likely to be read before a proposal. Nothing was changed
7990
+ about that here: restructuring twelve open entries is a larger edit than the
7991
+ finding justifies, and the finding is only four events old.
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
+
8004
+ ### A per-scenario axis recording which interface a scenario crosses
8005
+
8006
+ Prompted by an external write-up (`columns.chicken-house.net`, 2025-11-10, "AI-First
8007
+ Testing Workflow"), which ran one test case against a system's REST API and
8008
+ against its web UI and found the empty-basket check implemented in the UI and
8009
+ absent from the API. Neither run failed against its own expectation; the finding
8010
+ was that two runs of the *same* case disagreed. Attest already permits the
8011
+ arrangement — a `reqId` takes any number of attesting scenarios — but cannot
8012
+ tell three scenarios covering three edges of one requirement from three
8013
+ scenarios crossing three interfaces at the *same* edge. The second set can be
8014
+ asserted to agree; the first has nothing to compare. The proposal was to put
8015
+ that fact in the model, as a per-scenario axis that would have to be a literal
8016
+ in the AST, because `cover` executes nothing.
8017
+
8018
+ Filed 2026-08-22 with four blockers, and rejected on 2026-08-28. **The blocker
8019
+ that decides it needed no measurement, and the one that was measured came back
8020
+ at the floor.**
8021
+
8022
+ **There is no verdict to emit, and that is fatal on its own.** The engine cannot
8023
+ perform the comparison: agreement between two runs is an assertion, and
8024
+ assertions belong to the test. The most any axis could report is that a
8025
+ requirement has scenarios on more than one of them — information with no pass or
8026
+ fail attached, a new output *category* rather than a new check. So the field
8027
+ would be a thing every adopter has to learn about, including all of the ones
8028
+ with a single interface, in exchange for a report that decides nothing. This
8029
+ holds whatever the frequency turns out to be, which is why it is stated first.
8030
+
8031
+ **And the frequency, counted because the entry said it was cheap to count and it
8032
+ was.** Over `self/` through `inspectProject`: 84 requirements, all covered, **69
8033
+ carrying two or more scenarios**, of which 6 have scenarios in more than one
8034
+ spec file. Over the seventeen fixture projects: 15 ids, 6 multi-scenario, **0**
8035
+ cross-file. Cross-file is only a proxy — nothing records which interface a
8036
+ scenario crosses, which is the entry's own point — so all 6 were read by hand,
8037
+ and **exactly one is the shape**: `ATX-60`, whose statement says the `--json`
8038
+ `ok` and the human verdict printed beside it are decided by one predicate
8039
+ "never by a second spelling of that question", with `json.spec.ts` and
8040
+ `report.spec.ts` asserting the two surfaces separately.
8041
+
8042
+ *How the other five fail is the useful half.* `ATX-14` and `ATX-63` pair the
8043
+ validator with the renderer, which is two **different obligations** of one
8044
+ requirement — one reports the bad placeholder, the other omits it — so there is
8045
+ nothing to compare. `ATX-15` is five edges of one ordering rule. `ATX-80` is one
8046
+ interface run against a fixture and against a change the suite really archived.
8047
+ `ATX-25` is the near-miss worth naming: `pipeline.spec.ts` checks the producer
8048
+ and `json.spec.ts` the serialised report, which is one pipeline observed at two
8049
+ **layers** — downstream-coupled, so they cannot independently disagree, and
8050
+ independence is the property differential evidence is made of.
8051
+
8052
+ *The finding that carries more than the count.* This repository's own canonical
8053
+ instance of the pattern — `tests/static-registry.spec.ts`, the two registry
8054
+ readers required to agree, cited in `CLAUDE.md` as what makes that seam real —
8055
+ is bound to **no requirement id at all**. It is in the engine suite, outside
8056
+ `self/`'s registry, and the only `requirement(` in it is a string fixture. A
8057
+ repository that runs the pattern deliberately, and says so in its own design
8058
+ vocabulary, contributes zero rows to a count of it. The rate is 1 in 69, and the
8059
+ one is weaker than the pair that prompted the entry.
8060
+
8061
+ **What survives, and it is the part that costs nothing.** The deletion test the
8062
+ entry proposed is untried and is now the cheaper half by a wide margin: if the
8063
+ workflow document `init` writes (§9) simply says that two scenarios crossing one
8064
+ requirement at different interfaces should assert the same expectations, the
8065
+ practice may happen anyway — in which case the engine would have gained nothing
8066
+ and charged everyone a field for it. That sentence needs no decision
8067
+ infrastructure and is not what this rejects. The permanent property the
8068
+ rejection leaves in place — that Attest cannot distinguish covering N edges from
8069
+ crossing N interfaces, so a fully-covered requirement says nothing about how
8070
+ many surfaces were asked — is recorded in design §11 rather than only here.
8071
+
8072
+ **What this rejection does not claim.** One corpus, and the limit `[0.9.2]`
8073
+ states about this repository applies: the two-adapter arrangements it has are
8074
+ engine-internal, so the count measures Attest's habits rather than an adopter's.
8075
+ The prompting report is from a real system nobody here can walk. What is
8076
+ corpus-independent is the first argument — no verdict to emit — and that is what
8077
+ this is rejected on. A reporter whose registry has the shape at a rate that
8078
+ makes a **descriptive** output worth having would be re-opening a different
8079
+ proposal, not this one.
8080
+
6791
8081
  ### Rewriting the placeholder scan in `prose` to drop its tail slice
6792
8082
 
6793
8083
  Priced on 2026-08-21, during a standing sweep for more of the ATX-59 shapes, and
@@ -7010,7 +8300,7 @@ releases behind a blocker stated as "the mapping is unavailable", which was
7010
8300
  true and was about a mapping nothing needed; the evidence it carried was about
7011
8301
  the right problem and the mechanism it proposed was not, and nothing rereads a
7012
8302
  blocker that has already been written down. That is the same failure the
7013
- section's own preamble records about a figure nobody re-checks, one level up:
8303
+ `How an entry is re-measured` records about a figure nobody re-checks:
7014
8304
  the *shape* of the proposal goes stale as quietly as its numbers do.
7015
8305
  *What is left of this entry is only the original question* — whether `check`
7016
8306
  and `verify` should refuse a half-loaded registry outright, the way `cover`
@@ -7368,26 +8658,18 @@ matching between statements (token overlap, embeddings). That reintroduces the
7368
8658
  fuzzy comparison design §0 exists to remove; real semantic duplication needs
7369
8659
  judgement and belongs to human review at the propose stage.
7370
8660
 
7371
- ### A freshness gate on the file `attest init` writes (`init --check`)
7372
-
7373
- `render --check` is the obvious precedent and is the opposite case. A rendering
7374
- is a function of the *user's registry*, so its staleness means the user moved
7375
- intent without regenerating a real signal, with the fix in their hands. The
7376
- skill is a function of the *installed version*, so a gate on it turns red on a
7377
- change the user did not make, and is cleared by a commit containing no decision.
7378
- That is a chore wearing a diagnostic's clothes, and a gate that cannot be about
7379
- anything the reader did is worse than no gate the same standard 0.1.6 set when
7380
- it gave `render` no formatting options at all. An earlier draft of `attest init`
7381
- spent a second command, this gate and a second self-requirement on it. What that
7382
- would have bought is immunity to one honest and small exposure: an old file
7383
- describes an old workflow to an agent that trusts it. The backstop is that every
7384
- mistake it can cause is already a diagnostic with a fix hint — a registry
7385
- written the old way is `registry-not-static` — so the agent corrects itself from
7386
- the report, and `init` is idempotent, which makes re-running it the entire
7387
- update mechanism. Recorded here rather than left in the 0.3.0 entry that shipped
7388
- `init`, because the queue above depends on it: a gate is the first thing
7389
- proposed each time a new agent target is considered, and the answer must not
7390
- need re-deciding per target.
8661
+ **New evidence for "sees almost nothing", from `evidence-not-ordering`
8662
+ (2026-08-28), and it is about *why*.** `ATX-30`'s statement was reworded — same
8663
+ three obligations, one of them stated for the first time and the detector
8664
+ moved it from `correct` to `missed` on both variants. Nothing about the
8665
+ requirement got less compound; what changed is that the second clause is now a
8666
+ participle (*"keeping a recorded failure and replacing a recorded pass"*)
8667
+ rather than a second `MUST`. So the rule is not weakly detecting compoundness,
8668
+ it is detecting one **grammatical spelling** of it, and an author's ordinary
8669
+ choice between two ways of writing the same sentence decides the verdict. That
8670
+ is the "cleared by deleting the word that triggered it" failure arriving without
8671
+ anyone trying to clear it, which strengthens the rejection rather than
8672
+ qualifying it.
7391
8673
 
7392
8674
  ### A marked block in the adopter's `AGENTS.md` / `CLAUDE.md`
7393
8675
 
@@ -7518,9 +8800,13 @@ was written expecting the uncovered case and the demonstrated one has a covering
7518
8800
  scenario. **What that changes here is nothing**: this entry was rejected on the
7519
8801
  population a snapshot fires on — every legitimate param edit, fix a commit with
7520
8802
  no decision in it — and that objection is untouched by the new evidence, which
7521
- concerns detection the snapshot was never denied having. The live question moved
7522
- to `Under consideration` above, where it belongs, because the mechanism now in
7523
- 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.
7524
8810
 
7525
8811
  ### Grouping `render`'s output by prefix
7526
8812
 
@@ -7712,11 +8998,89 @@ fallback to evaluation. The same answer applies here for the same reason.
7712
8998
 
7713
8999
  Worth reopening if an adopter's registry reaches a size where prefix grouping
7714
9000
  and `grep` genuinely stop working — but the first thing to try then is the
7715
- declared prefix ownership already under consideration above, which addresses,
7716
- 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.
9004
+
9005
+ ## How an entry is re-measured
9006
+
9007
+ What the three sections above have learned about themselves, kept apart from all
9008
+ three because it belongs to none of them. Every lesson below was learned on an
9009
+ entry that has since left the section it was written in — five built entries now
9010
+ recorded under `[0.8.0]`, the `check` `empty-spec` entry under `[0.9.0]`,
9011
+ `divergent-param` and the committed params snapshot down in `Considered and
9012
+ rejected`. That is the argument rather than a coincidence: the method outlives
9013
+ every entry it was learned from, and filed under `Planned` it is unreachable in
9014
+ the stretches when `Planned` is empty, which is the state it is in as this is
9015
+ written. It was there because that is where the first of it happened to be
9016
+ written, which is not a reason.
9017
+
9018
+ **An entry can sit in these sections for releases on the strength of a figure
9019
+ nobody has checked.** The discipline aims at keeping the *argument*, not at re-testing the
9020
+ evidence under it, so the first act of building one is re-measuring what it
9021
+ claims rather than implementing what it proposes. It has paid in two ways:
9022
+ once killing an entry outright, when the spans it rested on turned out not to be
9023
+ spans; and five times correcting an entry's own numbers or scope before a line
9024
+ of it was written — a call-site count that a regex had inflated, a cost filed as
9025
+ "a fixture per diagnostic" that the corpus put at one fixture, an injection whose
9026
+ payload turned out to need the target file's *own* quote character, which is the
9027
+ difference between escaping one quote and escaping the right one, a fix
9028
+ filed as a map over `issues` that the report's own shape moved to the
9029
+ serialiser, four fields and one nesting level further out, and a type-level
9030
+ refusal filed as breaking the evaluating loader, which turned out not to touch
9031
+ it and to break the one delta shape the entry was about instead. **What a release costs its adopters is not known on the day it is cut**
9032
+ — several entries arrived from one adoption report, about shapes the tool made
9033
+ someone work around without ever erroring. And **the fixes with no observable
9034
+ symptom are worth batching**: individually none justifies a commit, together they
9035
+ cost about an hour, and they are the group no gate could have found, since having
9036
+ no symptom is the definition of what a gate cannot see.
9037
+
9038
+ *The five entries `Planned` carried before it emptied* were built and are
9039
+ recorded under `[0.8.0]`. The second is where this
9040
+ discipline paid again: it went in on an entry whose headline type
9041
+ measurement — instantiations unchanged — was false on re-measure, and the
9042
+ re-measure is the only reason the release notes do not repeat it. The fourth is
9043
+ the other outcome the discipline has, and worth a line because the first three
9044
+ were all corrections: the `render --out` symlink measurement reproduced exactly
9045
+ as filed, down to the leaf being safe for the reason given. What re-measuring
9046
+ added was not a correction but a **surface the entry had not counted** —
9047
+ `--check` reads the same destination, so the same link makes a freshness gate
9048
+ date a document outside the project. Re-measuring an entry is therefore not only
9049
+ a test of the number it quotes; walking the code the claim is about is what
9050
+ shows the claim's own edges. The fifth is that lesson arriving where an entry
9051
+ had already named its own fix: the C1 measurement reproduced exactly, and the
9052
+ repair it proposed — a map over `issues` — was still the wrong half, because
9053
+ four more fields and a nested scenario name carry the same untrusted text and
9054
+ never pass the envelope. An entry that ends with a fix is the one least likely
9055
+ to have its fix re-examined, which is the case worth naming.
9056
+
9057
+ *That has now happened twice, so it is a pattern rather than an incident.* The
9058
+ extensionless-import entry likewise reproduced exactly on its reported half and
9059
+ was wrong about its own repair — it recorded that the writing half needed
9060
+ nothing, and the writing half was guarded by a condition that was correct only
9061
+ while the reading half stayed broken. Both times the error had the same shape: an
9062
+ entry reasons about the code path it can see failing, and the repair makes a
9063
+ second path reachable that nobody has looked at because nothing could reach it.
9064
+ So the re-measure that matters is not "does the reported symptom reproduce" —
9065
+ both did, exactly — but **what does the proposed fix make reachable for the first
9066
+ time**.
9067
+
9068
+ *The first time that question was asked in advance, it paid, and how it paid is
9069
+ worth a line because it is not what the two cases above would predict.* The
9070
+ `check` `empty-spec` entry — `[0.9.0]`, the third entry to reproduce exactly
9071
+ on its reported half — was not wrong about its own repair. Asking the question
9072
+ anyway found the defect one step to the side: the fix it proposed was correct,
9073
+ and the *obvious implementation of it* — make `check` agree with `verify` —
9074
+ would have copied a misdiagnosis `verify` had been emitting unnoticed, because
9075
+ the reference implementation an entry points at is not itself re-read. So the
9076
+ question generalises past the shape it was learned from. It is not only about a
9077
+ path the repair makes reachable; it is about **everything the repair would make
9078
+ `check` agree with**, and an entry that names the right fix can still be
7717
9079
 
7718
9080
  ## Releases
7719
9081
 
9082
+ [1.0.0]: https://gitlab.com/Pseudorca/attest/-/tags/v1.0.0
9083
+ [0.11.0]: https://gitlab.com/Pseudorca/attest/-/tags/v0.11.0
7720
9084
  [0.10.0]: https://gitlab.com/Pseudorca/attest/-/tags/v0.10.0
7721
9085
  [0.9.4]: https://gitlab.com/Pseudorca/attest/-/tags/v0.9.4
7722
9086
  [0.9.3]: https://gitlab.com/Pseudorca/attest/-/tags/v0.9.3