@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 +846 -231
- package/README.md +14 -7
- package/dist/core/gate.d.ts +16 -6
- package/dist/core/gate.js +18 -8
- package/dist/core/red-record.d.ts +4 -4
- package/dist/core/red-record.js +12 -3
- package/dist/core/registry.d.ts +31 -1
- package/dist/core/registry.js +4 -0
- package/dist/core/skill.js +49 -28
- package/dist/core/splice.d.ts +20 -0
- package/dist/core/splice.js +25 -1
- package/dist/core/static-registry.js +81 -17
- package/package.json +2 -1
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
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
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
|
|
6013
|
-
|
|
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 **
|
|
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
|
|
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
|
|
6260
|
-
|
|
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 `
|
|
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
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
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
|
-
|
|
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
|