@am_shork/attest 0.10.0 → 0.11.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
@@ -13,10 +13,383 @@ 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
14
  not API.
15
15
 
16
+ **A narrowing that closes a security hole is breaking by that definition, and
17
+ takes the bump that definition gives it — including a major.** There is no
18
+ carve-out for it and no flag. The two alternatives were priced and both cost
19
+ more than the version number: an exemption needs a test for what counts as a
20
+ security narrowing, and nothing here can hold a prose rule to one, so the label
21
+ would be available to every inconvenient narrowing and judged by the person
22
+ making it. Shipping the new refusal behind an opt-in that defaults off is worse
23
+ for the case it is meant to serve — the four commands documented as safe on an
24
+ untrusted fork MR are the ones a reviewer runs on a file they have not read, and
25
+ a default that keeps answering wrong unless the reviewer knows to pass a flag
26
+ has not been fixed.
27
+
28
+ *What that costs is stated rather than hidden.* Under 0.x it costs nothing: the
29
+ same fix is a minor either way, which is why this could go undecided for so
30
+ long. Under 1.0 it is a major on whatever schedule a hole is found — the
31
+ `callsAuthoringFn` binding fix, the fifth breaking commit in this project's
32
+ history, would have been a 2.0 twenty-five days after a 1.0 cut at the fourth.
33
+ The pressure that creates lands on the version number, and the failure it
34
+ invites is the other one: a fix weakened until it fits a bump somebody
35
+ preferred. A narrowing that ships smaller than the defect it closes is what this
36
+ paragraph exists to make visible.
37
+
38
+ ## [0.11.0] - 2026-08-28
39
+
40
+ ### Added
41
+
42
+ - **`examples/judgement-table/` — the enforcement half of the intent-quality
43
+ method, as one runnable file with no columns.** `intent-quality.md` has told
44
+ an adopter to build a judgement table since `[0.9.2]` shipped `/inspect`, and
45
+ the path to producing one was five voluntary steps with a scaffold at none of
46
+ them: a row in a document table, a 214-line page, a hand pass over four
47
+ questions, then writing the two *mechanical* properties from a prose
48
+ description. The last of those is the step that did not need to be voluntary.
49
+ Completeness and rot-binding do not depend on what the adopter decided to
50
+ measure — their content is fixed — so they are the half where a copy is not an
51
+ echo, and the four judgement columns stay withheld for the reason `[0.9.2]`
52
+ gave: a second table built by copying ours is evidence about nothing.
53
+
54
+ **The columns are absent by construction rather than left blank.**
55
+ `Judgement` ships one optional `pin` — the anchor rot-binding needs, holding a
56
+ scenario name rather than a judgement about one — and adding a required field
57
+ to it makes **every existing row fail to compile**, measured rather than
58
+ claimed: one added field, `TS2741` on both rows. So the type does at author
59
+ time what the completeness assertion does at run time, and a row cannot be
60
+ added without a decision.
61
+
62
+ **It runs in the default suite, and that is the point rather than a detail.**
63
+ `pre-commit-check` §6 already records that the three usage samples on
64
+ `intent-quality.md` are consumer usage of the public API that no gate reaches;
65
+ a fourth copy of them in a directory that *looks* executable would be worse
66
+ than a fenced block, not better. So `vitest.config.ts` includes
67
+ `examples/**/*.spec.ts`, the specifier `@am_shork/attest/inspect` is aliased to
68
+ source there and in `tsconfig.typecheck.json`, and both assertions were shown
69
+ to bite before this was called done — a third requirement added to the sample
70
+ fails completeness with `[ 'EX-3' ]`, and renaming the pinned scenario fails
71
+ rot-binding by name. What the alias deliberately does not check is that the
72
+ specifier resolves in an installed package; `tests/consumer.spec.ts` owns that
73
+ against a real `npm install`, and the two together are the whole claim.
74
+
75
+ A third assertion is in the skeleton that is in neither the page nor this
76
+ entry's proposal: `expect(issues).toEqual([])`, **before** the other two. A
77
+ registry file that will not read statically contributes nothing to `registry`,
78
+ so an unread file is indistinguishable from a project with fewer requirements
79
+ and both completeness directions pass over it. It was not written from
80
+ reasoning — an invalid mutation during the bite test produced exactly that
81
+ state, `registry: {}` with one `registry-invalid` issue, and the guard is what
82
+ caught it.
83
+
84
+ *What it cost elsewhere, because it is the part that does not announce
85
+ itself.* The sample project is a registry, and `tests/intent-rule-candidates.spec.ts`
86
+ walks every `*.reqs.ts` in the tree — so the corpus moved **95 → 97** and the
87
+ `compound-requirement` snapshot was re-read rather than re-baselined:
88
+ `compound`, `correct` and `missed` are all unchanged, so no rate quoted
89
+ anywhere moved and only the denominator did. Those two ids are deliberately
90
+ dull, which is a biased sample, and it is admitted in the snapshot's comment
91
+ rather than held out — one example directory is one adapter, and a hold-out is
92
+ a seam this repository does not buy until something varies across it.
93
+
94
+ - **This repository now carries the workflow document it ships (`ATX-84`).**
95
+ `attest init` has written `.claude/skills/attest/SKILL.md` since 0.6.0, and this
96
+ repository had never run it: `.claude/skills/` held three hand-written skills and
97
+ no `attest/`, so the workflow half was reachable here only by opening
98
+ `src/core/skill.ts` — the exact gap `ATX-27` exists to close, open in the project
99
+ that ships the fix. Nothing could report it, because an absent file is
100
+ indistinguishable from a repository that has not adopted Attest. `.gitattributes`
101
+ has named the file among the generated files it pins since `fcd6140`, a sentence
102
+ false on the day it was written.
103
+
104
+ The requirement holds the committed file byte-identical to `skillContent()`, so
105
+ editing the workflow text turns `attest verify self` red until `attest init` is
106
+ re-run. That is deliberately *not* the freshness gate `src/core/skill.ts` argues
107
+ against shipping: that argument is about adopters, whose copy goes stale only at
108
+ an upgrade and whose every resulting mistake is a diagnostic carrying a fix hint.
109
+ This is the one place where `skillContent()` changes while the file exists, so
110
+ staleness is an ordinary edit rather than an upgrade event, and the gate costs one
111
+ string comparison. Byte equality rather than `ATX-29`'s document comparison for
112
+ the same reason the distinction runs the other way for `render --check`: that gate
113
+ runs in repositories where Attest does not own `.gitattributes`, this one only
114
+ here, where `eol=lf` is committed.
115
+
116
+ No engine behaviour changes. The document is unchanged, `init` is unchanged, and
117
+ nothing an adopter runs is affected.
118
+
119
+ ### Changed
120
+
121
+ - **`delta()` no longer offers the requirements it adds under `added`, because
122
+ that was the one spelling stage 1 could not tell from the right one
123
+ (`ATX-85`).** `delta()` returns the delta *and* the registry of what it
124
+ proposes, so `d.added['AUTH-7'].params.x` and `d['AUTH-7'].params.x` are both
125
+ correct while a change is being authored — and only the second survives
126
+ `--apply`, which repoints the spec's import at the merged registry, and a
127
+ registry has no `added`. Nothing in the pipeline could separate them: the
128
+ archive gate runs *before* the rename, so the spec it graded is not the spec
129
+ the merge commits; `check` executes nothing, so it never evaluates the
130
+ accessor; and the compiler is satisfied on either side because it only ever
131
+ sees whichever half is on disk. The failure arrived one command later as a
132
+ `spec-load-failed` naming the file and not the cause.
133
+
134
+ **The guidance already existed and did not prevent it, which is why this is a
135
+ type and not a warning.** `registry.ts` documents the intended expression and
136
+ the rejected one on the function itself, and the document `init` writes shows
137
+ the by-id form in its worked example; the reach-through was written anyway, by
138
+ the author of the workflow tooling, on the first change this repository put
139
+ through its own flow. The alternative priced against it was a `check`
140
+ WARNING — which would have had to be a warning, since enumerating what a
141
+ change adds is legitimate, and a warning is exactly the strength that already
142
+ failed. `DefinedDelta` withholds the key instead: no diagnostic, no roster
143
+ entry, no runtime cost.
144
+
145
+ **`added?: never` rather than a bare `Omit`, and the difference is nine call
146
+ sites.** `RegistryDelta`'s four fields are all optional, so an added-only delta
147
+ with `added` removed shares *no* property with it and TypeScript's weak-type
148
+ check refuses `applyDelta(base, delta({ added: … }))` outright — measured at 11
149
+ errors, of which 9 are that shape and every one correct. An optional `never`
150
+ restores the overlap and still refuses the read, past both escape hatches:
151
+ `d.added!` and `d.added?.[…]` bottom out in `never`, and only an explicit
152
+ `as any` compiles. That last property is the whole reason this beats the
153
+ WARNING, so it is pinned in `tests/delta-types.spec-d.ts` rather than left to
154
+ hold by luck — a type has no runtime footprint, so a regression here would
155
+ otherwise ship green.
156
+
157
+ **Breaking, and it takes the bump this file's Versioning section gives it.** A
158
+ stage-1 spec reading through `added` stops compiling. The type also now
159
+ deliberately understates its own value — `withProposedRequirements` still
160
+ spreads `added`, and `applyDelta` and the loader still read it through
161
+ `RegistryDelta` — so what is withheld is the authoring surface and not the
162
+ data; `self/registry.spec.ts` keeps asserting exactly that, through a cast that
163
+ is itself the assertion.
164
+
165
+ *Re-measured before implementing, per `How an entry is re-measured`, and the
166
+ filed figures reproduced exactly* — 11 errors in the same two kinds, 11 → 2
167
+ under the variant, `typecheck` clean throughout, all three escape-hatch results
168
+ as recorded. **What the re-measure added was a surface the entry had not
169
+ counted, and it falsified this requirement's first draft before it shipped.**
170
+ `modified`, `removed` and `renamed` vanish at the merge too, and reading any of
171
+ them compiles on both sides — so the statement first drafted for `ATX-85`, that
172
+ withholding `added` leaves the by-id read "the only expression that resolves",
173
+ was false. They are left readable on purpose: none has a by-id equivalent, so
174
+ there is no correct spelling to redirect an author to, and a spec reading one
175
+ is reading delta-only data rather than misspelling a right thing. The narrowed
176
+ statement claims only what the refusal actually buys.
177
+
178
+ *And the exposure claim was one notch too wide.* The entry recorded the window
179
+ between `--apply` and the next `verify` as the whole exposure. In this
180
+ repository it is not: post-merge, reading through any of the four fields fails
181
+ to compile under `noUncheckedIndexedAccess`, so `typecheck:all` bounds it. The
182
+ unbounded form is an adopter's, whose spec files need be in no `tsc` project at
183
+ all — which is the case the refusal is for.
184
+
185
+ *Two things this change made the existing counter-pressure say.* The judgement
186
+ tables refused the new id until it carried a hand-judged row, which is that
187
+ mechanism working on its own author rather than on a hypothetical adopter — and
188
+ the corpus moved 97 → 98 with no rate disturbed. And `divergent-param` churned
189
+ its `field` key a **fourth** time — `ATX-85`'s `added` joining ATX-20, ATX-82
190
+ and ATX-83 — changing no verdict again, which is one more measurement for the
191
+ entry recording that this rule keys on the evidence it discards.
192
+
193
+ ### Security
194
+
195
+ - **The static reader matched the authoring call by spelling, so a file could
196
+ shadow `defineRequirements` and hand the two readers different registries.**
197
+ `callsAuthoringFn` accepted any identifier named `defineRequirements` — the
198
+ bare name was added unconditionally as a fallback for a global or a harness —
199
+ and any property access whose *last* segment was that name. So a
200
+ `*.reqs.ts` opening with `const defineRequirements = (r) => ({ ...r,
201
+ 'ADMIN-1': … })`, or with a local object carrying a method of that name, was
202
+ read by parsing it as the literal argument in front of the call, while the
203
+ evaluator answered with whatever that function returned.
204
+
205
+ Not a refusal but a **wrong answer**, and pointed at under-reporting: `check`
206
+ called the registry covered, `cover` counted rows that were not the rows, and
207
+ `render` wrote a document with an id missing from it. The reach is what makes
208
+ it worth a section — `check`, `cover`, `render` and `status` are the commands
209
+ documented as safe on an untrusted fork MR *because they execute nothing*
210
+ (`ATX-16`), so they are exactly the ones a reviewer runs on a file they have
211
+ not read, and the registry they report is the one nothing else will mention.
212
+ Two readers of one file disagreeing is the condition the differential suite
213
+ exists to prevent (design §5.2), and it could not have caught this: every
214
+ registry it runs over calls the real function.
215
+
216
+ The rule is now about the **binding** rather than the spelling. An import
217
+ binding is trusted, a name the file never binds is trusted — that is what the
218
+ bare-name fallback has always been for — and a `const` / `let` / `var` /
219
+ `function` / `class` in the file itself is not, whether the callee is that
220
+ name or an object property reached through it. A file that contradicts itself
221
+ becomes `registry-not-static`, with `--eval` named as the way back, which is
222
+ `ATX-17` unchanged rather than a new promise.
223
+
224
+ **What this still does not see is the module graph, and it is not written
225
+ to.** `import { defineRequirements } from './wrapper.js'` binds the name by an
226
+ import and is accepted; what that module exports is not a question the source
227
+ text of *this* file answers, and resolving it would mean reading and then
228
+ trusting another file, which is the evaluator's job. The line drawn is what
229
+ the file contradicts about itself.
230
+
231
+ Rejects input that was previously accepted, so it is breaking under this
232
+ project's 0.x rule — though the shapes it rejects are ones no working registry
233
+ has: the four accepted spellings (the import, an alias, a namespace or default
234
+ import's property, and the bare name) all still read, and each is pinned.
235
+
236
+ - **`repointImport` wrote back at offsets taken from a source it never checked
237
+ compiles.** `ATX-69` states that a source that does not compile must be
238
+ refused rather than read out of whatever the parser recovered, and names the
239
+ three readers that open with `createSourceFile`. This was the fourth site and
240
+ the only one that *writes*: `--apply` repoints a proposed spec's import of the
241
+ delta at the registry, and it opened the compiler itself, so it asked nobody.
242
+ `ts.createSourceFile` recovers — a half-saved spec still yields an import
243
+ declaration and a set of offsets — and those offsets were then written over
244
+ the bytes of a `*.spec.ts` the user cannot regenerate.
245
+
246
+ Unreachable through the command today, because the gate refuses a spec it
247
+ could not parse before a merge starts, and fixed for that reason rather than
248
+ in spite of it: a defence that holds only because something upstream holds is
249
+ not a defence, which is the standard `keySource` in the same file is written
250
+ to. A source that does not compile now comes back unchanged, so `applyMerge`
251
+ — which writes only when the text moved — renames the spec with the import it
252
+ already had, and a re-run finds and reports that state instead of a file
253
+ rewritten at offsets nobody can trust.
254
+
255
+ ### Documentation
256
+
257
+ - **`## Versioning` now says what a security narrowing costs a consumer, which
258
+ was undefined and which 0.x was the only reason nobody had to ask.** The
259
+ definition at the head of this file counts *rejecting previously-valid input*
260
+ as breaking and says nothing about security, so under 0.x the question was
261
+ invisible: the same fix is a minor either way. It stopped being hypothetical in
262
+ the same week it was measured — the `callsAuthoringFn` binding fix below is
263
+ breaking precisely *because* it narrows what a static reader accepts, and it
264
+ closes a wrong-answer hole in the four commands documented as safe to run on an
265
+ untrusted fork MR.
266
+
267
+ **Decided: such a fix takes the bump the existing definition gives it,
268
+ including a major.** The three candidate answers are not equivalent and only
269
+ one needed no new machinery. A carve-out naming security narrowings as exempt
270
+ requires a test for what counts as one, and this repository has already
271
+ rejected that shape under another name — *Countable obligations*, killed on the
272
+ argument that **nothing can force the count to be true**; a label nothing can
273
+ hold is available to every inconvenient narrowing and judged by the person
274
+ making it. Reaching the same end additively — the new refusal behind a flag,
275
+ defaulting off until the next major — is the only answer that costs the
276
+ consumer nothing, and it is disqualified by the case it would have to serve
277
+ first: a default that keeps answering wrong unless a reviewer knows to pass a
278
+ flag has not fixed a hole in the commands a reviewer runs on a file they have
279
+ not read.
280
+
281
+ *What it is not.* Not a vulnerability-disclosure policy and not a security
282
+ section for the README — one paragraph in `## Versioning`, in the vocabulary
283
+ that section already uses. And it was needed either way: if 1.0 is never cut
284
+ the paragraph documents a real property of the tool, and if it is, the
285
+ paragraph is a precondition, because a 1.0 without it promises something whose
286
+ price nobody has computed. That is why this could be settled while the 1.0
287
+ entry below stays open — it is decidable from this repository alone, which is
288
+ the property that entry is missing.
289
+
290
+ - **`intent-quality.md` tells people to build on `/inspect` and never said
291
+ whether the shapes move.** `inspect.ts` exports five of them and the page
292
+ carries three samples destructuring them; `SCHEMA_VERSION` governs the `--json`
293
+ envelope and governs none of these. **The policy was not missing — its
294
+ statement was.** The Versioning section already binds them: removing or
295
+ renaming a public API is breaking, and under 0.x that is the minor bump. The
296
+ gap is that the reader this export was written for arrives through a method
297
+ page reached from the README's document table, and nothing on that path said
298
+ so.
299
+
300
+ **Which of two sentences to write was the whole decision, and it turned on
301
+ 1.0.** Restating the existing promise reads as a stability guarantee and is the
302
+ honest summary of current policy; saying the shape may move before 1.0 is the
303
+ more useful warning and discourages exactly the adopter whose judgement table
304
+ would settle the schema entry below. With 1.0 not being cut for this release,
305
+ the second is the true one, and the cost is accepted rather than hidden: the
306
+ page now warns off the reader it most needs. It is written as a promise about
307
+ being *told* rather than about nothing changing, which is what the versioning
308
+ policy actually offers.
309
+
310
+ - **`tests-red` told you to read the failures out of a report that does not carry
311
+ them.** The troubleshooting page said "`attest verify` gives you the same run
312
+ with the individual failures". It does not, and never did: `verify` and
313
+ `archive` both run the child suite with `quiet: true` because the CLI renders
314
+ its own report, so the whole output is one `tests-red` — no file, no scenario
315
+ name, no assertion. A reader following that sentence runs the command it names
316
+ and learns nothing new. Both languages now say to run the spec directly with
317
+ your own test command, and say why that is safe (a direct run writes nothing,
318
+ so it cannot touch `first-run.json`) and why it is not a verdict.
319
+
320
+ Found by dogfooding `ATX-84`: making the installed workflow document stale on
321
+ purpose, to check that the scenario's failure carries a fix hint, produced a
322
+ `verify self` output with no failure in it at all.
323
+
16
324
  ## [0.10.0] - 2026-08-22
17
325
 
18
326
  ### Changed
19
327
 
328
+ - **`never-red` no longer claims to be a rule about writing the test first.**
329
+ Archived as `evidence-not-ordering`. The obligation is unchanged in every
330
+ observable way — same code, same scoping to the ids a change ADDs, same
331
+ blocking condition — and what moved is the registry's account of it, plus
332
+ every document that had turned "red before you implement" into the rule.
333
+
334
+ **The finding is that the ordering was never in the engine.** `keepOutcome` is
335
+ monotonic toward failure in one direction only: a recorded fail is permanent, a
336
+ recorded **pass is provisional**. Its motive was to let an author fix a
337
+ scenario that passed on its first run rather than hand-delete the evidence, but
338
+ the consequence is larger than the motive — a failure observed *after* the
339
+ implementation exists replaces the pass and satisfies `neverRedIssues`
340
+ identically to one observed before it. `self/red-record.spec.ts` has asserted
341
+ that at the record level since the mechanism shipped. So the gate has always
342
+ taken the evidence without caring when it was obtained, and three pieces of
343
+ prose said otherwise: `ATX-30`'s statement stopped at "its first run",
344
+ `ATX-31`'s rationale named running the gate first as *the* remedy, and the
345
+ workflow document said "Run this before you implement anything", which is
346
+ false as written.
347
+
348
+ **Removing it closes nothing, which is the argument for removing it.** A
349
+ stage-1 red can come from a typo in the spec, a missing import, or a fixture
350
+ that is not there yet — none of which is the requirement being unimplemented.
351
+ A failure observed before the implementation was therefore never *better*
352
+ evidence than one observed after it, only earlier. What the gate can actually
353
+ demand is that the scenario was seen to fail with its subject absent, and both
354
+ routes to that produce the same fact by the same command: run the gate before
355
+ writing the implementation, or remove the implementation, run the gate, and
356
+ restore it. The second route is now documented, with the `git stash` shape
357
+ spelled out in `troubleshooting.md`.
358
+
359
+ **Why this is worth the churn rather than a footnote.** The rule as written
360
+ declared malformed the one workflow that is becoming ordinary — intent,
361
+ scenario and implementation authored in one pass — and told those authors to
362
+ redo finished work in a different order to satisfy a gate that would have
363
+ accepted their evidence anyway. Separating the discipline (*an assertion has
364
+ to be able to fail*) from the habit (*write it first*) is what keeps the
365
+ strong half enforceable and stops the weak half from being enforced by
366
+ documentation the engine does not back.
367
+
368
+ `ATX-30` gains the clause the suite already checked — a recorded pass is
369
+ replaced by any later failure — and stops calling the record a snapshot of the
370
+ first run. `ATX-31` keeps its statement, which was always order-free, and
371
+ loses the ordering from its rationale. `ATX-48`, `ATX-53` and `ATX-56` carry a
372
+ one-phrase rationale patch each, because all three named it "the
373
+ red-before-implement gate" and a registry that contradicts itself is the drift
374
+ this tool exists to report. **`first-run.json` keeps its name**: archived
375
+ records already carry it, `inspectRedEvidence` reads them back, and spending
376
+ that to correct a word is the wrong trade — the sentence is where the meaning
377
+ lives.
378
+
379
+ One side effect worth reading: the reworded `ATX-30` statement moved from
380
+ `correct` to `missed` in the compound-requirement measurement, on both
381
+ variants, and the snapshot in `tests/intent-rule-candidates.spec.ts` carries
382
+ it. The requirement did not get less compound — the second clause became a
383
+ participle instead of a second `MUST` — so this is new evidence about *why*
384
+ that rule sees almost nothing, filed with the rejection it belongs to.
385
+
386
+ Two diagnostic messages are reworded and no `code` moves, so `schemaVersion`
387
+ does not move either; `Issue.message` is prose, and this is exactly the kind of
388
+ rewording that contract exists to allow. One assertion in `tests/skill.spec.ts`
389
+ moved from the phrase `must be red` to the code `never-red` — a test pinned to
390
+ a sentence the document is free to reword, which is the same mistake one level
391
+ down.
392
+
20
393
  - **The same mutation pass over the verdict and its evidence: 24 mutations of
21
394
  `gate.ts` and `red-record.ts`, 21 dead, and two of the three survivors closed.**
22
395
  Every step of `evaluateGate` dies when removed, including scoping
@@ -5894,69 +6267,10 @@ losing the argument would cost more than losing the task.
5894
6267
  **Where an entry went is recorded under the version that shipped it.** This
5895
6268
  section keeps no ledger of its own departures — one was maintained here by hand
5896
6269
  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.
5899
-
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.
6270
+ claim that goes wrong quietly. What these sections have learned about
6271
+ re-measuring an entry before building it is at the foot of the file, under
6272
+ `How an entry is re-measured` — it applies to all three, and was filed here only
6273
+ because the first of it was written here.
5960
6274
 
5961
6275
  ## Under consideration
5962
6276
 
@@ -6009,12 +6323,20 @@ have to gather again.
6009
6323
  tautology.
6010
6324
  **The framing that probably resolves it: this is `never-red` wearing different
6011
6325
  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
6326
+ the exact defect `archive`'s `never-red` gate exists to catch — and it did
6327
+ catch two of them in this very repo (`CHECK-2`, `FOG-3`, both confirmed
6014
6328
  correct by the reporter). The reason it did not catch these is structural:
6015
6329
  **changing the value of an existing param goes through no gate at all.**
6016
6330
  `archive` gates a change; a param edit is not a change. That is a cleaner
6017
6331
  statement of the gap than "params can be tautological".
6332
+ *Re-read against `evidence-not-ordering`, which separated that gate's
6333
+ obligation from the ordering it used to be described by.* Nothing here moves:
6334
+ the blocker was never that the observation had to be taken first, it is that a
6335
+ param edit produces no change folder for any observation to be recorded
6336
+ against. If anything the framing gets sharper — what `never-red` demands is
6337
+ evidence that a scenario can fail, which is precisely what is missing for a
6338
+ tautological assertion, and the reason it goes undemanded is still the absence
6339
+ of a gate rather than the absence of a route to the evidence.
6018
6340
  **That observation is now its own entry at the foot of this section**, split
6019
6341
  out and measured on 2026-08-18, because it asks what happens when a value
6020
6342
  moves rather than whether an assertion is a real comparison, and neither
@@ -6164,7 +6486,7 @@ have to gather again.
6164
6486
  pass and separating them is the finding.
6165
6487
 
6166
6488
  - **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
6489
+ The registry is at **85 requirements and 240 scenarios, 2.82 each**, and
6168
6490
  every one is a permanent obligation: `uncovered-requirement` is an ERROR, so a
6169
6491
  requirement without a scenario cannot exist, and nothing ever removes one.
6170
6492
  `delta.removed` exists and `applyDelta` applies it, but `--apply` refuses to
@@ -6186,7 +6508,7 @@ have to gather again.
6186
6508
  requirement, where the scenario should move rather than go. Only the second is
6187
6509
  really about cost, and it is the one where deleting the intent to save the
6188
6510
  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
6511
+ Nothing here is urgent at 85. It is filed now because the moment to write the
6190
6512
  argument down is while the reasoning is available, and because a project whose
6191
6513
  requirement count only rises should have decided in advance what would make
6192
6514
  one leave.
@@ -6222,6 +6544,56 @@ have to gather again.
6222
6544
  no longer supported is the acceleration. **Reopening on cost needs the ratio to
6223
6545
  move again**; reopening on the three meanings of "retire" needs none of these
6224
6546
  figures, and that is still the part nobody has separated.
6547
+ *A fourth measurement, on 2026-08-28, and the cost half is now closed rather
6548
+ than merely unsupported.* **85 requirements and 240 scenarios**, counted the
6549
+ way `verify self` prints them: the ratio runs 2.56 → 2.78 → 2.80 → **2.82**.
6550
+ Three consecutive readings within 0.05 of each other, across twenty-five days
6551
+ and twenty-four requirements, are not a curve that is about to move. The
6552
+ reopening condition this entry set itself has now failed to trigger twice, so
6553
+ the cost argument is not waiting for evidence — it has been answered, in the
6554
+ negative, by its own test. What survives untouched is the half that was never
6555
+ about the ratio: the absolute total only rises, and permanence is the claim.
6556
+
6557
+ **The three meanings, separated — which is what the entry has been asking for
6558
+ and needs none of the figures above.** They are not one question, and only one
6559
+ of the three is open.
6560
+
6561
+ **(1) A requirement that turned out wrong.** Not a retirement at all, and the
6562
+ mechanism already exists: the record of being wrong belongs in `Considered and
6563
+ rejected`, which is what that section is, and removing the id is a manual
6564
+ registry edit nobody has argued against. Nothing is missing here. Filing it
6565
+ under "retire" was what made the question look bigger than it is.
6566
+
6567
+ **(2) A requirement still true but no longer worth a permanently-run
6568
+ scenario.** The only one of the three that is about cost, and the only one with
6569
+ no mechanism — and the sharpest thing this separation turned up is that the
6570
+ tool has been *advertising* one. `uncovered-requirement` reads "Add a scenario,
6571
+ or **mark it deferred**", and `deferred` appears nowhere else in `src/`: not a
6572
+ field, not a flag, not a schema key. The troubleshooting section for that code
6573
+ quotes the message as its symptom and then explains only the first half,
6574
+ because there is no second half to explain. So the escape hatch this meaning
6575
+ needs has been named in shipped output, and in the one place a reader looks
6576
+ when they hit the error, since before anyone asked the question. That is a
6577
+ defect in its own right — the message is prose and free to reword — but it is
6578
+ recorded here rather than fixed there, because rewording it would delete the
6579
+ only written trace that this affordance was ever intended, which is the thing
6580
+ this entry exists to keep.
6581
+
6582
+ **(3) A requirement subsumed by a later one**, where the scenario should move
6583
+ rather than go. That is a rename, and `--apply` refuses RENAMED outright, with
6584
+ the reason recorded in `splice.ts` — deleting an entry has no smaller span to
6585
+ fall back to. So it is blocked downstream of a refusal nobody has proposed
6586
+ lifting, which is the *same* blocker the backlink-index entry above carries.
6587
+ Two open entries now rest on that one refusal, which is worth knowing: it makes
6588
+ lifting it a decision with two dependents rather than one, and neither of them
6589
+ is the reason it exists.
6590
+
6591
+ *So the entry narrows to one meaning.* (1) is answered, (3) is somebody else's
6592
+ blocker, and what is actually open is (2) — whether a requirement can stop
6593
+ owing a permanently-run scenario while keeping its intent, which is the one
6594
+ case where deleting the intent to save the scenario inverts what the whole tool
6595
+ claims. Nothing here says it should be built; what changes is that the question
6596
+ is now one question.
6225
6597
 
6226
6598
  - **Running `verify` over a subset of the scenarios, and what a partial run may
6227
6599
  be allowed to say.** `verify` is all-or-nothing and `check` executes nothing,
@@ -6256,8 +6628,8 @@ have to gather again.
6256
6628
  slow half is smaller, so the question is a selector's ergonomics rather than
6257
6629
  one project's gates; and a scored answer to whether a scope-carrying report can
6258
6630
  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
6631
+ **Measured on 2026-08-12, before building anything, per `How an entry is
6632
+ re-measured`. The second of those two is now answered, and the answer is a
6261
6633
  refusal; the first blocker was wrong; and the whole feature turns out to be a
6262
6634
  different, much cheaper one.**
6263
6635
  *The cost claim reproduces on a second corpus.* `verify self`: the static half
@@ -6474,6 +6846,31 @@ have to gather again.
6474
6846
  §6 already records as the category no gate reaches: the page's three samples
6475
6847
  are unasserted today, and a fourth in a directory that looks executable would
6476
6848
  be worse than a fenced block, not better, unless something runs it.
6849
+ **Built under `[Unreleased]`, and the second of those two blockers was the
6850
+ buildable one.** `examples/judgement-table/` is in the default suite, the
6851
+ specifier is aliased rather than relativised so the file stays copyable
6852
+ verbatim, and both assertions were shown to fail before it was called done.
6853
+ So the category `pre-commit-check` §6 names now has exactly one member that a
6854
+ gate reaches, and the page's three prose samples are still not it.
6855
+ *Two things the build found that the proposal did not contain.* A third
6856
+ assertion — `issues` empty, before either of the other two — which is not a
6857
+ refinement but a hole: an unread registry file and a smaller project are the
6858
+ same value, and the two completeness directions pass over both. It was found
6859
+ by an invalid mutation during the bite test rather than by reasoning, which is
6860
+ `How an entry is re-measured`'s question about what a repair makes reachable,
6861
+ arriving from the other end. And a **type-level** forcing move the runtime
6862
+ assertion cannot make: a required field added to `Judgement` fails every
6863
+ existing row at compile time, so the schema-shaped half of "a row cannot
6864
+ arrive unjudged" costs one line and no engine rule. That is worth noting
6865
+ against blocker **(1)** above, which is about freezing a `Judgement` type:
6866
+ what shipped freezes nothing, because the type is in the adopter's copy.
6867
+ **What is untouched is the first blocker, and it is now the only thing between
6868
+ this entry and its evidence.** Whether an empty frame reads as an invitation
6869
+ or as a form to fill in is still untested, and cannot be tested here — the
6870
+ people who would answer it are the ones who have not arrived. What the build
6871
+ changes is only that the five voluntary steps are now four, and the one
6872
+ removed was the one with a fixed answer. If the next report is still nobody,
6873
+ the scaffold was not what was missing, and that is worth knowing too.
6477
6874
 
6478
6875
  - **Prefix ownership is inferred rather than declared, and the cost arrives only
6479
6876
  when adoption succeeds.** Design §11 records that the id grammar has no level
@@ -6595,127 +6992,6 @@ have to gather again.
6595
6992
  looking at already being that page's subject, and this being a fifth of the
6596
6993
  same kind.
6597
6994
 
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
6995
  - **Nothing is frozen, and the evidence this repository is waiting for requires
6720
6996
  somebody to build on something.** Filed 2026-08-19 from a product review of
6721
6997
  `[0.9.1]`–`[0.9.3]`. Three releases in five days each added public surface, and
@@ -6735,48 +7011,106 @@ have to gather again.
6735
7011
  on one adapter, which is the objection this repository raises against every
6736
7012
  other premature interface and does not get to waive for its own. And the
6737
7013
  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
7014
+ cost of adoption will be, and `How an entry is re-measured` records that **what
7015
+ a release costs its adopters is not known on the day it is cut**. There is no
6740
7016
  measurement to date of anyone adopting either export.
6741
7017
 
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.
7018
+ **Measured on 2026-08-28, before deciding anything. The one-adapter objection
7019
+ splits rather than holds, and a prerequisite this entry does not name has
7020
+ arrived on its own.**
7021
+
7022
+ *The surface has not been moving, which is not what "three releases in five
7023
+ days each added public surface" suggests.* Each entry point's last change to
7024
+ its exported shape, and how many releases have been cut since without touching
7025
+ it:
7026
+
7027
+ | surface | last shape change | releases since |
7028
+ |---|---|---|
7029
+ | CLI commands and options | 2026-08-03 (`--apply`) | 12 |
7030
+ | `.` the index exports | 2026-07-26 | 19 |
7031
+ | `./define` public signatures | 2026-08-08 | 7 |
7032
+ | `./inspect` | 2026-08-18 | 2 |
7033
+
7034
+ `src/index.ts` has had its export list edited in **three commits ever**, the
7035
+ last of them five weeks ago. So "freezing on one adapter" is weak for the CLI
7036
+ and `.` nineteen and twelve releases of evidence that they have stopped
7037
+ moving and strong exactly where this entry puts it, on `./inspect`, which
7038
+ has two releases, one adapter, and whose single post-shipping change was
7039
+ additive. That argues for a line this entry does not currently offer:
7040
+ **freeze what has stopped moving and explicitly do not freeze `./inspect`.**
7041
+
7042
+ *And the cost of any such line was demonstrated this week, unprompted.* The
7043
+ Versioning section at the head of this file defines breaking to include
7044
+ **rejecting previously-valid input**. There have been four breaking commits in
7045
+ this project's history, the last on 2026-08-03 and a fifth is sitting in
7046
+ `[Unreleased]` as of 2026-08-28: the `callsAuthoringFn` binding fix, which is
7047
+ breaking *because* it narrows what a static reader accepts, and which says so
7048
+ about itself. Under 1.0 that is a **major** bump. A fix closing a wrong-answer
7049
+ hole in the four commands documented as safe on an untrusted fork MR would
7050
+ have had to ship as 2.0, twenty-five days after 1.0 or not ship as written.
7051
+
7052
+ *So the decision has a prerequisite, and the prerequisite left this section.*
7053
+ What a narrowing that closes a security hole costs a consumer is undefined,
7054
+ and it is needed whether or not 1.0 is ever cut so it is under `Planned`
7055
+ above rather than here, where it would have been blocked on everything this
7056
+ entry is blocked on. Until it exists a 1.0 would be promising something whose
7057
+ price nobody has computed; what it does **not** do is unblock the rest of this
7058
+ entry, which turns on evidence no measurement here can supply.
7059
+
7060
+ *What is still unmeasured is unchanged, and it is the whole of the rest.* No
7061
+ adopter of either export, so the thing 1.0 is meant to buy — making "build
7062
+ your own counter-pressure on our export" a reasonable ask — has no measurement
7063
+ behind it in either direction. The `examples/` scaffold under `[Unreleased]`
7064
+ is the other half of that ask and was the cheaper half. Whether the missing
7065
+ half is the freeze, or was never the freeze, is what the next report answers.
7066
+
7067
+ - **The intent layer is separable from the repository it describes, and the
7068
+ verification layer is not — so a third party can audit intent and never
7069
+ obtain a verdict.** Filed 2026-08-22 from a design evaluation of using Attest
7070
+ as an external audit tool that touches none of the auditee's implementation.
7071
+ The separability is already bought and is stronger than it looks: §2 requires
7072
+ a registry to be a *literal*, no imported constant and no computed value, so
7073
+ `*.reqs.ts` names nothing in the project it is about and is a file another
7074
+ party could own outright. The four static commands execute none of the
7075
+ auditee's code, which is the posture an auditor needs and is already the
7076
+ documented reason for the split. The arrangement that follows is a two-party
7077
+ contract with `check` as its interface: the auditing party writes the SHALL,
7078
+ the developing party owes each id a scenario, and `uncovered-requirement` and
7079
+ `orphan-test` are the two directions that contract can be broken in.
7080
+
7081
+ *What blocks it, in the order that decides it.* **The verdict half cannot
7082
+ cross the boundary, and that is §1 rather than a gap.** `verify` and `archive`
7083
+ run the suite, the suite's specs import the implementation, and the environment
7084
+ those specs need is the auditee's — reachable only by trusting their
7085
+ `--vitest-config`. So what a decoupled auditor can obtain is the readable
7086
+ obligations and never `tests-red`, which is the same asymmetry `status` already
7087
+ carries and states about itself. That would be tolerable if the static report
7088
+ read as procedural, and it does not: an audit reader takes a full-coverage
7089
+ `check` as a claim about behaviour, while §11 records that coverage is binary,
7090
+ that an assertion can echo the param it is measuring, that shortening a
7091
+ list-valued param silently removes cases, that a quantifier is covered only as
7092
+ far as hand-written scenarios reach, and that two contradictory ids under
7093
+ different prefixes both pass. **The mechanism assumes one tree.** `locate.ts`
7094
+ scans a single project root, and mechanism 1 needs the spec to `import` the
7095
+ registry, so the externally-owned file has to be physically in the tree before
7096
+ any command runs — a checkout step, not a flag. Naming a registry outside the
7097
+ root would be a second answer to where intent lives, which is the shape this
7098
+ repository refuses elsewhere. **The flat ID space bills exactly here.** §11
7099
+ already records that nothing allocates a prefix and that the cost arrives when
7100
+ adoption succeeds; two parties sharing one registry *is* that case, and
7101
+ `duplicate-prefix` can only report a collision after both files were read.
7102
+ **And `--apply` writes into the other party's file.** The merge splices ADDED
7103
+ requirements into the registry, so the one command that edits a file the user
7104
+ cannot regenerate would be editing a document whose owner is not in the
7105
+ repository. Nothing about that is unsafe; it is that the ownership the
7106
+ arrangement exists to establish is the thing the merge step quietly crosses.
7107
+
7108
+ *What would settle it, and what it costs to try.* The whole arrangement is
7109
+ derivable from the design and has been run by nobody, so this is n=0 and cheap
7110
+ to move: the checkout-step form needs no engine change at all, and one
7111
+ adopter running it would say whether the two-party report is read as procedural
7112
+ or over-read as a behavioural claim. That is the measurement, and it is a
7113
+ question about the reader rather than about the code.
6780
7114
 
6781
7115
  ## Considered and rejected
6782
7116
 
@@ -6788,6 +7122,198 @@ it sat between 0.2.0 and 0.1.7 for two releases, where standing still meant
6788
7122
  sinking one version deeper each time a release was cut above it, and a rejection
6789
7123
  filed under a version reads as belonging to it.
6790
7124
 
7125
+ ### An id-keyed retrieval bundle over `archive/` and this file
7126
+
7127
+ Proposed 2026-08-19 inside the entry now titled *The decisions worth retrieving
7128
+ are in this file*: given an `ATX-n`, assemble its statement and params, the
7129
+ scenarios covering it, the first-run evidence for each, and the paths to the
7130
+ change that introduced it — the reasoning being the fifth part and, as filed,
7131
+ "the one with no index and no key". The entry declined to build it and specified
7132
+ a probe instead: assemble the bundle by hand for one requirement, run one real
7133
+ change against it, and record whether it was used, ignored, or used wrongly.
7134
+ **One session, and it decides between a command, a skill surface, and nothing.**
7135
+
7136
+ Run on 2026-08-28 on `ATX-79`, against the `examples/judgement-table/` change
7137
+ shipped in the same pass. **The answer is nothing.**
7138
+
7139
+ **Four of the five parts came back in one script**, as the entry predicted:
7140
+ statement, params and `outOfScope` from `inspectProject`; three scenarios with
7141
+ their evidence states and four archived change refs from `inspectRedEvidence`.
7142
+ No new surface was needed to assemble it. A command would therefore be a wrapper
7143
+ over two functions that already ship, which is the shallow-module case rather
7144
+ than a retrieval mechanism.
7145
+
7146
+ **The row that did the work is a plain field, and not the one this was about.**
7147
+ `outOfScope: ['judging the quality of what it reports', …]` settled the change's
7148
+ central design question — ship the enforcement, withhold the columns — and it
7149
+ has been a field of `inspectProject` since `[0.9.2]`. What the composition added
7150
+ was that it was *present at the moment of the decision*, which is a claim about
7151
+ assembly rather than about a missing store, and assembly is what a caller with
7152
+ two function calls already has.
7153
+
7154
+ **The evidence half is structurally empty for most ids here.** All three rows
7155
+ came back `unarchived`, which is correct — `ATX-79` was written straight into
7156
+ the registry — and which `ATX-80` defines as **not a finding**. So for any
7157
+ brownfield id that quarter of the bundle carries nothing, and brownfield is most
7158
+ of this registry. The bundle's information content varies enormously by which id
7159
+ is asked about, which nothing in the proposal anticipated.
7160
+
7161
+ **And the fifth part is not where the proposal placed it.** The reasoning *for a
7162
+ requirement* is keyed twice over: `rationale` is a field of `inspectProject`, so
7163
+ it was already part one, and design §10 carries the argument under a section
7164
+ number. What `CHANGELOG.md` contributed to this bundle was **two grep hits for
7165
+ `ATX-79`, neither of them about `ATX-79`** — one a parenthetical inside
7166
+ `[0.9.2]` about a different requirement's import closure, the other a false
7167
+ positive inside the tautology entry, where `ATX-79.subpath` appears as an
7168
+ example of a detector firing wrongly. Zero useful rows and two misleading ones.
7169
+
7170
+ So what this rejects is not only the command but the **key**. A design that was
7171
+ evaluated and killed has no `ATX-n`, so an id-keyed index over the decision
7172
+ store returns the entries that were not rejected — the opposite of the content
7173
+ the original entry correctly identified as the valuable part. That half is not
7174
+ rejected and stays open above, without a key and without a proposed mechanism.
7175
+
7176
+ *Labelled for what it is.* n=1, one requirement, one change, and the person who
7177
+ assembled the bundle is the person who made the change — so "was it used" is a
7178
+ self-report, and the honest reading is that one row was load-bearing and one
7179
+ quarter was empty by construction. That is a thin basis for building something
7180
+ and an adequate one for not building it, which is the asymmetry the probe was
7181
+ specified to exploit.
7182
+
7183
+ ### A retrieval mechanism over this file's decision sections
7184
+
7185
+ Proposed inside *The decisions worth retrieving are in this file*, which stayed
7186
+ open on the grounds that **no key had been proposed, and that was the whole of
7187
+ the problem rather than a step before it**. The entry specified the inverse of
7188
+ the probe that killed the id-keyed bundle above: take an entry from this section
7189
+ that was re-proposed and caught here, and ask what somebody would have had to
7190
+ type to find it before proposing it again. One session, no build. It was run on
7191
+ 2026-08-28 and it produced a key — which already exists, in this file, and is the
7192
+ reason nothing needs building.
7193
+
7194
+ *The store is 24 entries, not 8,300 lines.* That is the measurement the entry's
7195
+ premise was missing. `Considered and rejected` holds **24 `###` entries over
7196
+ 1,086 lines**, and every plausible search term a re-proposer would reach for
7197
+ narrows to a handful: `obligations` → 2 entries, `judge` → 5, `compound` → 2,
7198
+ `countable` → 1. Grep over one section already retrieves. An index would be a
7199
+ second copy of a table of contents the headings are.
7200
+
7201
+ ***And 23 of the 24 headings name the artifact or the mechanism*** — `zod/v4-mini`,
7202
+ `isSafeChangeName`, `typescript` as a peer dependency, a freshness gate on the
7203
+ file `init` writes, grouping `render`'s output by prefix. A proposer arrives
7204
+ holding the artifact they are about to touch, which is why the heading is the
7205
+ key: it is the one field written in the vocabulary the next person will have.
7206
+
7207
+ *The exception is the finding.* The single heading that names a **problem**
7208
+ rather than an artifact — *Nothing resists a bloated requirement, and nothing
7209
+ resists a duplicated one* — is the entry that was re-proposed and caught, as the
7210
+ judgement-schema entry's blocker (2). The second documented catch, the quantifier
7211
+ detector reused as `quantifier-unjudged`, is filed under *An engine rule for a
7212
+ statement whose scenarios do not span its quantifier*, which is problem-shaped
7213
+ too. The one artifact-shaped heading anybody has been recorded checking against —
7214
+ *A committed snapshot of the params* — was found by its re-proposer unaided, and
7215
+ they said so in the entry. Four events, so this is a correlation and not a rate;
7216
+ what makes it worth recording is that it points at a rule with no mechanism
7217
+ behind it.
7218
+
7219
+ *So what replaces the proposal is a sentence about headings, not a feature.* A
7220
+ rejection is filed under the name of the thing it declined to build. That is
7221
+ already true of 23 of 24 and needs enforcing by nobody, because it is what an
7222
+ author writing a heading naturally does when the thing has a name — the
7223
+ exception is the entry that declined **three** mechanisms at once and had no
7224
+ single artifact to name, which is the case to watch rather than a rule to state.
7225
+
7226
+ *What the probe also confirmed, in passing, is the claim the entry rested on.*
7227
+ **15 of the 24 rejected entries carry no `ATX-n` at all**; the nine that do
7228
+ mention one as evidence inside an argument about something else. A design that
7229
+ was evaluated and killed has no id because nothing was built to carry one, so an
7230
+ id-keyed index retrieves precisely the entries that were *not* rejected. That
7231
+ half of the entry was right and is now measured rather than reasoned.
7232
+
7233
+ *What is not solved, and it is next door.* `Under consideration` has no headings
7234
+ at all — its twelve entries are bullets whose first bolded sentence is the only
7235
+ handle — so the key found here exists for one of the three foot sections and not
7236
+ for the section most likely to be read before a proposal. Nothing was changed
7237
+ about that here: restructuring twelve open entries is a larger edit than the
7238
+ finding justifies, and the finding is only four events old.
7239
+
7240
+ ### A per-scenario axis recording which interface a scenario crosses
7241
+
7242
+ Prompted by an external write-up (`columns.chicken-house.net`, 2025-11-10, "AI-First
7243
+ Testing Workflow"), which ran one test case against a system's REST API and
7244
+ against its web UI and found the empty-basket check implemented in the UI and
7245
+ absent from the API. Neither run failed against its own expectation; the finding
7246
+ was that two runs of the *same* case disagreed. Attest already permits the
7247
+ arrangement — a `reqId` takes any number of attesting scenarios — but cannot
7248
+ tell three scenarios covering three edges of one requirement from three
7249
+ scenarios crossing three interfaces at the *same* edge. The second set can be
7250
+ asserted to agree; the first has nothing to compare. The proposal was to put
7251
+ that fact in the model, as a per-scenario axis that would have to be a literal
7252
+ in the AST, because `cover` executes nothing.
7253
+
7254
+ Filed 2026-08-22 with four blockers, and rejected on 2026-08-28. **The blocker
7255
+ that decides it needed no measurement, and the one that was measured came back
7256
+ at the floor.**
7257
+
7258
+ **There is no verdict to emit, and that is fatal on its own.** The engine cannot
7259
+ perform the comparison: agreement between two runs is an assertion, and
7260
+ assertions belong to the test. The most any axis could report is that a
7261
+ requirement has scenarios on more than one of them — information with no pass or
7262
+ fail attached, a new output *category* rather than a new check. So the field
7263
+ would be a thing every adopter has to learn about, including all of the ones
7264
+ with a single interface, in exchange for a report that decides nothing. This
7265
+ holds whatever the frequency turns out to be, which is why it is stated first.
7266
+
7267
+ **And the frequency, counted because the entry said it was cheap to count and it
7268
+ was.** Over `self/` through `inspectProject`: 84 requirements, all covered, **69
7269
+ carrying two or more scenarios**, of which 6 have scenarios in more than one
7270
+ spec file. Over the seventeen fixture projects: 15 ids, 6 multi-scenario, **0**
7271
+ cross-file. Cross-file is only a proxy — nothing records which interface a
7272
+ scenario crosses, which is the entry's own point — so all 6 were read by hand,
7273
+ and **exactly one is the shape**: `ATX-60`, whose statement says the `--json`
7274
+ `ok` and the human verdict printed beside it are decided by one predicate
7275
+ "never by a second spelling of that question", with `json.spec.ts` and
7276
+ `report.spec.ts` asserting the two surfaces separately.
7277
+
7278
+ *How the other five fail is the useful half.* `ATX-14` and `ATX-63` pair the
7279
+ validator with the renderer, which is two **different obligations** of one
7280
+ requirement — one reports the bad placeholder, the other omits it — so there is
7281
+ nothing to compare. `ATX-15` is five edges of one ordering rule. `ATX-80` is one
7282
+ interface run against a fixture and against a change the suite really archived.
7283
+ `ATX-25` is the near-miss worth naming: `pipeline.spec.ts` checks the producer
7284
+ and `json.spec.ts` the serialised report, which is one pipeline observed at two
7285
+ **layers** — downstream-coupled, so they cannot independently disagree, and
7286
+ independence is the property differential evidence is made of.
7287
+
7288
+ *The finding that carries more than the count.* This repository's own canonical
7289
+ instance of the pattern — `tests/static-registry.spec.ts`, the two registry
7290
+ readers required to agree, cited in `CLAUDE.md` as what makes that seam real —
7291
+ is bound to **no requirement id at all**. It is in the engine suite, outside
7292
+ `self/`'s registry, and the only `requirement(` in it is a string fixture. A
7293
+ repository that runs the pattern deliberately, and says so in its own design
7294
+ vocabulary, contributes zero rows to a count of it. The rate is 1 in 69, and the
7295
+ one is weaker than the pair that prompted the entry.
7296
+
7297
+ **What survives, and it is the part that costs nothing.** The deletion test the
7298
+ entry proposed is untried and is now the cheaper half by a wide margin: if the
7299
+ workflow document `init` writes (§9) simply says that two scenarios crossing one
7300
+ requirement at different interfaces should assert the same expectations, the
7301
+ practice may happen anyway — in which case the engine would have gained nothing
7302
+ and charged everyone a field for it. That sentence needs no decision
7303
+ infrastructure and is not what this rejects. The permanent property the
7304
+ rejection leaves in place — that Attest cannot distinguish covering N edges from
7305
+ crossing N interfaces, so a fully-covered requirement says nothing about how
7306
+ many surfaces were asked — is recorded in design §11 rather than only here.
7307
+
7308
+ **What this rejection does not claim.** One corpus, and the limit `[0.9.2]`
7309
+ states about this repository applies: the two-adapter arrangements it has are
7310
+ engine-internal, so the count measures Attest's habits rather than an adopter's.
7311
+ The prompting report is from a real system nobody here can walk. What is
7312
+ corpus-independent is the first argument — no verdict to emit — and that is what
7313
+ this is rejected on. A reporter whose registry has the shape at a rate that
7314
+ makes a **descriptive** output worth having would be re-opening a different
7315
+ proposal, not this one.
7316
+
6791
7317
  ### Rewriting the placeholder scan in `prose` to drop its tail slice
6792
7318
 
6793
7319
  Priced on 2026-08-21, during a standing sweep for more of the ATX-59 shapes, and
@@ -7010,7 +7536,7 @@ releases behind a blocker stated as "the mapping is unavailable", which was
7010
7536
  true and was about a mapping nothing needed; the evidence it carried was about
7011
7537
  the right problem and the mechanism it proposed was not, and nothing rereads a
7012
7538
  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:
7539
+ `How an entry is re-measured` records about a figure nobody re-checks:
7014
7540
  the *shape* of the proposal goes stale as quietly as its numbers do.
7015
7541
  *What is left of this entry is only the original question* — whether `check`
7016
7542
  and `verify` should refuse a half-loaded registry outright, the way `cover`
@@ -7368,6 +7894,19 @@ matching between statements (token overlap, embeddings). That reintroduces the
7368
7894
  fuzzy comparison design §0 exists to remove; real semantic duplication needs
7369
7895
  judgement and belongs to human review at the propose stage.
7370
7896
 
7897
+ **New evidence for "sees almost nothing", from `evidence-not-ordering`
7898
+ (2026-08-28), and it is about *why*.** `ATX-30`'s statement was reworded — same
7899
+ three obligations, one of them stated for the first time — and the detector
7900
+ moved it from `correct` to `missed` on both variants. Nothing about the
7901
+ requirement got less compound; what changed is that the second clause is now a
7902
+ participle (*"keeping a recorded failure … and replacing a recorded pass"*)
7903
+ rather than a second `MUST`. So the rule is not weakly detecting compoundness,
7904
+ it is detecting one **grammatical spelling** of it, and an author's ordinary
7905
+ choice between two ways of writing the same sentence decides the verdict. That
7906
+ is the "cleared by deleting the word that triggered it" failure arriving without
7907
+ anyone trying to clear it, which strengthens the rejection rather than
7908
+ qualifying it.
7909
+
7371
7910
  ### A freshness gate on the file `attest init` writes (`init --check`)
7372
7911
 
7373
7912
  `render --check` is the obvious precedent and is the opposite case. A rendering
@@ -7715,8 +8254,84 @@ and `grep` genuinely stop working — but the first thing to try then is the
7715
8254
  declared prefix ownership already under consideration above, which addresses,
7716
8255
  rather than a scorer, which ranks.
7717
8256
 
8257
+ ## How an entry is re-measured
8258
+
8259
+ What the three sections above have learned about themselves, kept apart from all
8260
+ three because it belongs to none of them. Every lesson below was learned on an
8261
+ entry that has since left the section it was written in — five built entries now
8262
+ recorded under `[0.8.0]`, the `check` `empty-spec` entry under `[0.9.0]`,
8263
+ `divergent-param` and the committed params snapshot down in `Considered and
8264
+ rejected`. That is the argument rather than a coincidence: the method outlives
8265
+ every entry it was learned from, and filed under `Planned` it is unreachable in
8266
+ the stretches when `Planned` is empty, which is the state it is in as this is
8267
+ written. It was there because that is where the first of it happened to be
8268
+ written, which is not a reason.
8269
+
8270
+ **An entry can sit in these sections for releases on the strength of a figure
8271
+ nobody has checked.** The discipline aims at keeping the *argument*, not at re-testing the
8272
+ evidence under it, so the first act of building one is re-measuring what it
8273
+ claims rather than implementing what it proposes. It has paid in two ways:
8274
+ once killing an entry outright, when the spans it rested on turned out not to be
8275
+ spans; and five times correcting an entry's own numbers or scope before a line
8276
+ of it was written — a call-site count that a regex had inflated, a cost filed as
8277
+ "a fixture per diagnostic" that the corpus put at one fixture, an injection whose
8278
+ payload turned out to need the target file's *own* quote character, which is the
8279
+ difference between escaping one quote and escaping the right one, a fix
8280
+ filed as a map over `issues` that the report's own shape moved to the
8281
+ serialiser, four fields and one nesting level further out, and a type-level
8282
+ refusal filed as breaking the evaluating loader, which turned out not to touch
8283
+ 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**
8284
+ — several entries arrived from one adoption report, about shapes the tool made
8285
+ someone work around without ever erroring. And **the fixes with no observable
8286
+ symptom are worth batching**: individually none justifies a commit, together they
8287
+ cost about an hour, and they are the group no gate could have found, since having
8288
+ no symptom is the definition of what a gate cannot see.
8289
+
8290
+ *The five entries `Planned` carried before it emptied* were built and are
8291
+ recorded under `[0.8.0]`. The second is where this
8292
+ discipline paid again: it went in on an entry whose headline type
8293
+ measurement — instantiations unchanged — was false on re-measure, and the
8294
+ re-measure is the only reason the release notes do not repeat it. The fourth is
8295
+ the other outcome the discipline has, and worth a line because the first three
8296
+ were all corrections: the `render --out` symlink measurement reproduced exactly
8297
+ as filed, down to the leaf being safe for the reason given. What re-measuring
8298
+ added was not a correction but a **surface the entry had not counted** —
8299
+ `--check` reads the same destination, so the same link makes a freshness gate
8300
+ date a document outside the project. Re-measuring an entry is therefore not only
8301
+ a test of the number it quotes; walking the code the claim is about is what
8302
+ shows the claim's own edges. The fifth is that lesson arriving where an entry
8303
+ had already named its own fix: the C1 measurement reproduced exactly, and the
8304
+ repair it proposed — a map over `issues` — was still the wrong half, because
8305
+ four more fields and a nested scenario name carry the same untrusted text and
8306
+ never pass the envelope. An entry that ends with a fix is the one least likely
8307
+ to have its fix re-examined, which is the case worth naming.
8308
+
8309
+ *That has now happened twice, so it is a pattern rather than an incident.* The
8310
+ extensionless-import entry likewise reproduced exactly on its reported half and
8311
+ was wrong about its own repair — it recorded that the writing half needed
8312
+ nothing, and the writing half was guarded by a condition that was correct only
8313
+ while the reading half stayed broken. Both times the error had the same shape: an
8314
+ entry reasons about the code path it can see failing, and the repair makes a
8315
+ second path reachable that nobody has looked at because nothing could reach it.
8316
+ So the re-measure that matters is not "does the reported symptom reproduce" —
8317
+ both did, exactly — but **what does the proposed fix make reachable for the first
8318
+ time**.
8319
+
8320
+ *The first time that question was asked in advance, it paid, and how it paid is
8321
+ worth a line because it is not what the two cases above would predict.* The
8322
+ `check` `empty-spec` entry — `[0.9.0]`, the third entry to reproduce exactly
8323
+ on its reported half — was not wrong about its own repair. Asking the question
8324
+ anyway found the defect one step to the side: the fix it proposed was correct,
8325
+ and the *obvious implementation of it* — make `check` agree with `verify` —
8326
+ would have copied a misdiagnosis `verify` had been emitting unnoticed, because
8327
+ the reference implementation an entry points at is not itself re-read. So the
8328
+ question generalises past the shape it was learned from. It is not only about a
8329
+ path the repair makes reachable; it is about **everything the repair would make
8330
+ `check` agree with**, and an entry that names the right fix can still be
8331
+
7718
8332
  ## Releases
7719
8333
 
8334
+ [0.11.0]: https://gitlab.com/Pseudorca/attest/-/tags/v0.11.0
7720
8335
  [0.10.0]: https://gitlab.com/Pseudorca/attest/-/tags/v0.10.0
7721
8336
  [0.9.4]: https://gitlab.com/Pseudorca/attest/-/tags/v0.9.4
7722
8337
  [0.9.3]: https://gitlab.com/Pseudorca/attest/-/tags/v0.9.3