@am_shork/attest 0.11.0 → 1.1.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 +1754 -599
- package/README.md +1 -1
- package/dist/core/docs.d.ts +1 -1
- package/dist/core/docs.js +1 -0
- package/dist/core/gate.d.ts +21 -0
- package/dist/core/gate.js +27 -1
- package/dist/core/pipeline.js +42 -3
- package/dist/core/runner.js +34 -1
- package/dist/core/skill.js +32 -11
- package/dist/core/targets.d.ts +29 -0
- package/dist/core/targets.js +46 -0
- package/dist/core/types.d.ts +29 -0
- package/dist/core/validator.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,11 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## Versioning
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
From `[1.0.0]` a breaking change bumps the **major**. "Breaking" means it
|
|
11
11
|
changes the exit code of an existing valid setup, rejects previously-valid
|
|
12
12
|
input, removes/renames a public API or `--json` field, or changes a default
|
|
13
13
|
runtime behavior an existing invocation relies on — diagnostic message text is
|
|
14
|
-
not API.
|
|
14
|
+
not API. The definition has not moved; what moved is the bump it earns, and the
|
|
15
|
+
whole history below `[1.0.0]` was written under the 0.x rule, where the same
|
|
16
|
+
definition earned a **minor**.
|
|
17
|
+
|
|
18
|
+
**One surface keeps the 0.x rule, and it is named rather than inferred.**
|
|
19
|
+
`./inspect` is excluded from the freeze below, so a breaking change to it is a
|
|
20
|
+
minor until a second adopter's usage exists. Everything else the next section
|
|
21
|
+
lists takes the major.
|
|
22
|
+
|
|
23
|
+
`schemaVersion` in the `--json` report is a **separate axis** and not a
|
|
24
|
+
restatement of this one: it is bumped whenever the emitted shape changes
|
|
25
|
+
incompatibly, and an additive field bumps neither it nor the package. Removing
|
|
26
|
+
or renaming a `--json` field is breaking by the definition above and is
|
|
27
|
+
therefore both — a `schemaVersion` bump and, from 1.0, a major.
|
|
15
28
|
|
|
16
29
|
**A narrowing that closes a security hole is breaking by that definition, and
|
|
17
30
|
takes the bump that definition gives it — including a major.** There is no
|
|
@@ -35,6 +48,466 @@ invites is the other one: a fix weakened until it fits a bump somebody
|
|
|
35
48
|
preferred. A narrowing that ships smaller than the defect it closes is what this
|
|
36
49
|
paragraph exists to make visible.
|
|
37
50
|
|
|
51
|
+
## What 1.0 freezes
|
|
52
|
+
|
|
53
|
+
Decided 2026-08-31 and binding since `[1.0.0]`, where the argument, the
|
|
54
|
+
measurement it rests on and what is still unmeasured are recorded. The line
|
|
55
|
+
exists because every open entry waiting on an adopter is asking somebody to build
|
|
56
|
+
on a surface documented as movable, and a line is the only thing that makes that
|
|
57
|
+
a reasonable ask. It is here rather than only there because a stability promise a
|
|
58
|
+
reader cannot find is not one.
|
|
59
|
+
|
|
60
|
+
**Frozen at 1.0: the CLI — its commands, options and exit codes — and the root
|
|
61
|
+
export `.`.** Both are frozen on evidence that they have stopped moving rather
|
|
62
|
+
than on intention: the CLI's last change of shape was `--apply` on 2026-08-03,
|
|
63
|
+
twelve releases ago, and `src/index.ts` has had its export list edited in three
|
|
64
|
+
commits ever, the last on 2026-07-26.
|
|
65
|
+
|
|
66
|
+
**Explicitly not frozen at 1.0: `./inspect`.** It shipped in `[0.9.2]` and has
|
|
67
|
+
two releases and one adapter behind it. Freezing it would be freezing on one
|
|
68
|
+
adapter, which is the objection this project raises against every premature
|
|
69
|
+
interface and does not get to waive for its own. It stays under the 0.x rule
|
|
70
|
+
above until a second adopter's usage exists — stated here, rather than left to
|
|
71
|
+
be inferred from a version number that would say otherwise. `./define` sits
|
|
72
|
+
between the two and is frozen with the CLI, on seven releases.
|
|
73
|
+
|
|
74
|
+
**1.x, not 1.0:** everything currently blocked on an adopter's evidence — the
|
|
75
|
+
judgement schema, any retrieval surface over the archive. The freeze is not a
|
|
76
|
+
claim that those are settled. (A backlink index over `archive/` was named here
|
|
77
|
+
until 2026-09-01, when it was rejected outright; it is not deferred to 1.x, and
|
|
78
|
+
what closed it is at the foot of this file.)
|
|
79
|
+
|
|
80
|
+
**Not a frozen surface, and deliberately: the workflow document `attest init`
|
|
81
|
+
writes into the adopter's repository.** It is a function of the installed version
|
|
82
|
+
— which is what makes re-running `init` after an upgrade the whole update
|
|
83
|
+
mechanism — so freezing it would freeze the workflow the tool teaches. What 1.0
|
|
84
|
+
does not do is make its drift detectable, and that is a live question at the foot
|
|
85
|
+
of this file rather than something this line settles. It is named here because
|
|
86
|
+
the first adoption measured across an upgrade paid nothing on the four surfaces
|
|
87
|
+
above and 110 stale lines on this one.
|
|
88
|
+
|
|
89
|
+
*What this costs is the paragraph above.* Under 1.0 a narrowing that closes a
|
|
90
|
+
security hole takes a major, on whatever schedule a hole is found. That price is
|
|
91
|
+
now stated rather than unknown, which is what made the line decidable at all.
|
|
92
|
+
|
|
93
|
+
## [1.1.0] - 2026-09-02
|
|
94
|
+
|
|
95
|
+
### Added
|
|
96
|
+
|
|
97
|
+
- **`ATX-89`, `ATX-90`, `ATX-91`: `check` reports a workflow document the
|
|
98
|
+
installed version no longer writes (`stale-workflow-doc`, INFO).** The
|
|
99
|
+
document `attest init` writes is a pure function of the installed version, and
|
|
100
|
+
the adopter's tree records nothing about which version wrote it —
|
|
101
|
+
`skillContent()` and `workflowBody()` take no argument, deliberately, because
|
|
102
|
+
that is what makes re-running `init` safe. So the file carries no version
|
|
103
|
+
stamp and a diff against the installed build is the only detector its own
|
|
104
|
+
design permits. Measured on a 0.9.1 → 0.11.0 upgrade report: **110 changed
|
|
105
|
+
lines across 9 hunks**, 403 lines against 458, found only because the reporter
|
|
106
|
+
wrote a script importing `workflowBody()` out of `dist/`, with `check`,
|
|
107
|
+
`cover`, `verify` and `render --check` all green.
|
|
108
|
+
*The level is an asymmetry of obligation rather than a severity judgement, and
|
|
109
|
+
that is the whole argument for it.* Keeping the document current is Attest's
|
|
110
|
+
obligation, because Attest is what changes it; taking the update is nobody
|
|
111
|
+
else's, because an adopter is free to run an older workflow, a modified one, or
|
|
112
|
+
none. A finding whose subject the reader owes nothing about is exactly the
|
|
113
|
+
finding that may only inform — so `ok`, the exit code and every gate are
|
|
114
|
+
untouched, and a reader who ignores this forever pays what they pay today.
|
|
115
|
+
*This is not `init --check`*, which stays rejected below. That gate turns red
|
|
116
|
+
on a diff the person who must commit it has no way to review: a regenerated
|
|
117
|
+
`SPEC.md` shows what **you** changed about your promises, and a regenerated
|
|
118
|
+
`SKILL.md` shows what Attest's authors changed. It also fires in CI, where
|
|
119
|
+
nobody can run `init`, blocking whoever pushes next for an upgrade somebody
|
|
120
|
+
else made.
|
|
121
|
+
*Three requirements because there are three ways to be wrong, and this file
|
|
122
|
+
already carves the identical problem up this way for renderings.* `ATX-89` is
|
|
123
|
+
the report; `ATX-90` is that an **absent** document draws nothing, which is the
|
|
124
|
+
one the obvious implementation breaks — `staleIssue` reports a rendering that
|
|
125
|
+
was never generated under its own code, and copying that here would fire on two
|
|
126
|
+
of this repository's own three targets and on the majority of adopters, since
|
|
127
|
+
`init` writes one target by default and nothing obliges a project to run it;
|
|
128
|
+
`ATX-91` is that the comparison is of the document rather than the bytes, which
|
|
129
|
+
is deliberately the **opposite** of `ATX-84` and could not be had by widening
|
|
130
|
+
`ATX-29`. `ATX-84` compares this repository's copy byte-for-byte and may,
|
|
131
|
+
because `.gitattributes` here is committed and pins `eol=lf` — that file is
|
|
132
|
+
Attest's here and nobody's in an adopter's tree, so one rule cannot cover both.
|
|
133
|
+
*What it costs, measured before it was built:* 0.234 ms for all three target
|
|
134
|
+
comparisons, plus 4.19 ms to import `targets.ts` and the 26 KB template behind
|
|
135
|
+
it, against a `check self --json` of 814–1,183 ms — **~0.5%**. No
|
|
136
|
+
`SCHEMA_VERSION` bump: the field exists, the value is new, and `ok` is
|
|
137
|
+
untouched because INFO is below `hasError`.
|
|
138
|
+
*Two things the build found that the proposal did not contain.* The fix hint
|
|
139
|
+
had to name what `init` does to the file and not only the command — `runInit`
|
|
140
|
+
writes with `writeAtomic`, no merge and no backup, so an adopter who edited
|
|
141
|
+
these instructions is being pointed at something destructive. `targets.ts`
|
|
142
|
+
rule 1 makes that defensible, Attest owning the path being the whole reason it
|
|
143
|
+
may overwrite, and it was nowhere stated to the reader. And the two negative
|
|
144
|
+
scenarios had to be **paired with the case that must fire**, in the same
|
|
145
|
+
scenario, because an absence assertion alone is green in a tree where nothing
|
|
146
|
+
is implemented — which is the defect `never-red` exists to name, arriving in
|
|
147
|
+
the scenarios written to attest a rule about not firing.
|
|
148
|
+
*One cost that is not paid and is stated rather than left to be discovered:*
|
|
149
|
+
this repository cannot dogfood it. `check` reads the project root, which here
|
|
150
|
+
is `self/`, while the document sits at the repository root — so `check self`
|
|
151
|
+
finds it absent and is correctly silent. `ATX-84`'s scenario reaches two
|
|
152
|
+
directories up because it is a test; a command cannot. All three scenarios
|
|
153
|
+
build a temporary project, and `self/` gives this rule none of the continuous
|
|
154
|
+
exercise `ATX-84` gets.
|
|
155
|
+
*What the judgement tables did with the three ids, since that is the
|
|
156
|
+
counter-pressure earning its place rather than a chore paid to it.* All four
|
|
157
|
+
demanded rows and got them. `compound-requirement` **missed `ATX-89`** — two
|
|
158
|
+
obligations, "reports it" and "at that level", and the keyword heuristic sees
|
|
159
|
+
one — which is the "never wrong, nearly blind" reading holding at 18 of 42
|
|
160
|
+
rather than a new finding. `divergent-param` produced a **new false positive
|
|
161
|
+
and re-keyed an old one**: `level` joins `code`, `file`, `flag` and `field` as
|
|
162
|
+
a fifth natural word for a kind of constant, and `level:ATX-60,ATX-89` is the
|
|
163
|
+
second key where the two requirements genuinely *are* related — both about the
|
|
164
|
+
severity ladder and the `hasError` predicate — while the rule relates them for
|
|
165
|
+
a reason that is not that. The echo detector flagged both of `ATX-89`'s params
|
|
166
|
+
and both were measured `independent` by mutation, one `verify self` each,
|
|
167
|
+
taking it to 4 true positives of 11.
|
|
168
|
+
- **`ATX-88`: `tests-red` names the timeout the run applied and how many
|
|
169
|
+
scenarios reached it, so a suite that went red on the clock can be told from
|
|
170
|
+
one that went red on its code.** Filed from a 0.9.1 → 0.11.0 upgrade report
|
|
171
|
+
where `npx vitest run` was 170/170 green on the same tree on which `verify`
|
|
172
|
+
was red two runs in three. The standing answer — run the suite yourself under
|
|
173
|
+
your own reporter — is circular in exactly that case, because the
|
|
174
|
+
disagreement between the two runs *is* the failure; what the reporter did
|
|
175
|
+
instead was time every scenario by hand and cross-reference which of eleven
|
|
176
|
+
over four seconds declared no `timeoutMs`. **This is deliberately not the
|
|
177
|
+
pass-through**, which stays open at the foot of this file: carrying the
|
|
178
|
+
child's failures means new fields, a `SCHEMA_VERSION` question and a third
|
|
179
|
+
party's assertion wording in the field consumers are told not to branch on.
|
|
180
|
+
It is the narrow half that is not blocked — the isolated child run drops the
|
|
181
|
+
config that would have raised the timeout, so *did anything run out the clock*
|
|
182
|
+
is the one question a green direct run cannot answer and this one can, and it
|
|
183
|
+
needs nothing the report does not already carry. A count and never a list, for
|
|
184
|
+
the same reason.
|
|
185
|
+
*The number is read from the resolved child config rather than written down,
|
|
186
|
+
and that is the half a constant would have got wrong.* It is the one value
|
|
187
|
+
here Attest does not choose: Vitest's own default when the run is isolated,
|
|
188
|
+
the supplied config's value when `--vitest-config` raised it. `ATX-88.params`
|
|
189
|
+
pins the isolated default at 5000 because two documents quote it in prose —
|
|
190
|
+
the workflow document `init` writes and the troubleshooting page — and until
|
|
191
|
+
now nothing would have failed if a Vitest release moved it.
|
|
192
|
+
*Detected by duration and never by the child's error text.* Vitest does say
|
|
193
|
+
"Test timed out in 5000ms." and matching that would be shorter; it is also the
|
|
194
|
+
category error this project refuses in its own diagnostics, since a message is
|
|
195
|
+
prose in somebody else's package too. Measured rather than assumed: a scenario
|
|
196
|
+
that reaches the ceiling ends `fail` a hair past it — 5015 ms against 5000 —
|
|
197
|
+
while an assertion failure ends `fail` in under a millisecond, so `>=` needs no
|
|
198
|
+
tolerance and a tolerance would start claiming timeouts for slow assertions
|
|
199
|
+
that failed honestly. A missing duration reads as *not* a timeout, which is the
|
|
200
|
+
direction that fails quietly; the other reports every red as a timeout, which
|
|
201
|
+
is a wrong diagnosis printed with confidence.
|
|
202
|
+
*Stated over the run rather than over the two commands that render it*, unlike
|
|
203
|
+
`ATX-51` beside it, and by this project's own rule rather than by preference:
|
|
204
|
+
what `verify` and `archive` add is a sentence in `Issue.message`, which is
|
|
205
|
+
prose and never API, so a scenario asserting it would be asserting wording.
|
|
206
|
+
Additive to `--json` only in the sense that nothing there moved at all — no
|
|
207
|
+
field, no code, no `schemaVersion` question.
|
|
208
|
+
|
|
209
|
+
- **`examples/judgement-table/` states the counting rule, which is the one piece
|
|
210
|
+
of content that decides nothing and without which two tables cannot be
|
|
211
|
+
compared.** Decided under `Planned` on 2026-08-31 on the first table built from
|
|
212
|
+
that example, which distrusted its own obligation column above all its others
|
|
213
|
+
and said why: two tables that count differently are not a second measurement of
|
|
214
|
+
the same thing, they are a second vocabulary. The rule is not a column and
|
|
215
|
+
answers nothing — what a requirement carries is still the adopter's judgement —
|
|
216
|
+
so it sits on the same side of the split as the two mechanical properties, and
|
|
217
|
+
was being withheld along with the answers, which was the wrong side. It fixes
|
|
218
|
+
the test (two clauses are two obligations when an implementation can satisfy
|
|
219
|
+
one completely and fail the other), the six kinds of clause that read as a
|
|
220
|
+
second obligation and are not (scope, criterion, purpose, the other side, a
|
|
221
|
+
consequence, and the states a scenario happens to construct), the
|
|
222
|
+
one-promise-per-emitted-artifact case, and the tiebreak — perturb one half and
|
|
223
|
+
see whether the other half's scenario stays green. Extracted from the labels in
|
|
224
|
+
`tests/intent-rule-candidates.spec.ts` rather than invented, which is why it is
|
|
225
|
+
a description of a rule already in use and not a proposal. It also names what it
|
|
226
|
+
does **not** ask: whether the obligations belong under one id is a different
|
|
227
|
+
question, and keeping the two apart is what lets the count stay a measurement.
|
|
228
|
+
|
|
229
|
+
### Changed
|
|
230
|
+
|
|
231
|
+
- **The example's anchor is no longer a field to be typed, because the first
|
|
232
|
+
table built from that file filled it in for all 80 of its rows mechanically.**
|
|
233
|
+
`Judgement` shipped with an optional `pin`, documented as not a judgement
|
|
234
|
+
column and nevertheless the only concrete field in the interface — and a field
|
|
235
|
+
gets populated. Copying 80 scenario names out of the AST and back into the
|
|
236
|
+
table is not judgement, and it is exactly the form-filling the empty frame
|
|
237
|
+
exists to prevent. `pin` is now out of `Judgement` entirely and sits in a
|
|
238
|
+
sibling `PINS` map, and an anchor on an id declaring fewer than two scenarios is
|
|
239
|
+
**refused**: with one scenario the anchor is a function of the project,
|
|
240
|
+
`inspectProject` already computes it, and the line naming it is transcription
|
|
241
|
+
wearing a judgement's clothes. What survives is the case where the anchor is a
|
|
242
|
+
real choice, which is the case a machine cannot make. *The cost is named rather
|
|
243
|
+
than hidden:* a sole scenario deleted and replaced under a different name is a
|
|
244
|
+
revisit `PINS` will not force. That is what not asking anyone to transcribe
|
|
245
|
+
costs, and it is paid where a rename cannot change which scenario the row meant.
|
|
246
|
+
- **The frame's emptiness is now typed, which the obvious implementation of this
|
|
247
|
+
fix would have left unenforced.** Deleting `pin` leaves `interface Judgement {}`,
|
|
248
|
+
and an empty interface is not an empty row type — it admits `0` and `""`, so
|
|
249
|
+
every row could quietly acquire anything at all while the file claimed to ship
|
|
250
|
+
no fields. `Record<string, never>` is the type that means what the sentence
|
|
251
|
+
says. Caught by `eslint` rather than by reasoning, which is `How an entry is
|
|
252
|
+
re-measured`'s question about what a repair makes reachable arriving one step to
|
|
253
|
+
the side of where it was expected. Measured both directions: a row carrying an
|
|
254
|
+
undeclared field does not compile, and replacing the line with an interface
|
|
255
|
+
holding one **required** column errors on every existing row.
|
|
256
|
+
- **The example's own subject gained a second scenario**, so `EX-1`'s anchor is a
|
|
257
|
+
choice among several rather than a value the machine could have derived — the
|
|
258
|
+
refusal above has nothing to demonstrate on a project where every id declares
|
|
259
|
+
one scenario. It is also the counting rule standing on its own subject: two
|
|
260
|
+
scenarios, one obligation, because "before it is stored" is the criterion for
|
|
261
|
+
"rejected" and not a second promise. All three assertions in the anchor test
|
|
262
|
+
were shown to fail before this was called done — a derivable anchor, a renamed
|
|
263
|
+
scenario, and an anchor with no row.
|
|
264
|
+
|
|
265
|
+
### Documentation
|
|
266
|
+
|
|
267
|
+
- **Three places carried a backstop claim that is now false — `ATX-84`'s
|
|
268
|
+
rationale, `skill.ts`'s header, and design §9 in both languages — and all
|
|
269
|
+
three withdraw it.** The claim is that every mistake a stale workflow
|
|
270
|
+
document can cause is a diagnostic carrying a fix hint the agent corrects
|
|
271
|
+
itself from, and it is one of the two arguments for shipping no freshness gate
|
|
272
|
+
to adopters. An upgrade report measured the counterexample: a
|
|
273
|
+
copy six weeks and one upgrade behind still described `never-red`'s ordering
|
|
274
|
+
rule, which `[0.10.0]` archived as `evidence-not-ordering` because the
|
|
275
|
+
ordering was never in the engine. An agent reading it does something the
|
|
276
|
+
engine does not require — more conservative, not wrong-shaped — so **no issue
|
|
277
|
+
fires at any severity** and there is nothing to correct itself from. The
|
|
278
|
+
backstop covers a stale document producing bad input; it does not cover one
|
|
279
|
+
producing unnecessary work, and the second is what the drift produced.
|
|
280
|
+
*All three, because they are one argument stored three times and this
|
|
281
|
+
repository's whole subject is what happens when those disagree.* Two were
|
|
282
|
+
known from the entry that priced this; the third was found by grepping for the
|
|
283
|
+
claim rather than for the files that discuss it, and design §9 is the copy
|
|
284
|
+
with the widest audience. `ATX-84`'s rationale cites "the decision in
|
|
285
|
+
`skill.ts`", which cites the design — so correcting any one of them alone
|
|
286
|
+
leaves a citation pointing at a document that says something else. The
|
|
287
|
+
workflow document's own bytes are untouched — the claim lives in the module
|
|
288
|
+
header, above `BODY` — so no `init` re-run is owed and `ATX-84` stays green
|
|
289
|
+
byte-for-byte.
|
|
290
|
+
*What does not change is the requirement.* `ATX-84` gates this repository's
|
|
291
|
+
copy and is not adopter-facing, and the surviving objection — that a gate
|
|
292
|
+
charges every adopter a diff they did not ask for — carries it alone. What the
|
|
293
|
+
withdrawal reopens is the adopter-facing question, which is priced under
|
|
294
|
+
`Under consideration` rather than restated in a rationale, since a second copy
|
|
295
|
+
of an argument that is still moving is the failure this entry is about.
|
|
296
|
+
- **The workflow document named one of the two things the isolated child run
|
|
297
|
+
withholds, and the troubleshooting page named both.** Re-measuring this before
|
|
298
|
+
building it is what found that: the ask was filed as "have the workflow
|
|
299
|
+
document say that a scenario near the default needs an explicit `timeoutMs`",
|
|
300
|
+
and that sentence already existed — in `tests-red`'s troubleshooting section,
|
|
301
|
+
worked example included. What did not exist was the pointer. The workflow
|
|
302
|
+
document's own bullet on why a green direct run does not predict the gate lists
|
|
303
|
+
aliases, transforms and a DOM, and stops there; an agent reading it was told
|
|
304
|
+
about the environment and not about the clock, while the section it links to
|
|
305
|
+
covers both. So what was owed was a second bullet rather than a new
|
|
306
|
+
explanation, which is cheaper than the entry priced it.
|
|
307
|
+
- **`tests-red`'s troubleshooting section stops saying the report carries
|
|
308
|
+
nothing about the failure**, in both languages, because it now carries exactly
|
|
309
|
+
one thing. The sentence that had to change is the one that reads as a general
|
|
310
|
+
promise — "not from this report, because it does not carry them" — and the
|
|
311
|
+
worked example beside it says which half crossed and why the other half is a
|
|
312
|
+
decision rather than an omission. History rows on `verify` and `archive` in
|
|
313
|
+
both references, since a reader on 1.0.0 gets the older report.
|
|
314
|
+
- **`CLAUDE.md`'s "it will not tell you why" gotcha gains its exception.** The
|
|
315
|
+
gotcha is still true of the file, the scenario and the assertion; it is no
|
|
316
|
+
longer true of the clock, and a gotcha that overstates itself is how somebody
|
|
317
|
+
stops reading the report.
|
|
318
|
+
|
|
319
|
+
- **`intent-quality.md` step 2 points at the counting rule, and says why that
|
|
320
|
+
one pointer does not contradict the page's own sequencing.** The page asks a
|
|
321
|
+
reader to do step 2 before reading our table, because a table built by someone
|
|
322
|
+
who read ours first is an echo of ours. A rule that fixes the *unit* is the
|
|
323
|
+
opposite case — sharing it is what makes a second table comparable, where
|
|
324
|
+
sharing an answer is what makes it a copy — and a page that withheld both
|
|
325
|
+
without distinguishing them would have been asking for an incomparable
|
|
326
|
+
measurement on principle. Both languages, and a line added to *Read our table
|
|
327
|
+
after step 2, not before* so the exemption is stated where the rule it excepts
|
|
328
|
+
is. The History row is owed for the second reason rather than the first: step 4
|
|
329
|
+
tells a reader to **copy** `examples/judgement-table/`, and that file changed
|
|
330
|
+
under this version, so a copy taken at `[1.0.0]` is no longer the file this
|
|
331
|
+
page describes — which is exactly what that table is for and the kind of row
|
|
332
|
+
easiest to miss, because nothing on the page announces that it hands out a
|
|
333
|
+
file.
|
|
334
|
+
|
|
335
|
+
## [1.0.0] - 2026-09-01
|
|
336
|
+
|
|
337
|
+
### Added
|
|
338
|
+
|
|
339
|
+
- **`ATX-87` gates the CLI's command and option roster, which is the other half
|
|
340
|
+
of the same freeze.** `What 1.0 freezes` names two surfaces in one sentence —
|
|
341
|
+
the CLI, its commands, options and exit codes, and the root export `.` — and
|
|
342
|
+
after `ATX-86` three of those four halves were held: the export by `ATX-86`,
|
|
343
|
+
the exit codes by the requirement making `hasError` the single predicate the
|
|
344
|
+
verdict, the JSON `ok` and the process status all read from, and by the one
|
|
345
|
+
keeping the report alone on stdout. **The roster was held by nothing.** Seven
|
|
346
|
+
commands and seventeen options, and no test anywhere enumerated either, so
|
|
347
|
+
deleting a flag or renaming a command broke nothing a contributor runs.
|
|
348
|
+
`ATX-16.staticCommands` reads like the missing gate and is not: it names four
|
|
349
|
+
commands in order to say they execute no project code, which is a claim about
|
|
350
|
+
behaviour rather than a claim that the roster is closed.
|
|
351
|
+
|
|
352
|
+
**Read out of `--help` rather than out of `cli/index.ts`, and that is forced
|
|
353
|
+
rather than preferred.** Every option string in the source is a constant —
|
|
354
|
+
`JSON_FLAG`, `EVAL_FLAG`, `VITEST_CONFIG_FLAG`, `TARGET_FLAG` — so a static
|
|
355
|
+
walk for literals sees the command names and almost none of the flags, and
|
|
356
|
+
resolving the constants would mean reimplementing enough of the emitter to
|
|
357
|
+
guess what it emits. The built module cannot be introspected either:
|
|
358
|
+
`program.parseAsync()` runs at module scope and `program` is never exported,
|
|
359
|
+
so importing `dist/cli/index.js` runs the CLI instead of describing it. What
|
|
360
|
+
is left is the artifact a consumer actually reads, and it is the right ground
|
|
361
|
+
truth in both directions — a flag that works and is absent from the help is
|
|
362
|
+
not on the published surface, and one documented there is, whatever the source
|
|
363
|
+
says.
|
|
364
|
+
|
|
365
|
+
**commander's `-h, --help`, `-V, --version` and `help` command are excluded,
|
|
366
|
+
and matched by name rather than by shape.** They are not this project's to
|
|
367
|
+
freeze, and a gate that reddens because a dependency reworded its own built-in
|
|
368
|
+
is red for a change nobody here decided — the shape this repository refused
|
|
369
|
+
once already, when it rejected a freshness gate that could fail a fresh clone
|
|
370
|
+
on somebody else's release. Matching them by long name is what keeps the
|
|
371
|
+
exclusion from hiding one of ours: `-h, --help` and a hypothetical `-j,
|
|
372
|
+
--json` are the same shape, so a shape test would silently drop a real flag
|
|
373
|
+
the day anybody adds a short one.
|
|
374
|
+
|
|
375
|
+
The two rosters are asserted apart because they are separately falsifiable: a
|
|
376
|
+
command list that is exactly right can belong to commands whose flags have all
|
|
377
|
+
been renamed, and every flag can be correct on a command that should not
|
|
378
|
+
exist. Measured — perturbing the CLI with one added command *and* one added
|
|
379
|
+
flag failed both scenarios, while an added command alone would have failed
|
|
380
|
+
only the first, since the option scenario iterates the registry's list and
|
|
381
|
+
would never have visited it. Eight child processes, one per help page,
|
|
382
|
+
spawning `process.execPath` with `bin/attest.js` rather than the installed
|
|
383
|
+
shim, so no shell is involved on either platform; both scenarios carry an
|
|
384
|
+
explicit `timeoutMs` because the CLI's start-up cost puts the option scenario
|
|
385
|
+
over Vitest's 5 s default.
|
|
386
|
+
|
|
387
|
+
No engine code changed, no command or flag moved, and no `--json` field or
|
|
388
|
+
issue `code` was added: this is a self-requirement and a spec file.
|
|
389
|
+
|
|
390
|
+
- **`ATX-86` gates the shape of what `.` exports, ahead of the release that
|
|
391
|
+
freezes it.** Nothing in the suite enumerated the public surface, and the
|
|
392
|
+
three things that looked as though they did each answered a different
|
|
393
|
+
question: `tests/consumer.spec.ts` asserts that every specifier in the
|
|
394
|
+
`exports` map *resolves*, which is reachability rather than shape;
|
|
395
|
+
`tests/smoke.spec.ts` asserts four runtime exports are functions, and is
|
|
396
|
+
silent about the eleven type-only names beside them and about an export
|
|
397
|
+
arriving that nobody intended; and the four `*.spec-d.ts` files pin the
|
|
398
|
+
behaviour of particular types against `src/` rather than against the emitted
|
|
399
|
+
declarations. So dropping a type export, or widening `Issue.code` back to
|
|
400
|
+
`string`, broke nothing a contributor runs — on the surface `What 1.0 freezes`
|
|
401
|
+
promises not to move.
|
|
402
|
+
|
|
403
|
+
**Stated over the built package and attested over its two artifacts
|
|
404
|
+
separately**, which is `ATX-79`'s shape and is not an analogy to it. `export
|
|
405
|
+
type` is erased whole at emit, so `Issue` is a name in `dist/index.d.ts` and is
|
|
406
|
+
absent from `dist/index.js`, and a consumer importing it as a value gets
|
|
407
|
+
nothing. That asymmetry is the claim rather than an accident of it, and it is
|
|
408
|
+
invisible to any walk over `src/index.ts`, where both kinds are one export
|
|
409
|
+
list. The declared lists live in `params`, so each scenario compares the
|
|
410
|
+
registry against the artifact and the two terms are independent in both
|
|
411
|
+
directions: a barrel the registry does not follow goes red, and so does the
|
|
412
|
+
reverse.
|
|
413
|
+
|
|
414
|
+
**What it does not close, stated here rather than found later.** An edit that
|
|
415
|
+
removes a name from the barrel *and* the registry moves both terms together
|
|
416
|
+
and stays green. That is a deliberate registry edit, visible as a `SPEC.md`
|
|
417
|
+
diff under `render --check`, and review is the gate for it — the same answer
|
|
418
|
+
the foot of this file reaches for a param whose value moves. The count pinned
|
|
419
|
+
beside each comparison is the independent term against the careless half of
|
|
420
|
+
that case and is not a claim to have closed it.
|
|
421
|
+
|
|
422
|
+
No engine code changed, no command or flag moved, and no `--json` field or
|
|
423
|
+
issue `code` was added: this is a self-requirement and a spec file. The entry
|
|
424
|
+
that proposed it was filed under `Under consideration` on 2026-09-01 from a
|
|
425
|
+
comparative read of `nostics` 1.2.0, promoted to `Planned` the same day once
|
|
426
|
+
re-measuring corrected its cost, and the correction is the reason it was
|
|
427
|
+
buildable immediately — it had priced a build coupling that `self/` already
|
|
428
|
+
carries, having considered only `tests/` and the consumer suite as homes.
|
|
429
|
+
|
|
430
|
+
### Changed
|
|
431
|
+
|
|
432
|
+
- **`uncovered-requirement` stops advertising an affordance that has never
|
|
433
|
+
existed.** The message read *"Add a scenario, or mark it deferred"*, and
|
|
434
|
+
`deferred` appears nowhere else in the project: not a field of
|
|
435
|
+
`RequirementSchema`, not a flag on any command, not a key in the report. The
|
|
436
|
+
troubleshooting section for the code quoted the message as its symptom and
|
|
437
|
+
then explained only the first half, because there is no second half to
|
|
438
|
+
explain. So the one place a reader looks when they hit this error named an
|
|
439
|
+
escape hatch, in shipped output, in every release to date. It now names the
|
|
440
|
+
two exits that exist — write a scenario, or delete the entry — and
|
|
441
|
+
troubleshooting says why there is no third: an id Attest holds is an
|
|
442
|
+
obligation it holds permanently.
|
|
443
|
+
|
|
444
|
+
**The trace of the affordance having been intended is kept rather than
|
|
445
|
+
deleted**, which is why this waited. `Considered and rejected` gains the
|
|
446
|
+
entry that found it (*Retiring a requirement*), and design §11 gains the
|
|
447
|
+
permanence as a property to design against rather than a gap awaiting a
|
|
448
|
+
mechanism. Rewording the message alone would have removed the only written
|
|
449
|
+
evidence that anybody ever meant to build the thing.
|
|
450
|
+
|
|
451
|
+
`Issue.message` is prose and not API, so no consumer branches on this and no
|
|
452
|
+
exit code moves; there is no `code`, `level` or `--json` field change and
|
|
453
|
+
nothing to bump.
|
|
454
|
+
|
|
455
|
+
- **1.0: the CLI and `.` are frozen, and the freeze ships with the gates that
|
|
456
|
+
enforce it.** The line itself is at the head of this file under `What 1.0
|
|
457
|
+
freezes` — a stability promise a reader cannot find is not one — and this is
|
|
458
|
+
the release that makes it binding. Decided 2026-08-31, cut 2026-09-01.
|
|
459
|
+
|
|
460
|
+
**Frozen: the CLI — its commands, options and exit codes — and the root export
|
|
461
|
+
`.`.** `./define` is frozen with them, on seven releases. Both are frozen on
|
|
462
|
+
evidence that they had stopped moving rather than on intention: the CLI's last
|
|
463
|
+
change of shape was `--apply` on 2026-08-03, twelve releases before the
|
|
464
|
+
decision, and `src/index.ts` has had its export list edited in three commits
|
|
465
|
+
ever.
|
|
466
|
+
|
|
467
|
+
**Not frozen, explicitly: `./inspect`.** It shipped in `[0.9.2]` with two
|
|
468
|
+
releases and one adapter behind it, and freezing on one adapter is the
|
|
469
|
+
objection this project raises against every premature interface and does not
|
|
470
|
+
get to waive for its own. Stating the exclusion is the point: a version number
|
|
471
|
+
alone would have said the opposite.
|
|
472
|
+
|
|
473
|
+
**What the freeze costs is stated rather than discovered later.** Under 1.0 a
|
|
474
|
+
narrowing that closes a security hole takes a major, on whatever schedule a
|
|
475
|
+
hole is found. `[0.11.0]` is what made that price knowable — it recorded that
|
|
476
|
+
such a narrowing takes the bump its definition gives it, and that the
|
|
477
|
+
`callsAuthoringFn` fix would have been a 2.0 twenty-five days after a 1.0 cut
|
|
478
|
+
at the fourth breaking commit.
|
|
479
|
+
|
|
480
|
+
**The line was decided with neither frozen surface enforced by anything, and
|
|
481
|
+
that is no longer true.** A frozen surface with no gate on its shape is a
|
|
482
|
+
promise nothing enforces, and the measurement behind the decision recorded when
|
|
483
|
+
each surface last *moved*, never what would notice if it moved again. `ATX-86`
|
|
484
|
+
and `ATX-87` in this release close both halves: the eleven type-only names and
|
|
485
|
+
four values of the root export, read from the emitted declaration file and the
|
|
486
|
+
emitted JavaScript separately, and the seven commands and seventeen options of
|
|
487
|
+
the CLI, read from the help it prints. Neither existed when the line was
|
|
488
|
+
decided.
|
|
489
|
+
|
|
490
|
+
**The exclusion list is one item longer than the four surfaces, and the fifth
|
|
491
|
+
is the one that moved.** The workflow document `attest init` writes into an
|
|
492
|
+
adopter's repository is deliberately not frozen — it is a function of the
|
|
493
|
+
installed version, so freezing it would freeze the workflow the tool teaches.
|
|
494
|
+
A 0.9.1 → 0.11.0 upgrade report measured it at **110 changed lines over 9
|
|
495
|
+
hunks**, still stating a `never-red` ordering rule `[0.10.0]` withdrew, with
|
|
496
|
+
four gates green and nothing reporting it. What 1.0 does **not** do is make
|
|
497
|
+
that drift detectable; the question is open at the foot of this file, and the
|
|
498
|
+
release is explicit about it rather than quiet.
|
|
499
|
+
|
|
500
|
+
**1.0 settles what an adopter may build on and unblocks nothing else.** The
|
|
501
|
+
judgement schema and any retrieval surface over the archive stay under `Under
|
|
502
|
+
consideration` and are explicitly 1.x. The one adoption crossing both of
|
|
503
|
+
`[0.11.0]`'s breaking changes paid nothing — no file edited, no peer upgrade,
|
|
504
|
+
no gate going red — but that sample dodged both narrowings, so what is measured
|
|
505
|
+
is that the release cost a *conforming* adopter zero, not that either narrowing
|
|
506
|
+
is cheap. And what a release costs its adopters is not known on the day it is
|
|
507
|
+
cut, which the foot of this file records as a general rule and which applies to
|
|
508
|
+
this one: the freeze is cut on the strength of the line being **correct**, not
|
|
509
|
+
on evidence that it is **sufficient**.
|
|
510
|
+
|
|
38
511
|
## [0.11.0] - 2026-08-28
|
|
39
512
|
|
|
40
513
|
### Added
|
|
@@ -985,7 +1458,7 @@ paragraph exists to make visible.
|
|
|
985
1458
|
one scenario asserting the *discrimination* rather than two asserting each
|
|
986
1459
|
side, which fails now and still refuses an implementation that warns
|
|
987
1460
|
unconditionally later. This is the mechanism catching the exact defect class
|
|
988
|
-
the tautology entry
|
|
1461
|
+
the tautology entry at the foot of this file is about, on the first change
|
|
989
1462
|
it was ever pointed at, in the author's own work.
|
|
990
1463
|
|
|
991
1464
|
*A second defect surfaced after `--apply` and is worth recording because
|
|
@@ -4579,7 +5052,8 @@ field moved.
|
|
|
4579
5052
|
the record would not need to exist at all. So it is the one thing Attest writes
|
|
4580
5053
|
that cannot be checked by regenerating it, and what stands in for that check is
|
|
4581
5054
|
a reviewer. (The workflow document has no gate either, but that is a decision —
|
|
4582
|
-
`init --check` was rejected,
|
|
5055
|
+
`init --check` was rejected, and has since been reopened at the foot of this
|
|
5056
|
+
file — not an impossibility.)
|
|
4583
5057
|
Landed in `troubleshooting.md#never-red` and under `attest archive` in the CLI
|
|
4584
5058
|
reference (both languages), and in the workflow document `attest init` writes —
|
|
4585
5059
|
where it also became a fourth entry in "things you must not do", beside editing
|
|
@@ -4791,9 +5265,10 @@ field moved.
|
|
|
4791
5265
|
the first fix written: it makes both maps resolve, at 195 kB and 148 files, to
|
|
4792
5266
|
serve stepping into the engine from an adopter's `node_modules` — which
|
|
4793
5267
|
nothing has asked for, and which anyone auditing the reader would do from the
|
|
4794
|
-
repository instead. This file has rejected `
|
|
4795
|
-
|
|
4796
|
-
|
|
5268
|
+
repository instead. This file has rejected `divergent-param` and countable
|
|
5269
|
+
obligations for costing more than a demonstrated need — and rejected
|
|
5270
|
+
`init --check` on the same standard, an entry since reopened on measured
|
|
5271
|
+
drift — and that standard does not bend for a packaging decision. Turning the two compiler
|
|
4797
5272
|
options off is the cleanest published output, since it leaves no dangling
|
|
4798
5273
|
`sourceMappingURL` comment, but it also removes the maps from `dist/` here,
|
|
4799
5274
|
where `verify self` runs through the built CLI and they are the only thing
|
|
@@ -5389,9 +5864,10 @@ every platform, which is what makes them work on Windows at all.
|
|
|
5389
5864
|
than assumed: there isn't one, and that is the design.** A committed
|
|
5390
5865
|
instruction file is a function of the installed version, so it does go stale on
|
|
5391
5866
|
upgrade — but staleness only *costs* anything if the tool also ships a gate to
|
|
5392
|
-
turn red about it, and `init --check`
|
|
5393
|
-
|
|
5394
|
-
|
|
5867
|
+
turn red about it, and `init --check` was rejected on its own terms, together
|
|
5868
|
+
with the exposure that leaves and the backstop that keeps it small — an entry
|
|
5869
|
+
since reopened at the foot of this file, on the first measurement of that
|
|
5870
|
+
exposure and the failure of that backstop. What belongs here is what the absence of a gate
|
|
5395
5871
|
bought: with none to serve there is no reason to keep the prose out of the file
|
|
5396
5872
|
either, so it lives in the skill, where an agent reads it with no extra call,
|
|
5397
5873
|
and a skill body is loaded on demand — a long document costs context only when
|
|
@@ -6284,317 +6760,6 @@ deletion. What each entry has to carry is the evidence currently blocking the
|
|
|
6284
6760
|
decision, because that is what a re-proposal a year from now would otherwise
|
|
6285
6761
|
have to gather again.
|
|
6286
6762
|
|
|
6287
|
-
- **An assertion that recomputes its expectation from the param the code under
|
|
6288
|
-
test just read is a tautology, and nothing says so.** Reported by
|
|
6289
|
-
`mine-capablanca`, which probed it by mutating the registry and running the
|
|
6290
|
-
full gate on each mutation:
|
|
6291
|
-
|
|
6292
|
-
| mutation | who reads it | `check` | `verify` |
|
|
6293
|
-
|---|---|---|---|
|
|
6294
|
-
| `GEN-1.maxCandidatesPerPiece` 64 → 4 | scenario only | ok | **red** |
|
|
6295
|
-
| `BOARD-1.boardSize` 10 → 12 | app + fixtures | ok | **red** |
|
|
6296
|
-
| `BOARD-2.maxMines` 12 → **40** | app + assertion | ok | **green** |
|
|
6297
|
-
| `BOARD-2.minMines` 9 → **1** | app + assertion | ok | **green** |
|
|
6298
|
-
|
|
6299
|
-
The mine-density band — the one number deciding whether that game is playable —
|
|
6300
|
-
moved by 3.3× with the whole suite green, `check` silent, and `possible-drift`
|
|
6301
|
-
correctly not firing, because a covering scenario *does* read the param.
|
|
6302
|
-
**Read the third and fourth rows against the second, because that is the
|
|
6303
|
-
finding.** The reporter's diagnosis was "app and spec both read it", but row 2
|
|
6304
|
-
is app-and-spec and goes red. What distinguishes the green rows is narrower and
|
|
6305
|
-
more useful: the assertion **derives its expectation from the same param the
|
|
6306
|
-
code under test used**, so both sides move together and the comparison has no
|
|
6307
|
-
independent term. Row 2's expectation lives in fixtures, which do not move when
|
|
6308
|
-
the param does, and the mechanism works. So the failing condition is a property
|
|
6309
|
-
of the assertion, not of who reads the value — which matters, because it means
|
|
6310
|
-
the answer is a rule about how to write the assertion, and *not* the detector
|
|
6311
|
-
the reporter proposed.
|
|
6312
|
-
**What this falsifies is written in this file.** The rejection of a committed
|
|
6313
|
-
params snapshot, below, closes on *"the mechanism that guards a param is a
|
|
6314
|
-
scenario reading it, not a file recording it"* — a sufficiency claim, and rows
|
|
6315
|
-
3 and 4 are counterexamples to it. A scenario reading the param is necessary
|
|
6316
|
-
and not sufficient. That entry stays rejected on its own argument, which was
|
|
6317
|
-
never about detection power, but its fallback clause is now known incomplete
|
|
6318
|
-
and is annotated accordingly. The README's §"killer move" has the same shape:
|
|
6319
|
-
it says noticing a param *moved* is `verify`'s job "only when a scenario asserts
|
|
6320
|
-
on the value it read from `params`" — literally a necessary condition, and read
|
|
6321
|
-
by an adopter as a sufficient one, with the very next line (*"Read the param
|
|
6322
|
-
inside the assertion, not beside it"*) being the instruction that produces the
|
|
6323
|
-
tautology.
|
|
6324
|
-
**The framing that probably resolves it: this is `never-red` wearing different
|
|
6325
|
-
clothes.** A tautological assertion is a scenario that cannot fail, which is
|
|
6326
|
-
the exact defect `archive`'s `never-red` gate exists to catch — and it did
|
|
6327
|
-
catch two of them in this very repo (`CHECK-2`, `FOG-3`, both confirmed
|
|
6328
|
-
correct by the reporter). The reason it did not catch these is structural:
|
|
6329
|
-
**changing the value of an existing param goes through no gate at all.**
|
|
6330
|
-
`archive` gates a change; a param edit is not a change. That is a cleaner
|
|
6331
|
-
statement of the gap than "params can be tautological".
|
|
6332
|
-
*Re-read against `evidence-not-ordering`, which separated that gate's
|
|
6333
|
-
obligation from the ordering it used to be described by.* Nothing here moves:
|
|
6334
|
-
the blocker was never that the observation had to be taken first, it is that a
|
|
6335
|
-
param edit produces no change folder for any observation to be recorded
|
|
6336
|
-
against. If anything the framing gets sharper — what `never-red` demands is
|
|
6337
|
-
evidence that a scenario can fail, which is precisely what is missing for a
|
|
6338
|
-
tautological assertion, and the reason it goes undemanded is still the absence
|
|
6339
|
-
of a gate rather than the absence of a route to the evidence.
|
|
6340
|
-
**That observation is now its own entry at the foot of this section**, split
|
|
6341
|
-
out and measured on 2026-08-18, because it asks what happens when a value
|
|
6342
|
-
moves rather than whether an assertion is a real comparison, and neither
|
|
6343
|
-
answer settles the other. What stays here is the detector.
|
|
6344
|
-
*What blocks the decision — three things, all of them real.* **(1)** Every
|
|
6345
|
-
mutation above is synthetic. Across 8 commits touching `*.reqs.ts` in that
|
|
6346
|
-
repo, **no param value was ever modified** — every `params:` line in every diff
|
|
6347
|
-
is an addition. So the hole is proven as a logical property and its frequency is
|
|
6348
|
-
entirely unmeasured, from one greenfield repo. **(2)** The reporter's proposed
|
|
6349
|
-
mechanism — a WARNING when one param id is read from both a spec and a non-spec
|
|
6350
|
-
file — is the wrong shape twice over: it fires on row 2, which is correct usage
|
|
6351
|
-
and goes red, and it would fire on the arrangement the README actively
|
|
6352
|
-
recommends, which is §9's "usually wrong about staleness" failure mode again.
|
|
6353
|
-
It also needs a static sweep of the whole project for `reqs['X'].params.y`,
|
|
6354
|
-
far past the two registry readers' current surface. **(3)** No mechanism is yet
|
|
6355
|
-
known that separates "the assertion pins this independently" from "the assertion
|
|
6356
|
-
echoes it" without executing the scenario twice under different values — which
|
|
6357
|
-
is mutation testing, a much larger thing to own. *One has since been built and
|
|
6358
|
-
scored, and it does not lift this blocker.* The syntactic candidate — flag a
|
|
6359
|
-
scenario whose expectation is derived from the param the code under test
|
|
6360
|
-
consumed, decided inside one body without executing anything — flags 9 params
|
|
6361
|
-
here, of which **4 are real echoes and 5 are pinned by something outside the
|
|
6362
|
-
body it cannot see**. Under half precision, and the failure is the one
|
|
6363
|
-
predicted above rather than a tuning problem: what pins a value is often a
|
|
6364
|
-
fixture or a second derivation in another file, so a single-body detector is
|
|
6365
|
-
looking in the wrong place by construction. The newest false positive is the
|
|
6366
|
-
clearest instance — `ATX-79.subpath` is compared against `package.json`, which
|
|
6367
|
-
is not the code under test and does not read the registry, so the independent
|
|
6368
|
-
term is in another file entirely. Precision has fallen on every addition
|
|
6369
|
-
measured so far, which is the direction that matters. The score is quoted from the
|
|
6370
|
-
suite's snapshot and re-read with the judgement-table entry above, which is
|
|
6371
|
-
where the same measurement is used for a different decision.
|
|
6372
|
-
**The guidance half has shipped; this entry is now only about the detector.**
|
|
6373
|
-
"Read the param inside the assertion" was insufficient and half-wrong as
|
|
6374
|
-
written, and `[0.5.0]` replaces it everywhere it appeared — README, the
|
|
6375
|
-
`init` skill, `possible-drift`, and design §11 — with what the evidence
|
|
6376
|
-
supports: **the expectation must not be a function of the param the code under
|
|
6377
|
-
test consumed**, pinned in a fixture, a literal, or a second independently
|
|
6378
|
-
derived value. Row 2 is the worked example, and the reporter's own
|
|
6379
|
-
`CHECK-2`/`FOG-3` fixes are the same move. It was held back once because
|
|
6380
|
-
rewording the central claim of the tool is not a line appended; that is what
|
|
6381
|
-
was then done, deliberately and in one pass.
|
|
6382
|
-
**What stays undecided is the mechanism**, and all three blockers above are
|
|
6383
|
-
untouched by the rewording: the frequency is still unmeasured from one
|
|
6384
|
-
greenfield repo, the reporter's proposed detector is still the wrong shape
|
|
6385
|
-
twice over, and nothing short of mutation testing separates an independent pin
|
|
6386
|
-
from an echo. What the guidance does not do is close the hole — a tautological
|
|
6387
|
-
assertion is still green, still silent, and still reachable — so this stays
|
|
6388
|
-
here rather than moving to `Considered and rejected`: documenting a trap is
|
|
6389
|
-
not the same as deciding not to detect it.
|
|
6390
|
-
**Re-measured after `[0.7.1]` widened `params`, on this registry rather than
|
|
6391
|
-
the reporter's — the first time this experiment has been run here.** Twelve
|
|
6392
|
-
mutations, each followed by the whole gate. Two results matter, and they point
|
|
6393
|
-
opposite ways.
|
|
6394
|
-
*The shape is now reachable here, and the widening is why.* `self/render.spec.ts`
|
|
6395
|
-
asserts `expect(md).toContain(JSON.stringify(kinds))` over the same
|
|
6396
|
-
`payloadKinds` it feeds to the renderer — the expectation is a function of the
|
|
6397
|
-
value under test, verbatim row 3, and it could not have been written before
|
|
6398
|
-
the widening because the param could not be an object. The mechanism is
|
|
6399
|
-
sharper than a frequency count and is the strongest thing this entry has
|
|
6400
|
-
gained: **the prescribed remedy costs in proportion to the value's
|
|
6401
|
-
complexity.** Same repo, adjacent files, same value shape — `ATX-7` pins two
|
|
6402
|
-
numbers with literals and goes red; `ATX-63` would have had to hand-write a
|
|
6403
|
-
rendered JSON block, so it echoed. Depth raises the defect rate and the cost
|
|
6404
|
-
of avoiding it along one axis.
|
|
6405
|
-
*And its cost here is nil, which is the honest other half.* Both composite
|
|
6406
|
-
params are fixtures for the renderer and the validator; the numbers stand for
|
|
6407
|
-
nothing, so the green is arguably correct. Blocker **(1)** is therefore only
|
|
6408
|
-
half retired — the shape is reproduced in a second, mature repo, and the count
|
|
6409
|
-
of *harmful* instances is still zero. Blocker **(2)** is reinforced rather than
|
|
6410
|
-
weakened: all four green mutations were on params `src/` never reads, so the
|
|
6411
|
-
reporter's "read from both a spec and a non-spec file" detector would have
|
|
6412
|
-
flagged none of them. Blocker **(3)** is untouched.
|
|
6413
|
-
*The frequency is no longer unmeasured, and it points away from building
|
|
6414
|
-
anything.* Blocker **(1)** rested on 8 commits of one greenfield repo. Parsed
|
|
6415
|
-
at every one of the **38** revisions that have touched this registry — all 38
|
|
6416
|
-
readable by the current static reader, so nothing was skipped — the history is
|
|
6417
|
-
**57 param keys added, 0 removed, and exactly 1 value changed**: `ATX-16.staticCommands`
|
|
6418
|
-
gaining `status`. That single edit is a roster *widening*, the safe direction,
|
|
6419
|
-
and it is on the one list that was already pinned, so it could not have been
|
|
6420
|
-
made without updating the pin. Two independent codebases, one greenfield and
|
|
6421
|
-
one at 64 requirements, and the event this whole entry is about has happened
|
|
6422
|
-
zero times in either. What that argues is not that the hole is unreal — it is
|
|
6423
|
-
reachable, demonstrated above — but that it is **rare and silent**, which is
|
|
6424
|
-
the profile `divergent-param` was rejected on rather than the profile that
|
|
6425
|
-
justifies an engine rule.
|
|
6426
|
-
*The same number says the split-out fix below has also never been triggered.*
|
|
6427
|
-
0 removals ever means no domain has ever been shortened here. Those pins close
|
|
6428
|
-
a live *exposure*, not a live *loss*, and the case for them is that they cost
|
|
6429
|
-
one line each against a failure with no symptom — not that anything was
|
|
6430
|
-
currently broken.
|
|
6431
|
-
*Blocker **(3)** assumed mutation testing was the only separator. A cheaper
|
|
6432
|
-
candidate existed, has now been scored, and does not survive it.* The idea was
|
|
6433
|
-
a **syntactic** discriminator over one test body — flag an assertion whose
|
|
6434
|
-
expected value derives from a param that also reached the arrange/act part —
|
|
6435
|
-
which is exactly the difference between the two cases the widening produced:
|
|
6436
|
-
`renderMarkdown({params: {payloadKinds: kinds}})` … `toContain(JSON.stringify(kinds))`
|
|
6437
|
-
against `expect(kinds.json.maxBytes).toBe(1_048_576)`. Scored the way
|
|
6438
|
-
`divergent-param` should have been and was not: implemented in
|
|
6439
|
-
`tests/intent-rule-candidates.spec.ts`, run over the specs that attest this
|
|
6440
|
-
repo, and then **every param it fires on mutated with the whole gate run —
|
|
6441
|
-
fifteen of them, one gate run each. Four went green; eleven went red.**
|
|
6442
|
-
*Then the detector was found to be wrong, which is the more useful half.* Its
|
|
6443
|
-
first implementation counted an identifier in any position as a read of a
|
|
6444
|
-
local with that name, so `i.code` in `issues.map((i) => i.code)` looked like a
|
|
6445
|
-
use of a param named `code` — and `code` is this repo's commonest param name.
|
|
6446
|
-
Corrected to count reference positions only, it flags **8, not 15**, and the
|
|
6447
|
-
seven it drops are exactly seven the mutations had already shown to be pinned
|
|
6448
|
-
by `src/`. The honest score is therefore **4 of 8, not 4 of 15** — precision
|
|
6449
|
-
50%, not 27%. The conclusion does not move, because it never rested on the
|
|
6450
|
-
precision figure; what moves is how much of the original number was the
|
|
6451
|
-
candidate failing and how much was the measurement being wrong, and that is
|
|
6452
|
-
worth writing down rather than quietly restating.
|
|
6453
|
-
The remaining false positives are not near-misses. They are independent terms
|
|
6454
|
-
the detector cannot see: **the runtime is the term** — `ATX-14`'s
|
|
6455
|
-
`inheritedKey` is `'toString'`, and a mutated spelling is not a prototype key
|
|
6456
|
-
at all, so the behaviour under test changes; **`src/` is the term** —
|
|
6457
|
-
`ATX-51`'s `code` is a second copy of a string the engine emits as a literal;
|
|
6458
|
-
and **arithmetic is the term** — `threshold + 3` builds a derived expectation
|
|
6459
|
-
that mutation breaks.
|
|
6460
|
-
And the four true positives fail the second test, which is the one that
|
|
6461
|
-
decides it: **none is harmful.** `ATX-10` and `ATX-3` assert that a value was
|
|
6462
|
-
substituted or applied, where the identity of the number is not the
|
|
6463
|
-
obligation; `ATX-51`'s is a path prefix and `ATX-63`'s a rendering fixture.
|
|
6464
|
-
Telling those from a mine-density band that must not move is a judgement about
|
|
6465
|
-
whether the value *means* anything — the ground truth §0 refuses to compute
|
|
6466
|
-
for someone else's registry. **50% precision on the shape, 0% on the harm.**
|
|
6467
|
-
Two limits on that number, stated because a score with an unstated method is
|
|
6468
|
-
what this file exists to prevent. The mutation has **one direction per kind**
|
|
6469
|
-
— numbers +1, lists drop-last, strings suffixed — and direction can decide the
|
|
6470
|
-
verdict: `ATX-40.languages` is red at 2→3 and was green at 2→1 before it was
|
|
6471
|
-
pinned, so the true-positive count is a floor. And **recall is unmeasured**:
|
|
6472
|
-
the eight params mutated earlier that this detector does not flag all went
|
|
6473
|
-
red, which is eight correct negatives and not a recall figure.
|
|
6474
|
-
The detector and its verdict table are kept in the measurement suite, with the
|
|
6475
|
-
reach recomputed on every run and the table asserted to cover exactly what
|
|
6476
|
-
fires — because the reach figure quoted for `compound-requirement` went stale
|
|
6477
|
-
in this very file for want of that.
|
|
6478
|
-
*A scoping limit on all of the above.* This repo cannot settle blocker **(2)**.
|
|
6479
|
-
`src/` reads no param at all — every `reqs[…].params` in it is a comment or a
|
|
6480
|
-
doc string — so params here are test vocabulary, while in the reporter's repo
|
|
6481
|
-
they are application constants the app itself consumes. The reporter's
|
|
6482
|
-
detector keys on exactly that difference, so it is inert here and this corpus
|
|
6483
|
-
is evidence neither for nor against it.
|
|
6484
|
-
*What the re-measurement did find is a different defect, and it has been split
|
|
6485
|
-
out below* — the entry is kept whole here because the two were measured in one
|
|
6486
|
-
pass and separating them is the finding.
|
|
6487
|
-
|
|
6488
|
-
- **Whether a requirement can ever be retired, and what it costs that none can.**
|
|
6489
|
-
The registry is at **85 requirements and 240 scenarios, 2.82 each**, and
|
|
6490
|
-
every one is a permanent obligation: `uncovered-requirement` is an ERROR, so a
|
|
6491
|
-
requirement without a scenario cannot exist, and nothing ever removes one.
|
|
6492
|
-
`delta.removed` exists and `applyDelta` applies it, but `--apply` refuses to
|
|
6493
|
-
write it back, deliberately and with the reason recorded — REMOVED "cannot say
|
|
6494
|
-
which comments belonged to the entry it deletes". So retiring one is a manual
|
|
6495
|
-
registry edit, which is fine as a mechanism and says nothing about whether it
|
|
6496
|
-
is ever the right move.
|
|
6497
|
-
*What the growth actually costs, measured on this session rather than
|
|
6498
|
-
estimated.* One small fix took: a requirement, its scenarios, a hand-judged
|
|
6499
|
-
row in the `OBLIGATIONS` table, two inline snapshots, a `[Unreleased]` entry,
|
|
6500
|
-
History rows in two languages, and a regenerated `SPEC.md`. That is
|
|
6501
|
-
proportionate for a spec framework that has to dogfood — the ceremony *is* the
|
|
6502
|
-
product being used on itself — and it is also a number that only goes up.
|
|
6503
|
-
*What blocks the decision, and it is not the mechanism.* It is that "retire"
|
|
6504
|
-
has at least three meanings this file has never had to separate: a requirement
|
|
6505
|
-
that turned out **wrong** (which should leave a record of being wrong, and
|
|
6506
|
-
`Considered and rejected` is where that already goes); one still **true** but
|
|
6507
|
-
no longer worth a permanently-run scenario; and one **subsumed** by a later
|
|
6508
|
-
requirement, where the scenario should move rather than go. Only the second is
|
|
6509
|
-
really about cost, and it is the one where deleting the intent to save the
|
|
6510
|
-
scenario inverts what the whole tool claims — that intent outlives its tests.
|
|
6511
|
-
Nothing here is urgent at 85. It is filed now because the moment to write the
|
|
6512
|
-
argument down is while the reasoning is available, and because a project whose
|
|
6513
|
-
requirement count only rises should have decided in advance what would make
|
|
6514
|
-
one leave.
|
|
6515
|
-
*The figures above were restated on 2026-08-08, and how they were wrong is
|
|
6516
|
-
itself the section's own warning happening.* As filed on 2026-08-03 this entry
|
|
6517
|
-
said "62 requirements and 160 scenarios, roughly 2.6 each" while closing on
|
|
6518
|
-
"the number is 61" — an inconsistency inside one entry, and the closing half
|
|
6519
|
-
was the right one. Re-measured at the entry's own commit with the current
|
|
6520
|
-
parser: **61 requirements, 156 scenarios**, so both opening figures were wrong
|
|
6521
|
-
on the day they were written, and only the ratio survived. Measured the same
|
|
6522
|
-
way today it is 69 and 192, and the method is the same one `verify self`
|
|
6523
|
-
prints — the control is that today's `self/` counted this way returns exactly
|
|
6524
|
-
what the runner does, which is what makes the 2026-08-03 number trustworthy. The
|
|
6525
|
-
cheap way — `grep -c 'scenario('` — answers 211 against today's true 192, and
|
|
6526
|
-
is what would have been reached for: comments and prose count, which is the
|
|
6527
|
-
`ATX-15` error again.
|
|
6528
|
-
**What moved in five days sharpens the argument rather than dating it.** +8
|
|
6529
|
-
requirements and +36 scenarios, concentrated in three sessions rather than
|
|
6530
|
-
accrued at a rate, so nothing here is a trend line. The part worth keeping is
|
|
6531
|
-
the ratio: 2.56 → 2.78. The cost of a requirement is not flat and it is not
|
|
6532
|
-
falling, which is the premise the whole entry rests on, and it is the first
|
|
6533
|
-
time this file has had two measurements of it to compare.
|
|
6534
|
-
*A third measurement, on 2026-08-18, and it points the other way from the
|
|
6535
|
-
second.* **79 requirements and 221 scenarios**, counted the way `verify self`
|
|
6536
|
-
prints them, which is the control the 2026-08-08 restatement established. The
|
|
6537
|
-
ratio runs 2.56 → 2.78 → **2.80**, so the step that carried the argument was
|
|
6538
|
-
the first one and the second is **+0.015**, which is flat. The premise this
|
|
6539
|
-
entry rests on now has one measurement for it and one against, and the honest
|
|
6540
|
-
statement is the narrower one: per-requirement cost has held at about 2.8
|
|
6541
|
-
scenarios across ten days and ten requirements. What is unchanged is the
|
|
6542
|
-
absolute total, which only rises — that half of the argument is untouched, and
|
|
6543
|
-
it was always the half about permanence rather than about acceleration. What is
|
|
6544
|
-
no longer supported is the acceleration. **Reopening on cost needs the ratio to
|
|
6545
|
-
move again**; reopening on the three meanings of "retire" needs none of these
|
|
6546
|
-
figures, and that is still the part nobody has separated.
|
|
6547
|
-
*A fourth measurement, on 2026-08-28, and the cost half is now closed rather
|
|
6548
|
-
than merely unsupported.* **85 requirements and 240 scenarios**, counted the
|
|
6549
|
-
way `verify self` prints them: the ratio runs 2.56 → 2.78 → 2.80 → **2.82**.
|
|
6550
|
-
Three consecutive readings within 0.05 of each other, across twenty-five days
|
|
6551
|
-
and twenty-four requirements, are not a curve that is about to move. The
|
|
6552
|
-
reopening condition this entry set itself has now failed to trigger twice, so
|
|
6553
|
-
the cost argument is not waiting for evidence — it has been answered, in the
|
|
6554
|
-
negative, by its own test. What survives untouched is the half that was never
|
|
6555
|
-
about the ratio: the absolute total only rises, and permanence is the claim.
|
|
6556
|
-
|
|
6557
|
-
**The three meanings, separated — which is what the entry has been asking for
|
|
6558
|
-
and needs none of the figures above.** They are not one question, and only one
|
|
6559
|
-
of the three is open.
|
|
6560
|
-
|
|
6561
|
-
**(1) A requirement that turned out wrong.** Not a retirement at all, and the
|
|
6562
|
-
mechanism already exists: the record of being wrong belongs in `Considered and
|
|
6563
|
-
rejected`, which is what that section is, and removing the id is a manual
|
|
6564
|
-
registry edit nobody has argued against. Nothing is missing here. Filing it
|
|
6565
|
-
under "retire" was what made the question look bigger than it is.
|
|
6566
|
-
|
|
6567
|
-
**(2) A requirement still true but no longer worth a permanently-run
|
|
6568
|
-
scenario.** The only one of the three that is about cost, and the only one with
|
|
6569
|
-
no mechanism — and the sharpest thing this separation turned up is that the
|
|
6570
|
-
tool has been *advertising* one. `uncovered-requirement` reads "Add a scenario,
|
|
6571
|
-
or **mark it deferred**", and `deferred` appears nowhere else in `src/`: not a
|
|
6572
|
-
field, not a flag, not a schema key. The troubleshooting section for that code
|
|
6573
|
-
quotes the message as its symptom and then explains only the first half,
|
|
6574
|
-
because there is no second half to explain. So the escape hatch this meaning
|
|
6575
|
-
needs has been named in shipped output, and in the one place a reader looks
|
|
6576
|
-
when they hit the error, since before anyone asked the question. That is a
|
|
6577
|
-
defect in its own right — the message is prose and free to reword — but it is
|
|
6578
|
-
recorded here rather than fixed there, because rewording it would delete the
|
|
6579
|
-
only written trace that this affordance was ever intended, which is the thing
|
|
6580
|
-
this entry exists to keep.
|
|
6581
|
-
|
|
6582
|
-
**(3) A requirement subsumed by a later one**, where the scenario should move
|
|
6583
|
-
rather than go. That is a rename, and `--apply` refuses RENAMED outright, with
|
|
6584
|
-
the reason recorded in `splice.ts` — deleting an entry has no smaller span to
|
|
6585
|
-
fall back to. So it is blocked downstream of a refusal nobody has proposed
|
|
6586
|
-
lifting, which is the *same* blocker the backlink-index entry above carries.
|
|
6587
|
-
Two open entries now rest on that one refusal, which is worth knowing: it makes
|
|
6588
|
-
lifting it a decision with two dependents rather than one, and neither of them
|
|
6589
|
-
is the reason it exists.
|
|
6590
|
-
|
|
6591
|
-
*So the entry narrows to one meaning.* (1) is answered, (3) is somebody else's
|
|
6592
|
-
blocker, and what is actually open is (2) — whether a requirement can stop
|
|
6593
|
-
owing a permanently-run scenario while keeping its intent, which is the one
|
|
6594
|
-
case where deleting the intent to save the scenario inverts what the whole tool
|
|
6595
|
-
claims. Nothing here says it should be built; what changes is that the question
|
|
6596
|
-
is now one question.
|
|
6597
|
-
|
|
6598
6763
|
- **Running `verify` over a subset of the scenarios, and what a partial run may
|
|
6599
6764
|
be allowed to say.** `verify` is all-or-nothing and `check` executes nothing,
|
|
6600
6765
|
so there is no middle. Reported by an adoption where `verify` takes **7m33s**
|
|
@@ -6727,6 +6892,554 @@ have to gather again.
|
|
|
6727
6892
|
and the second report of the same shape, which this measurement does not
|
|
6728
6893
|
supply and does not replace.
|
|
6729
6894
|
|
|
6895
|
+
*The second report arrived on 2026-08-31 and it is not the second report this
|
|
6896
|
+
entry asked for*, which is itself the finding. A 0.9.1 → 0.11.0 upgrade report
|
|
6897
|
+
from `mine-capablanca` — the same adoption this entry was filed on, six weeks
|
|
6898
|
+
later, now at 46 spec files, 170 scenarios and a `verify` median of **564
|
|
6899
|
+
seconds** against the 7m33s originally reported — asks for nothing named here.
|
|
6900
|
+
It does not ask for a selector.
|
|
6901
|
+
*The cost claim reproduces a third time, and the speed argument is finished.*
|
|
6902
|
+
`check` against `verify` on that project is **3,403 ms against 564,279 ms**,
|
|
6903
|
+
and the reporter is careful that this is a lower bound on engine share rather
|
|
6904
|
+
than an estimate of it, since `check` also pays a process start `verify` pays
|
|
6905
|
+
once. Either way the direction is settled on a corpus eighteen times slower
|
|
6906
|
+
than this one: over 99% of a `verify` there is that project's own search engine
|
|
6907
|
+
running. A selector saves no engine work anywhere, and now on two registries.
|
|
6908
|
+
*The parity argument — the one this entry called the strongest available —
|
|
6909
|
+
could not be tested there, and why it could not is a fact about it.* That
|
|
6910
|
+
project has **no `vitest.config.ts` and no `vite.config.ts`**, no spec
|
|
6911
|
+
importing through an alias, and no DOM anywhere in its specs, so `config:
|
|
6912
|
+
false` withholds nothing a direct run would have had. `--vitest-config` is
|
|
6913
|
+
passed by no script and no pipeline; it never became necessary. That does not
|
|
6914
|
+
refute the argument, it **bounds** it: the parity gap is real only for an
|
|
6915
|
+
adopter carrying a config, the population is therefore smaller than "adopters",
|
|
6916
|
+
and nobody has yet reported being in it.
|
|
6917
|
+
*What that adoption does have is a divergence, and it is not the one this entry
|
|
6918
|
+
predicted.* On the same tree, `npx vitest run` was 170/170 green while `attest
|
|
6919
|
+
verify` reported `tests-red` on **two runs out of three**. The mechanism the
|
|
6920
|
+
reporter reaches — labelled there as the strongest available inference and not
|
|
6921
|
+
as an observation — is one scenario measured at **4,971 ms against Vitest's
|
|
6922
|
+
undeclared 5,000 ms default**: 1.3 s alone, four times slower under suite
|
|
6923
|
+
contention, and the only one of eleven scenarios over four seconds that
|
|
6924
|
+
declares no explicit `timeoutMs`. That is `CLAUDE.md`'s own timing gotcha
|
|
6925
|
+
arriving in somebody else's repository, where no `CLAUDE.md` carries it.
|
|
6926
|
+
*So the residue of this entry is a different entry.* What that reporter needed
|
|
6927
|
+
was not a way to run less; it was a way to learn **which** of 170 scenarios was
|
|
6928
|
+
red, and `verify` cannot say. That is filed separately below rather than folded
|
|
6929
|
+
in here, because a selector and a diagnosis are different artifacts and this
|
|
6930
|
+
entry has already been narrowed twice by refusing to let them merge.
|
|
6931
|
+
*What is left blocking this one, restated:* an adopter who carries a Vitest
|
|
6932
|
+
config, and whose fast loop and verdict actually diverge because of it. That is
|
|
6933
|
+
the whole of it. The stage-2 cost argument above is unchanged and still comes
|
|
6934
|
+
from this repository rather than from a user.
|
|
6935
|
+
|
|
6936
|
+
- **`verify` reports that the suite is red and cannot say which scenario, and
|
|
6937
|
+
the documented way to find out fails in exactly the case that needs it.**
|
|
6938
|
+
Filed 2026-08-31 from a 0.9.1 → 0.11.0 upgrade report from `mine-capablanca`.
|
|
6939
|
+
`verify` and `archive` run the child suite with `quiet: true` because the CLI
|
|
6940
|
+
renders its own report, so the whole of a failure is one `tests-red` issue with
|
|
6941
|
+
no file, no scenario and no assertion — in the human output and in `--json`
|
|
6942
|
+
alike. The standing answer is to run the suite yourself under Vitest's
|
|
6943
|
+
reporter, which is what this repository does with `pnpm debug:self`.
|
|
6944
|
+
*The report is that this answer is circular where it is needed.* On that
|
|
6945
|
+
project `npx vitest run` was green, twice, on the same tree on which `verify`
|
|
6946
|
+
was red two runs in three. Running the suite yourself reproduces nothing,
|
|
6947
|
+
because the disagreement between the two runs **is** the failure. What the
|
|
6948
|
+
reporter did instead was time every scenario in the suite, compare alone
|
|
6949
|
+
against in-suite durations, and cross-reference which of the eleven scenarios
|
|
6950
|
+
over four seconds declared no timeout. That is a defensible inference and the
|
|
6951
|
+
report labels it as one; it is not a thing a tool should require.
|
|
6952
|
+
*What separates this from the entry above is that nothing here is a selector.*
|
|
6953
|
+
No scope, no partial verdict, no `--json` field naming what ran, no
|
|
6954
|
+
`SCHEMA_VERSION` question. The run is exactly the run it already is, and what
|
|
6955
|
+
changes is only how much of the child's output survives into the report.
|
|
6956
|
+
*What blocks it — and the first blocker is the reason `quiet: true` is there.*
|
|
6957
|
+
**(1)** The CLI renders its own report, and a child suite printing its own on
|
|
6958
|
+
top of that is two reports disagreeing about their own format. Passing the
|
|
6959
|
+
child's failures through means deciding what a `tests-red` issue carries — a
|
|
6960
|
+
file, a scenario name, a message — and each is a new field with a
|
|
6961
|
+
`SCHEMA_VERSION` question attached, which is the cheapest part to price first
|
|
6962
|
+
because it decides whether this is additive at all. **(2)** A failure message
|
|
6963
|
+
is somebody else's assertion text, and this project's rule is that
|
|
6964
|
+
`Issue.message` is prose and never API. Carrying a Vitest assertion into an
|
|
6965
|
+
`Issue` puts a third party's wording in the field consumers are told not to
|
|
6966
|
+
branch on. That is either harmless or a category error and nobody has decided
|
|
6967
|
+
which, and the decision is not obvious in either direction: the wording is not
|
|
6968
|
+
Attest's to keep stable, which is an argument both for carrying it and against.
|
|
6969
|
+
**(3)** The instance that produced this report may not be a diagnosis problem
|
|
6970
|
+
at all. A scenario 29 ms under a default it never declared is a known trap with
|
|
6971
|
+
a known fix, and the cheaper answers are for `verify` to name **the timeout it
|
|
6972
|
+
imposed** when a scenario fails on time, or for the workflow document to say
|
|
6973
|
+
that a scenario anywhere near the default needs an explicit `timeoutMs`. Both
|
|
6974
|
+
are much smaller than passing failures through, and whether either covers the
|
|
6975
|
+
general case is unmeasured, because n=1.
|
|
6976
|
+
*What would move it:* a red `verify` on an adopter's tree whose cause is not a
|
|
6977
|
+
timeout. Every instance anybody has is this one.
|
|
6978
|
+
|
|
6979
|
+
**Blocker (3)'s two cheap answers are built under `[1.1.0]`, and the
|
|
6980
|
+
entry stays open on the other two.** `ATX-88` has `tests-red` name the timeout
|
|
6981
|
+
that applied and how many scenarios reached it, and the workflow document now
|
|
6982
|
+
says the isolated run drops your `testTimeout` along with the rest of your
|
|
6983
|
+
config. Neither touches (1) or (2): no field was added, no `SCHEMA_VERSION`
|
|
6984
|
+
question was asked, and no assertion text from the child crossed into an
|
|
6985
|
+
`Issue`. What the entry proposes — passing the child's failures through — is
|
|
6986
|
+
unchanged and undecided.
|
|
6987
|
+
*Two things the re-measure corrected, and one is the second cheap answer's
|
|
6988
|
+
whole shape.* The workflow-document line was filed as content to write, and it
|
|
6989
|
+
was already written: `tests-red`'s troubleshooting section carries it, worked
|
|
6990
|
+
example included. What was missing was a pointer from the workflow document's
|
|
6991
|
+
own bullet about why a green direct run does not predict the gate, which lists
|
|
6992
|
+
aliases, transforms and a DOM and stops before the clock. And the first
|
|
6993
|
+
answer's phrasing — "the timeout it **imposed**" — was wrong about who imposes
|
|
6994
|
+
it: Attest sets no timeout, it inherits Vitest's default by isolating, so the
|
|
6995
|
+
number has to be read off the resolved child config rather than stated. That
|
|
6996
|
+
distinction is what makes the answer correct under `--vitest-config` too,
|
|
6997
|
+
where the ceiling is the adopter's.
|
|
6998
|
+
*What this does not do, stated so the entry is not read as smaller than it
|
|
6999
|
+
is.* It answers whether the clock was involved. It does not name the scenario,
|
|
7000
|
+
the file or the assertion, so a red `verify` whose cause is not a timeout is
|
|
7001
|
+
exactly as opaque as it was — which is the case this entry is still waiting on
|
|
7002
|
+
an instance of.
|
|
7003
|
+
|
|
7004
|
+
- **Changing the value of an existing param passes no gate, and for seven of
|
|
7005
|
+
them there is no record either.** Split out of the tautology entry that
|
|
7006
|
+
headed this section on 2026-08-18, and which is now under *Considered and
|
|
7007
|
+
rejected*, where it was one sentence inside the
|
|
7008
|
+
reframing paragraph. It is a different question: that entry asks whether an
|
|
7009
|
+
assertion is a real comparison, this one asks what happens when the number
|
|
7010
|
+
itself moves, and neither answer settles the other. `archive` gates a
|
|
7011
|
+
**change** — a delta, proposed specs, a recorded failing run — and a param edit
|
|
7012
|
+
is none of those. It is one literal in a merged `*.reqs.ts`, and every gate
|
|
7013
|
+
reads it as fine: `check` finds it schema-valid, because a param is any JSON
|
|
7014
|
+
value; `possible-drift` is quiet by construction, since `ATX-8` makes it ask
|
|
7015
|
+
whether *any* covering scenario reads the params at all rather than whether a
|
|
7016
|
+
value moved; and `verify` stays green wherever the assertion derives its
|
|
7017
|
+
expectation from the same param, which is the pair of green rows measured in
|
|
7018
|
+
the entry this was split from.
|
|
7019
|
+
*What is left is `render`, and it was measured here rather than assumed.*
|
|
7020
|
+
Across **89 params on 59 of this repository's 79 requirements, 82 — 92.1% —
|
|
7021
|
+
are interpolated into their own statement**, so editing one changes
|
|
7022
|
+
`self/requirements/SPEC.md` and `render --check` fails until the document is
|
|
7023
|
+
regenerated. That is a review surface and not a gate: the new value lands in a
|
|
7024
|
+
committed generated file where a reader can see it, and nothing anywhere asks
|
|
7025
|
+
whether the move was intended.
|
|
7026
|
+
*The other seven leave no trace outside the line that was edited, and which
|
|
7027
|
+
seven they are is the finding rather than the count.* Five are **structurally
|
|
7028
|
+
uninterpolatable**: `ATX-7.payloadKinds` and `ATX-63.payloadKinds` are
|
|
7029
|
+
kind → payload tables, and `ATX-15.sortMethods`, `ATX-15.bannedApis` and
|
|
7030
|
+
`ATX-72.quotes` are lists. A statement cannot carry any of them —
|
|
7031
|
+
`non-scalar-interpolation` is the diagnostic that refuses it — so the params
|
|
7032
|
+
with no record are disproportionately the shape §2 widened the params rule to
|
|
7033
|
+
cover **precisely because lists and tables are the most drift-prone constants
|
|
7034
|
+
there are**. The remaining two, `ATX-15.comparator` and `ATX-26.code`, are
|
|
7035
|
+
scalars their author simply did not interpolate.
|
|
7036
|
+
*What this is not.* It is not a re-proposal of the committed params snapshot
|
|
7037
|
+
rejected below. That was rejected on the argument that the mechanism guarding a
|
|
7038
|
+
param is a scenario reading it rather than a file recording it, and nothing
|
|
7039
|
+
here disputes that or needs a second copy of the values. The observation is
|
|
7040
|
+
narrower: the **edit** is unremarkable to every gate, which is a different
|
|
7041
|
+
claim from the values being unguarded.
|
|
7042
|
+
*Blocker **(1)** was answered on 2026-08-31, in the direction that opens the
|
|
7043
|
+
entry rather than closing it.* It read that frequency is unmeasured and that
|
|
7044
|
+
the one corpus anybody had looked at pointed at zero: 8 commits touching
|
|
7045
|
+
`*.reqs.ts` in the reporting repository, every `params:` line in every diff an
|
|
7046
|
+
addition. A 0.9.1 → 0.11.0 upgrade report from `mine-capablanca` re-ran that
|
|
7047
|
+
count over the **41 revisions** that have touched the same registry, evaluating
|
|
7048
|
+
the file at each revision and comparing key by key rather than reading diffs,
|
|
7049
|
+
and it reads **2 keys added, 1 removed and 7 values changed**. The 8-commit
|
|
7050
|
+
figure was a sample and not an answer; it is withdrawn here and in the
|
|
7051
|
+
tautology entry under `Considered and rejected` that quoted it.
|
|
7052
|
+
*One of the seven landed with nothing capable of objecting, and it is the
|
|
7053
|
+
general case rather than an accident of that commit.* A single commit moved
|
|
7054
|
+
`AI-14.sampledCandidates` from 8 to 3 and `AI-14.samplingTemperature` from 5 to
|
|
7055
|
+
20, and its entire diff is a proposal, a delta, the registry and the
|
|
7056
|
+
regenerated `SPEC.md`. **No spec file and no implementation file.** Nine
|
|
7057
|
+
implementation modules in that project import the registry and read params out
|
|
7058
|
+
of it at runtime, so the opponent's behaviour on the easy setting changed with
|
|
7059
|
+
the suite green — the scenarios read the same two values the implementation
|
|
7060
|
+
reads, so both sides moved together. `render --check` did not object and
|
|
7061
|
+
structurally cannot: it enforces that the rendering *moved*, never that the
|
|
7062
|
+
movement was intended. What caught it was a reviewer on ten lines of `SPEC.md`
|
|
7063
|
+
diff, with a 63-line proposal beside it arguing for exactly that move.
|
|
7064
|
+
*So review is the gate, and the second corpus says it holds for a reason
|
|
7065
|
+
nothing enforces.* That registry is **58 of 58** params interpolated into their
|
|
7066
|
+
own statement, against 82 of 89 — 92.1% — here. Every param edit there moves a
|
|
7067
|
+
line of `SPEC.md`, which is why the commit above was visible at all, and the
|
|
7068
|
+
reporter is explicit that nothing makes this true: `check` is at zero issues,
|
|
7069
|
+
and a declared key that no statement interpolates is not a problem at any
|
|
7070
|
+
severity. A 100% discipline held over 41 revisions with nothing behind it is
|
|
7071
|
+
one careless commit away from being 98%.
|
|
7072
|
+
*What blocks the decision now — and **(2)** has become the opposite of what it
|
|
7073
|
+
said.* **(2) A shape is obvious, and it is small.** `check` already reports
|
|
7074
|
+
`unbound-param` for a placeholder with no key; the reverse — a declared key no
|
|
7075
|
+
statement interpolates — is reported at no severity, and it is the exact static
|
|
7076
|
+
test for "this param's edit leaves no trace outside the line that changed". It
|
|
7077
|
+
needs no previous value, so it is not the rejected snapshot wearing a hat: it
|
|
7078
|
+
is a property of one revision. The nearest precedent is `rationale-placeholder`,
|
|
7079
|
+
a WARNING deliberately below `hasError` so that an existing green registry
|
|
7080
|
+
gains a line of output and keeps its exit code, and that is the level this would
|
|
7081
|
+
take. **(3) What it would fire on here is the objection.** Five of this
|
|
7082
|
+
repository's seven uninterpolated params are **structurally uninterpolatable** —
|
|
7083
|
+
`ATX-7.payloadKinds` and `ATX-63.payloadKinds` are kind → payload tables,
|
|
7084
|
+
`ATX-15.sortMethods`, `ATX-15.bannedApis` and `ATX-72.quotes` are lists, and
|
|
7085
|
+
`non-scalar-interpolation` is the diagnostic that refuses a statement carrying
|
|
7086
|
+
any of them. So the rule must exempt non-scalars, and what is left is
|
|
7087
|
+
`ATX-15.comparator` and `ATX-26.code`: two params, in one repository, that
|
|
7088
|
+
their author simply did not interpolate. A warning firing twice on its own
|
|
7089
|
+
corpus and zero times on the only other one measured is a rule looking for a
|
|
7090
|
+
population. And the exemption is the hole rather than a detail — the five it
|
|
7091
|
+
must skip are precisely the shape §2 widened the params rule to cover
|
|
7092
|
+
**because lists and tables are the most drift-prone constants there are**.
|
|
7093
|
+
*What would decide it:* an adopter whose interpolation rate is below 100% and
|
|
7094
|
+
whose uninterpolated params are scalars. Both corpora sit at the extremes —
|
|
7095
|
+
58 of 58 there, and here the gap is almost entirely the shape the rule cannot
|
|
7096
|
+
see — so nobody has yet measured the population it would serve. Until then the
|
|
7097
|
+
answer for the five that matter may still be nothing more than a line in
|
|
7098
|
+
`intent-quality.md`, the four things no gate is looking at already being that
|
|
7099
|
+
page's subject and this being a fifth of the same kind.
|
|
7100
|
+
|
|
7101
|
+
- **The intent layer is separable from the repository it describes, and the
|
|
7102
|
+
verification layer is not — so a third party can audit intent and never
|
|
7103
|
+
obtain a verdict.** Filed 2026-08-22 from a design evaluation of using Attest
|
|
7104
|
+
as an external audit tool that touches none of the auditee's implementation.
|
|
7105
|
+
The separability is already bought and is stronger than it looks: §2 requires
|
|
7106
|
+
a registry to be a *literal*, no imported constant and no computed value, so
|
|
7107
|
+
`*.reqs.ts` names nothing in the project it is about and is a file another
|
|
7108
|
+
party could own outright. The four static commands execute none of the
|
|
7109
|
+
auditee's code, which is the posture an auditor needs and is already the
|
|
7110
|
+
documented reason for the split. The arrangement that follows is a two-party
|
|
7111
|
+
contract with `check` as its interface: the auditing party writes the SHALL,
|
|
7112
|
+
the developing party owes each id a scenario, and `uncovered-requirement` and
|
|
7113
|
+
`orphan-test` are the two directions that contract can be broken in.
|
|
7114
|
+
|
|
7115
|
+
*What blocks it, in the order that decides it.* **The verdict half cannot
|
|
7116
|
+
cross the boundary, and that is §1 rather than a gap.** `verify` and `archive`
|
|
7117
|
+
run the suite, the suite's specs import the implementation, and the environment
|
|
7118
|
+
those specs need is the auditee's — reachable only by trusting their
|
|
7119
|
+
`--vitest-config`. So what a decoupled auditor can obtain is the readable
|
|
7120
|
+
obligations and never `tests-red`, which is the same asymmetry `status` already
|
|
7121
|
+
carries and states about itself. That would be tolerable if the static report
|
|
7122
|
+
read as procedural, and it does not: an audit reader takes a full-coverage
|
|
7123
|
+
`check` as a claim about behaviour, while §11 records that coverage is binary,
|
|
7124
|
+
that an assertion can echo the param it is measuring, that shortening a
|
|
7125
|
+
list-valued param silently removes cases, that a quantifier is covered only as
|
|
7126
|
+
far as hand-written scenarios reach, and that two contradictory ids under
|
|
7127
|
+
different prefixes both pass. **The mechanism assumes one tree.** `locate.ts`
|
|
7128
|
+
scans a single project root, and mechanism 1 needs the spec to `import` the
|
|
7129
|
+
registry, so the externally-owned file has to be physically in the tree before
|
|
7130
|
+
any command runs — a checkout step, not a flag. Naming a registry outside the
|
|
7131
|
+
root would be a second answer to where intent lives, which is the shape this
|
|
7132
|
+
repository refuses elsewhere. **The flat ID space bills exactly here.** §11
|
|
7133
|
+
already records that nothing allocates a prefix and that the cost arrives when
|
|
7134
|
+
adoption succeeds; two parties sharing one registry *is* that case, and
|
|
7135
|
+
`duplicate-prefix` can only report a collision after both files were read.
|
|
7136
|
+
**And `--apply` writes into the other party's file.** The merge splices ADDED
|
|
7137
|
+
requirements into the registry, so the one command that edits a file the user
|
|
7138
|
+
cannot regenerate would be editing a document whose owner is not in the
|
|
7139
|
+
repository. Nothing about that is unsafe; it is that the ownership the
|
|
7140
|
+
arrangement exists to establish is the thing the merge step quietly crosses.
|
|
7141
|
+
|
|
7142
|
+
*What would settle it, and what it costs to try.* The whole arrangement is
|
|
7143
|
+
derivable from the design and has been run by nobody, so this is n=0 and cheap
|
|
7144
|
+
to move: the checkout-step form needs no engine change at all, and one
|
|
7145
|
+
adopter running it would say whether the two-party report is read as procedural
|
|
7146
|
+
or over-read as a behavioural claim. That is the measurement, and it is a
|
|
7147
|
+
question about the reader rather than about the code.
|
|
7148
|
+
|
|
7149
|
+
- **A freshness gate on the file `attest init` writes (`init --check`).**
|
|
7150
|
+
Rejected 2026-08-19, and **reopened 2026-08-31 by the first measurement of the
|
|
7151
|
+
exposure it was rejected on.** The rejection is kept below in full because it
|
|
7152
|
+
is still the argument to beat, and what beat one of its two load-bearing claims
|
|
7153
|
+
is a 0.9.1 → 0.11.0 upgrade report from `mine-capablanca`: the committed
|
|
7154
|
+
`.claude/skills/attest/SKILL.md` in that repository, written by `init` at
|
|
7155
|
+
0.9.1, differs from the 0.11.0 body by **9 hunks and 110 changed lines, 403
|
|
7156
|
+
lines against 458**. It was found only because the reporter wrote a script
|
|
7157
|
+
importing `workflowBody()` out of the installed build and diffed it. `check`,
|
|
7158
|
+
`cover`, `verify` and `render --check` were all green with the document stale.
|
|
7159
|
+
*The backstop is what failed, and it failed on the worst available case.* The
|
|
7160
|
+
rejection's second claim is that every mistake a stale document can cause is
|
|
7161
|
+
already a diagnostic with a fix hint — "a registry written the old way is
|
|
7162
|
+
`registry-not-static`", so the agent corrects itself from the report. The stale
|
|
7163
|
+
text here is `never-red`'s ordering rule, which `[0.10.0]` archived as
|
|
7164
|
+
`evidence-not-ordering` on the grounds that the ordering was never in the
|
|
7165
|
+
engine. An agent reading that document is told to do something the engine does
|
|
7166
|
+
not require. Its behaviour is more conservative, not wrong-shaped, so **no
|
|
7167
|
+
issue fires at any severity** — there is nothing to correct itself from,
|
|
7168
|
+
because nothing it does is an error. The backstop covers a stale document
|
|
7169
|
+
producing bad input. It does not cover a stale document producing unnecessary
|
|
7170
|
+
work, and the second is what six weeks of drift actually produced.
|
|
7171
|
+
*The first claim survives, and it is still the objection.* A gate on this file
|
|
7172
|
+
does turn red on a change the user did not make and is cleared by a commit
|
|
7173
|
+
containing no decision. Nothing in the report disputes that. What the report
|
|
7174
|
+
supplies is the other side of the ledger, which the rejection priced as "one
|
|
7175
|
+
honest and small exposure": 110 lines, over one upgrade, undetectable without
|
|
7176
|
+
writing a script against `dist/`.
|
|
7177
|
+
*And the property that makes `init` idempotent is the same one that makes the
|
|
7178
|
+
drift invisible.* `skillContent()` and `workflowBody()` take no argument — not
|
|
7179
|
+
the version, not the project — deliberately, so that re-running `init` after an
|
|
7180
|
+
upgrade is always safe. The consequence is that the file carries **no version
|
|
7181
|
+
stamp**: nothing in the adopter's repository records which version wrote it,
|
|
7182
|
+
and *"Re-run `attest init` after upgrading"* — which the CLI prints once, at
|
|
7183
|
+
`init` time, to a reader who is not upgrading — is only actionable by somebody
|
|
7184
|
+
who already knows they need to. A diff against the installed build is not one
|
|
7185
|
+
detector among several. It is the only one the file's own design permits.
|
|
7186
|
+
*What is undecided is the shape, and the rejection narrows it rather than being
|
|
7187
|
+
answered by it.* A red `init --check` in CI is the thing that was rejected, and
|
|
7188
|
+
the report does not rehabilitate it: it would fail a fresh clone on somebody
|
|
7189
|
+
else's release, which is the same standard `render --check` was held to when a
|
|
7190
|
+
freshness gate that calls a fresh clone stale was called worse than no gate.
|
|
7191
|
+
What was never proposed, and is not what the heading names, is a **notice on a
|
|
7192
|
+
command the adopter is already running** — the same information at the cost of
|
|
7193
|
+
no gate and no chore commit. Pricing that is the next step; it is not a
|
|
7194
|
+
decision already taken, and it needs its own answer to which command, at what
|
|
7195
|
+
level, and whether a WARNING nobody can clear without running `init` is a
|
|
7196
|
+
chore in a diagnostic's clothes after all.
|
|
7197
|
+
*What is still unmeasured:* whether the drift cost that reporter anything. 110
|
|
7198
|
+
changed lines measures the document, not the work, and the report is explicit
|
|
7199
|
+
that it did not run `init` and did not act on the withdrawn rule. Nobody has
|
|
7200
|
+
yet followed a stale instruction and paid for it, and the rejection below is
|
|
7201
|
+
entitled to that until somebody has.
|
|
7202
|
+
|
|
7203
|
+
**Priced on 2026-09-02, per `How an entry is re-measured`, and the three
|
|
7204
|
+
questions have answers: `check`, `INFO`, and no — the chore is the exit code
|
|
7205
|
+
rather than the line.**
|
|
7206
|
+
*The re-measure corrected the entry's framing of the detector first.* "A diff
|
|
7207
|
+
against the installed build is the only detector the file's own design
|
|
7208
|
+
permits" reproduces exactly, and what it makes reachable is the opposite of
|
|
7209
|
+
how it reads: the comparison needs no version stamp, because `skillContent()`
|
|
7210
|
+
is already inside the process that would report. Measured against this
|
|
7211
|
+
repository's own copy — byte-equal and document-equal at 26,584 bytes both
|
|
7212
|
+
sides — and against the other two targets, which are **absent**, that being
|
|
7213
|
+
the population the notice has to be right about. Cost is **0.234 ms** per run
|
|
7214
|
+
for all three comparisons plus **4.19 ms** to import `targets.ts` and through
|
|
7215
|
+
it the 26 KB template in `skill.ts`, which `check` does not import today;
|
|
7216
|
+
`check self --json` is 814–1,183 ms over three runs, so the notice is **~0.5%**
|
|
7217
|
+
of the command it rides on.
|
|
7218
|
+
*Which command, and the objection to it is answered by precedent rather than
|
|
7219
|
+
by argument.* `check` is the only command an adopter runs unconditionally and
|
|
7220
|
+
is the one the workflow document tells the agent to run. `status` takes a
|
|
7221
|
+
`<change>`, so it is silent during exactly the brownfield adoption whose
|
|
7222
|
+
document is stalest; `cover` and `render` are narrower; `verify` and `archive`
|
|
7223
|
+
are the expensive verdicts, where a non-verdict line is noise at the worst
|
|
7224
|
+
available price. The objection is that this would be `check`'s first finding
|
|
7225
|
+
that is not about the project's intent layer — and it would not be:
|
|
7226
|
+
`compilerIssue()` is the first statement of `runCheck`, and
|
|
7227
|
+
`compiler-unsupported` is a finding about the *installed environment*, at
|
|
7228
|
+
ERROR. What ATX-16 scopes `check` to is executing nothing, never intent only.
|
|
7229
|
+
*What level, and `WARNING` is wrong for a reason the roster states.* All three
|
|
7230
|
+
existing WARNINGs — `rationale-placeholder`, `requirement-open`,
|
|
7231
|
+
`orphan-from-failed-registry` — are defects in the reader's own intent layer
|
|
7232
|
+
that the static half declines to fail on. A stale document is neither a defect
|
|
7233
|
+
nor theirs. `INFO` has exactly one member, `possible-drift`, and it is this
|
|
7234
|
+
shape precisely: something here may be drifting and nothing can prove it cost
|
|
7235
|
+
you anything — which is also what this entry's own unmeasured half says.
|
|
7236
|
+
`ERROR` is `init --check`, and that stays rejected.
|
|
7237
|
+
*The chore question dissolves at that level, on a fact about when the drift
|
|
7238
|
+
appears.* The rejection's standard is a gate that "turns red on a change the
|
|
7239
|
+
user did not make". Drift appears only when the installed version moves, which
|
|
7240
|
+
is a lockfile edit and is a change they made; a fresh clone at a pinned version
|
|
7241
|
+
compares equal and is silent, so the failure mode that forced `render --check`
|
|
7242
|
+
off byte comparison — calling a fresh clone stale — cannot occur here. What
|
|
7243
|
+
makes a thing a chore is the exit code, not the line, and at INFO there is
|
|
7244
|
+
nothing to clear: a reader who ignores it forever pays what they pay today.
|
|
7245
|
+
*The repair makes one thing reachable that the entry does not contain, which
|
|
7246
|
+
is the question the method exists to ask.* `runInit` writes with `writeAtomic`
|
|
7247
|
+
unconditionally — no merge, no backup — and the notice cannot tell a stale file
|
|
7248
|
+
from an edited one. Today that overwrite happens only when the user chooses to
|
|
7249
|
+
run `init`; a notice prompts them into it on the tool's schedule, so the fix
|
|
7250
|
+
hint has to say that `init` rewrites the file rather than merely naming the
|
|
7251
|
+
command. `targets.ts` rule 1 makes that defensible — Attest owns the path, and
|
|
7252
|
+
that is the whole reason it may overwrite — but defensible and unstated are
|
|
7253
|
+
different things.
|
|
7254
|
+
*And the obvious implementation is wrong twice, both times by copying
|
|
7255
|
+
`staleIssue`, which is the reference implementation this entry points at and
|
|
7256
|
+
therefore the one nobody re-reads.* **(a)** It emits `missing-spec-doc` for an
|
|
7257
|
+
absent file. Here an absent file is a project that never ran `init` — two of
|
|
7258
|
+
this repository's own three targets — and firing on it would be the tool
|
|
7259
|
+
scattering findings about editors nobody uses, which is the argument
|
|
7260
|
+
`DEFAULT_TARGET` already makes. Absent must be silent, so this takes one code
|
|
7261
|
+
and not two. **(b)** `sameDocument` normalises CRLF and has to here, for the
|
|
7262
|
+
reason `render --check` needs it: Attest does not own an adopter's
|
|
7263
|
+
`.gitattributes`. That is the **opposite** of ATX-84, which compares this
|
|
7264
|
+
repository's copy byte-for-byte, and whose rationale already argues why the two
|
|
7265
|
+
must differ. The comparisons are deliberately not the same one.
|
|
7266
|
+
*What the re-measure found that belongs to no proposal: ATX-84's rationale
|
|
7267
|
+
carries the standing version of this argument, and one clause of it is now
|
|
7268
|
+
false.* It records that a gate here does not reverse `skill.ts`'s decision
|
|
7269
|
+
against shipping one, on the grounds that "every mistake an old document can
|
|
7270
|
+
cause is a diagnostic carrying a fix hint the agent corrects itself from".
|
|
7271
|
+
That is the clause the `mine-capablanca` report falsified — the withdrawn
|
|
7272
|
+
`never-red` ordering produces unnecessary work rather than bad input, and
|
|
7273
|
+
nothing fires at any severity. **Withdrawn under `[1.1.0]` on 2026-09-02,
|
|
7274
|
+
in all three places that carried it**, since the correction was owed whether or not
|
|
7275
|
+
the notice ships; what the withdrawal leaves standing is the diff-nobody-asked-for
|
|
7276
|
+
objection alone, which is the argument this entry now has to beat.
|
|
7277
|
+
*One cost the entry did not price: this repository cannot dogfood it.* `check`
|
|
7278
|
+
reads the project root, which here is `self/`, and the document is at the repo
|
|
7279
|
+
root — so `check self` would find the file absent and be correctly silent.
|
|
7280
|
+
ATX-84's scenario reaches two directories up because it is a test; a command
|
|
7281
|
+
cannot. The scenario would land on a fixture, and `self/` would give the notice
|
|
7282
|
+
none of the continuous exercise ATX-84's rationale calls the only detector this
|
|
7283
|
+
document has.
|
|
7284
|
+
*What it does not need, priced because the entries around it spend most of
|
|
7285
|
+
their length on exactly this:* no `SCHEMA_VERSION` bump — the field exists, the
|
|
7286
|
+
value is new, and `ok` is untouched because INFO is below `hasError`, the same
|
|
7287
|
+
reading that has held the version at 1 across all 41 codes. One roster entry
|
|
7288
|
+
and one troubleshooting section per language, both made mandatory by
|
|
7289
|
+
`docs-consistency.spec.ts` rather than by remembering.
|
|
7290
|
+
*The one form still worth arguing is the cheapest one, and it loses on the
|
|
7291
|
+
exposure's own shape.* A dim line after the report — `init`'s own "Re-run
|
|
7292
|
+
`attest init` after upgrading", moved to where it is actionable — needs no
|
|
7293
|
+
code, no roster entry and no section in two languages. It is also invisible
|
|
7294
|
+
under `--json`, and the drift in that report was found by a reader writing a
|
|
7295
|
+
script against `dist/`. A detector a machine cannot read reproduces the
|
|
7296
|
+
invisibility it exists to remove.
|
|
7297
|
+
|
|
7298
|
+
**Built under `[1.1.0]` on 2026-09-02, and the entry stays here because
|
|
7299
|
+
what was built is not what it names.** The priced form shipped exactly as
|
|
7300
|
+
priced — `check`, INFO, one code, silent on an absent file, document rather
|
|
7301
|
+
than bytes — as `ATX-89`, `ATX-90` and `ATX-91`. The re-measure corrected none
|
|
7302
|
+
of the three answers, which is worth recording against the two cases in `How
|
|
7303
|
+
an entry is re-measured` where it corrected an entry outright.
|
|
7304
|
+
*What the build added that the pricing did not have is the argument the
|
|
7305
|
+
approver gave, and it is better than the one written here.* This entry argues
|
|
7306
|
+
the level from the negative — a gate is a chore, so do not gate. The reason
|
|
7307
|
+
actually adopted is positive and is an **asymmetry of obligation**: keeping
|
|
7308
|
+
this document current is Attest's obligation, because Attest is what changes
|
|
7309
|
+
it, and taking the update is nobody else's. A finding whose subject the reader
|
|
7310
|
+
owes nothing about is exactly the finding that may only inform. That reaches
|
|
7311
|
+
the same level by a route that does not depend on calling anything a chore,
|
|
7312
|
+
and it is what `ATX-89`'s rationale carries.
|
|
7313
|
+
*What is left of this entry is the gate alone, and it is unchanged.* A notice
|
|
7314
|
+
is not a weaker gate, it is a different artifact: it can be ignored, and by a
|
|
7315
|
+
CI-only adopter who never reads `check` output it certainly will be. That is
|
|
7316
|
+
the cost of the choice rather than an oversight — the trade taken was "never
|
|
7317
|
+
blocks anyone" against "guaranteed to be seen". The thing that would move the
|
|
7318
|
+
gate is what it always was, and the notice does not supply it: an adopter who
|
|
7319
|
+
followed a stale instruction and paid for it. Nobody has.
|
|
7320
|
+
|
|
7321
|
+
The rejection, kept as it was written:
|
|
7322
|
+
|
|
7323
|
+
`render --check` is the obvious precedent and is the opposite case. A rendering
|
|
7324
|
+
is a function of the *user's registry*, so its staleness means the user moved
|
|
7325
|
+
intent without regenerating — a real signal, with the fix in their hands. The
|
|
7326
|
+
skill is a function of the *installed version*, so a gate on it turns red on a
|
|
7327
|
+
change the user did not make, and is cleared by a commit containing no decision.
|
|
7328
|
+
That is a chore wearing a diagnostic's clothes, and a gate that cannot be about
|
|
7329
|
+
anything the reader did is worse than no gate — the same standard 0.1.6 set when
|
|
7330
|
+
it gave `render` no formatting options at all. An earlier draft of `attest init`
|
|
7331
|
+
spent a second command, this gate and a second self-requirement on it. What that
|
|
7332
|
+
would have bought is immunity to one honest and small exposure: an old file
|
|
7333
|
+
describes an old workflow to an agent that trusts it. The backstop is that every
|
|
7334
|
+
mistake it can cause is already a diagnostic with a fix hint — a registry
|
|
7335
|
+
written the old way is `registry-not-static` — so the agent corrects itself from
|
|
7336
|
+
the report, and `init` is idempotent, which makes re-running it the entire
|
|
7337
|
+
update mechanism. Recorded here rather than left in the 0.3.0 entry that shipped
|
|
7338
|
+
`init`, because the queue above depends on it: a gate is the first thing
|
|
7339
|
+
proposed each time a new agent target is considered, and the answer must not
|
|
7340
|
+
need re-deciding per target.
|
|
7341
|
+
|
|
7342
|
+
*One locator moved with the entry.* "The queue above" was written from inside
|
|
7343
|
+
`Considered and rejected` and means the queue of agent targets, not the entries
|
|
7344
|
+
now above this one; the point it makes — that the answer must not need
|
|
7345
|
+
re-deciding per target — is unaffected by where the entry sits.
|
|
7346
|
+
|
|
7347
|
+
- **A `fix` field on `Issue`, derived from `code` the way `docsUrl` is.** From
|
|
7348
|
+
`nostics` 1.2.0 (vercel-labs), read 2026-09-01, whose `Diagnostic` carries
|
|
7349
|
+
`why` / `fix` / `docs` as three fields rather than one prose message. Most of
|
|
7350
|
+
what that library is for is already here and stricter — its docs link is
|
|
7351
|
+
authored per code with a `docs: false` opt-out, ours is derived from the code
|
|
7352
|
+
at render time and cannot be forgotten or disagreed with — so the `why`/`fix`
|
|
7353
|
+
split is the one half of it this file has no answer for.
|
|
7354
|
+
*What the roster says, and it is not what the proposal assumed.* The claim
|
|
7355
|
+
filed was that a fix is either buried in prose or absent. Reading all 41
|
|
7356
|
+
rendered messages in `docs/en/troubleshooting.md`: **11 state an action to
|
|
7357
|
+
take** (`uncovered-requirement`, `never-red`, `orphan-test`, `open-unresolved`
|
|
7358
|
+
and seven more), **5 state the constraint but not the action**
|
|
7359
|
+
(`registry-no-default`, `non-scalar-interpolation`, `unknown-target`,
|
|
7360
|
+
`compiler-unsupported`, `invalid-change-name`), and **25 state only what is
|
|
7361
|
+
wrong** — `registry-not-static` is the whole of "Value is not a literal.", and
|
|
7362
|
+
`tests-red`, `unbound-param`, `duplicate-requirement` and `possible-drift` are
|
|
7363
|
+
the same shape. So the distribution is uneven and unenforced rather than
|
|
7364
|
+
absent, which is a weaker premise than the one filed but a more precise one:
|
|
7365
|
+
what is missing is not the text, it is anything that makes the text obligatory.
|
|
7366
|
+
(Classified by reading. A first pass by pattern undercounted — it missed the
|
|
7367
|
+
lowercase mid-sentence imperatives in `never-red` and `empty-spec` — and its
|
|
7368
|
+
number is deliberately not quoted here.)
|
|
7369
|
+
*The derivation argument holds, and it is the reason to file this at all.* 31
|
|
7370
|
+
of the 41 codes appear as a `code: '…'` literal, across 43 sites; the other 10
|
|
7371
|
+
are built by a ternary or handed through a variable, which is the shape
|
|
7372
|
+
`types.ts` narrows `code` to `IssueCode` for rather than policing with a regex.
|
|
7373
|
+
A `fix` authored at issue sites inherits that split and can be forgotten at any
|
|
7374
|
+
of the 43. A `fix` keyed on `code` and resolved when a report is rendered
|
|
7375
|
+
cannot be — it covers all 41 including the 10, which is exactly the argument
|
|
7376
|
+
`docs.ts` already makes for the link, applied to the other half of the line.
|
|
7377
|
+
*What blocks it is that the text would then exist four times.* Every one of the
|
|
7378
|
+
41 codes already has a `**Solution**` block in `docs/en/troubleshooting.md` and
|
|
7379
|
+
its counterpart in `docs/zh` — 41 sections each, checked by
|
|
7380
|
+
`docs-consistency.spec.ts` for existence and by nothing for content. A `fix`
|
|
7381
|
+
string makes a fourth statement of the same thing, and no gate can check the
|
|
7382
|
+
four agree. That is the failure mode `docsUrl` was designed to avoid, arriving
|
|
7383
|
+
through the field added next to it.
|
|
7384
|
+
*And a surface the proposal had not counted: 80 Symptom blocks.* Each of the 41
|
|
7385
|
+
sections opens with a fenced block quoting real output, in both languages — 82
|
|
7386
|
+
blocks, of which **80 quote the terminal rendering** and would be dated the
|
|
7387
|
+
moment `formatIssues` gains a `fix:` line. (The other two are `internal-error`,
|
|
7388
|
+
whose Symptom is a `--json` document, so it is the one section a rendering
|
|
7389
|
+
change leaves alone. It is worth naming rather than rounding away: it is the
|
|
7390
|
+
same carve-out `docs-consistency.spec.ts` already makes for that code.) **No
|
|
7391
|
+
test reads any of them** — that suite asserts the code-to-section
|
|
7392
|
+
correspondence and the pinned docs URLs, and has no assertion over a Symptom
|
|
7393
|
+
block at all — so nothing would demand the 80 edits and nothing would catch
|
|
7394
|
+
them being skipped. The documentation cost is larger than the feature.
|
|
7395
|
+
*What would move it.* Either a gate binding a code's `fix` to its `Solution`
|
|
7396
|
+
section, so the fourth copy is checked rather than merely written — which would
|
|
7397
|
+
also retire the 82-block exposure by making one of the two a function of the
|
|
7398
|
+
other — or an adoption report that one of the 25 action-less messages actually
|
|
7399
|
+
cost somebody a round trip. Without the first, this trades a derivable field for
|
|
7400
|
+
an underivable duplicate.
|
|
7401
|
+
|
|
7402
|
+
- **A Stop hook that will not let an agent end a turn while `check self` is red.**
|
|
7403
|
+
From the same read: `nostics` ships `hooks/consume-diagnostic.mjs`, a Claude
|
|
7404
|
+
Code Stop hook that reads the log its dev-server collector writes and returns
|
|
7405
|
+
`{"decision": "block"}` with the diagnostic as the reason, so the agent keeps
|
|
7406
|
+
working rather than stopping on a red application. Three details there are the
|
|
7407
|
+
design and not the plumbing, and are the transferable part: it guards on
|
|
7408
|
+
`stop_hook_active` so a blocked stop cannot loop; it feeds back **one**
|
|
7409
|
+
diagnostic per turn rather than the whole log; and when it consumes one it drops
|
|
7410
|
+
every other entry sharing that code and file, so a fault at twenty call sites is
|
|
7411
|
+
one round trip and not twenty.
|
|
7412
|
+
*Why the shape fits this repository specifically.* `CLAUDE.md`'s "Before
|
|
7413
|
+
committing" and the whole of `pre-commit-check` are discipline: nothing runs
|
|
7414
|
+
`check self` unless a person or an agent remembers to, and the repeat-grouping
|
|
7415
|
+
that hook does by hand is something `formatIssues` already computes. `.claude/`
|
|
7416
|
+
here holds five skills, no hooks and no `settings.json`, so this would be the
|
|
7417
|
+
first of its kind rather than an extension of anything.
|
|
7418
|
+
*Measured cost, 2026-09-01.* `node bin/attest.js check self --json` runs in
|
|
7419
|
+
**1.04–1.57 s** over three consecutive runs; `status self --json` in 0.78 s.
|
|
7420
|
+
That is paid at the end of every turn, which is the number to weigh rather than
|
|
7421
|
+
the wall clock of a single invocation.
|
|
7422
|
+
*What blocks it is the stale-`dist/` trap, and a hook is the worst place to meet
|
|
7423
|
+
it.* `bin/attest.js` loads `dist/`, not `src/` — the Gotcha this repository
|
|
7424
|
+
already keeps — so a Stop hook reports on the last build. After a `src/` edit it
|
|
7425
|
+
blocks on a diagnostic the working tree has already fixed, or clears while the
|
|
7426
|
+
tree is broken, and in both directions it is a gate arguing about a state nobody
|
|
7427
|
+
is in. Either the hook builds first, adding seconds to every turn, or it runs the
|
|
7428
|
+
engine from source and stops being the thing an adopter could copy.
|
|
7429
|
+
*And the larger question it sits in front of.* If this is right here it is right
|
|
7430
|
+
for an adopter, which makes it a question about what `attest init` writes rather
|
|
7431
|
+
than about `.claude/` in this repository. A hook that can block an agent's stop
|
|
7432
|
+
is a much larger commitment than a document, and the rejection quoted in full
|
|
7433
|
+
inside the `init --check` entry in this section — the gate on the installed
|
|
7434
|
+
skill's freshness — turned on exactly the distinction it needs:
|
|
7435
|
+
a signal about work the reader actually did, against a chore the tool imposes. A
|
|
7436
|
+
block on `check` is the first kind, which is the argument for it, and that
|
|
7437
|
+
argument should be made explicitly rather than arrived at by shipping.
|
|
7438
|
+
*What would move it.* Running it here, unshipped and repo-local, for a few weeks,
|
|
7439
|
+
and counting how often it blocks on something that would otherwise have been
|
|
7440
|
+
committed. If that count is zero the discipline was already working and the hook
|
|
7441
|
+
is overhead; if it is not, the number is the case for `init` writing one.
|
|
7442
|
+
|
|
6730
7443
|
- **An outward-facing form of the judgement tables, so an adopter can run this
|
|
6731
7444
|
counter-pressure on their own registry.**
|
|
6732
7445
|
`tests/intent-rule-candidates.spec.ts` maintains four hand-judged tables over
|
|
@@ -6767,26 +7480,29 @@ have to gather again.
|
|
|
6767
7480
|
is real: the grep is what caught a deleted pin that `toContain` had passed.
|
|
6768
7481
|
*Why no detector ships with it, and this is the part that is already
|
|
6769
7482
|
measured.* Three rule families were built and scored against the whole corpus
|
|
6770
|
-
(
|
|
7483
|
+
(104 entries as of 1.1.0, counting a fixture id once per registry file) before any was
|
|
6771
7484
|
written into the engine, which is the order `divergent-param` was originally
|
|
6772
7485
|
designed in the wrong way round. Every figure below is a quotation of the
|
|
6773
7486
|
suite's inline snapshots; the snapshot is the fresh copy, and this paragraph
|
|
6774
7487
|
is only ever as fresh as its last edit.
|
|
6775
|
-
**(1) `compound-requirement`** —
|
|
6776
|
-
obligation, it flags
|
|
6777
|
-
and nearly blind, and the
|
|
7488
|
+
**(1) `compound-requirement`** — 42 requirements carry more than one
|
|
7489
|
+
obligation, it flags 18, is **wrong about none**, and misses 24. Never wrong
|
|
7490
|
+
and nearly blind, and the 24 are the form the coverage incentive actually
|
|
6778
7491
|
rewards: extra clauses under a single keyword. **(2) The widened variant** —
|
|
6779
|
-
|
|
7492
|
+
36 correct, but 14 wrong and still 6 missed, and it is silenced by deleting an
|
|
6780
7493
|
"and". A rule whose cheapest fix degrades the artifact it protects does not
|
|
6781
7494
|
ship, and its precision falls as the registry grows. **(3) `divergent-param`**
|
|
6782
|
-
—
|
|
6783
|
-
recorded. The `code` group now spans
|
|
7495
|
+
— 9 divergences on this repo, **all 9 judged wrong**, zero true positives ever
|
|
7496
|
+
recorded. The `code` group now spans 22 ids, and the failure is structural
|
|
6784
7497
|
rather than a matter of naming: the key is built by discarding the evidence of
|
|
6785
7498
|
relatedness, so the one genuinely paired divergence in the group is the one it
|
|
6786
7499
|
cannot show, and the key has churned three times under `flag` on additions
|
|
6787
|
-
that changed no verdict
|
|
6788
|
-
|
|
6789
|
-
|
|
7500
|
+
that changed no verdict, and once more under `code` at 1.1.0. `level` arriving
|
|
7501
|
+
there as a fifth param name is the second key whose two requirements genuinely
|
|
7502
|
+
*are* related — `ATX-60` and `ATX-89` are both about the severity ladder — and
|
|
7503
|
+
the rule still relates them by the one thing that is not the relation. A fourth candidate — the echo detector, scored at
|
|
7504
|
+
half precision — belongs to the tautology-detector entry now under
|
|
7505
|
+
*Considered and rejected* and is not re-quoted here, because a figure stated in two places goes stale in one. Taken together
|
|
6790
7506
|
these are the measured range of what a token or single-body detector over
|
|
6791
7507
|
prose can do here, and they are why the proposal is enforcement-only.
|
|
6792
7508
|
*What blocks the decision — three things.* **(1) One adapter.** This
|
|
@@ -6817,7 +7533,10 @@ have to gather again.
|
|
|
6817
7533
|
*What would decide it:* one judgement table maintained by someone who did not
|
|
6818
7534
|
design it. If it comes back the same shape, the seam is real and the schema
|
|
6819
7535
|
follows; if it comes back different, `inspect` was the whole answer and the
|
|
6820
|
-
schema never needs to exist.
|
|
7536
|
+
schema never needs to exist. *(One came back on 2026-08-31, and the disjunction
|
|
7537
|
+
above is too clean to survive it: partly the same shape, partly not, and the
|
|
7538
|
+
same half is the contaminated half. The close of this entry is where that is
|
|
7539
|
+
read.)*
|
|
6821
7540
|
**Nothing currently makes that table arrive, which is a separate problem from
|
|
6822
7541
|
the three blockers and the one that gates all of them.** Raised in a product
|
|
6823
7542
|
review of `[0.9.1]`–`[0.9.3]` on 2026-08-19. The deciding evidence is an
|
|
@@ -6846,7 +7565,7 @@ have to gather again.
|
|
|
6846
7565
|
§6 already records as the category no gate reaches: the page's three samples
|
|
6847
7566
|
are unasserted today, and a fourth in a directory that looks executable would
|
|
6848
7567
|
be worse than a fenced block, not better, unless something runs it.
|
|
6849
|
-
**Built under `[
|
|
7568
|
+
**Built under `[0.11.0]`, and the second of those two blockers was the
|
|
6850
7569
|
buildable one.** `examples/judgement-table/` is in the default suite, the
|
|
6851
7570
|
specifier is aliased rather than relativised so the file stays copyable
|
|
6852
7571
|
verbatim, and both assertions were shown to fail before it was called done.
|
|
@@ -6871,246 +7590,80 @@ have to gather again.
|
|
|
6871
7590
|
changes is only that the five voluntary steps are now four, and the one
|
|
6872
7591
|
removed was the one with a fixed answer. If the next report is still nobody,
|
|
6873
7592
|
the scaffold was not what was missing, and that is worth knowing too.
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
itself moves, and neither answer settles the other. `archive` gates a
|
|
6949
|
-
**change** — a delta, proposed specs, a recorded failing run — and a param edit
|
|
6950
|
-
is none of those. It is one literal in a merged `*.reqs.ts`, and every gate
|
|
6951
|
-
reads it as fine: `check` finds it schema-valid, because a param is any JSON
|
|
6952
|
-
value; `possible-drift` is quiet by construction, since `ATX-8` makes it ask
|
|
6953
|
-
whether *any* covering scenario reads the params at all rather than whether a
|
|
6954
|
-
value moved; and `verify` stays green wherever the assertion derives its
|
|
6955
|
-
expectation from the same param, which is the pair of green rows measured in
|
|
6956
|
-
the entry this was split from.
|
|
6957
|
-
*What is left is `render`, and it was measured here rather than assumed.*
|
|
6958
|
-
Across **89 params on 59 of this repository's 79 requirements, 82 — 92.1% —
|
|
6959
|
-
are interpolated into their own statement**, so editing one changes
|
|
6960
|
-
`self/requirements/SPEC.md` and `render --check` fails until the document is
|
|
6961
|
-
regenerated. That is a review surface and not a gate: the new value lands in a
|
|
6962
|
-
committed generated file where a reader can see it, and nothing anywhere asks
|
|
6963
|
-
whether the move was intended.
|
|
6964
|
-
*The other seven leave no trace outside the line that was edited, and which
|
|
6965
|
-
seven they are is the finding rather than the count.* Five are **structurally
|
|
6966
|
-
uninterpolatable**: `ATX-7.payloadKinds` and `ATX-63.payloadKinds` are
|
|
6967
|
-
kind → payload tables, and `ATX-15.sortMethods`, `ATX-15.bannedApis` and
|
|
6968
|
-
`ATX-72.quotes` are lists. A statement cannot carry any of them —
|
|
6969
|
-
`non-scalar-interpolation` is the diagnostic that refuses it — so the params
|
|
6970
|
-
with no record are disproportionately the shape §2 widened the params rule to
|
|
6971
|
-
cover **precisely because lists and tables are the most drift-prone constants
|
|
6972
|
-
there are**. The remaining two, `ATX-15.comparator` and `ATX-26.code`, are
|
|
6973
|
-
scalars their author simply did not interpolate.
|
|
6974
|
-
*What this is not.* It is not a re-proposal of the committed params snapshot
|
|
6975
|
-
rejected below. That was rejected on the argument that the mechanism guarding a
|
|
6976
|
-
param is a scenario reading it rather than a file recording it, and nothing
|
|
6977
|
-
here disputes that or needs a second copy of the values. The observation is
|
|
6978
|
-
narrower: the **edit** is unremarkable to every gate, which is a different
|
|
6979
|
-
claim from the values being unguarded.
|
|
6980
|
-
*What blocks the decision — and the first is the one that blocks the entry this
|
|
6981
|
-
came from.* **(1)** Frequency is unmeasured, and the one corpus anybody has
|
|
6982
|
-
looked at points at zero: across 8 commits touching `*.reqs.ts` in the
|
|
6983
|
-
reporting repository, no param value was ever modified — every `params:` line
|
|
6984
|
-
in every diff is an addition. A defect nobody has committed is a hazard rather
|
|
6985
|
-
than a report. **(2)** No shape is obvious. A param edit is not a change, so
|
|
6986
|
-
`archive` is the wrong place; making `check` notice one needs a previous value
|
|
6987
|
-
to compare against, which is the rejected snapshot wearing a different hat. The
|
|
6988
|
-
only mechanism needing no stored copy is review, and review already sees the
|
|
6989
|
-
`SPEC.md` diff for 82 of the 89. **(3)** What is left after that is seven
|
|
6990
|
-
params in one repository, and the right answer for the five that matter may be
|
|
6991
|
-
nothing more than a line in `intent-quality.md` — the four things no gate is
|
|
6992
|
-
looking at already being that page's subject, and this being a fifth of the
|
|
6993
|
-
same kind.
|
|
6994
|
-
|
|
6995
|
-
- **Nothing is frozen, and the evidence this repository is waiting for requires
|
|
6996
|
-
somebody to build on something.** Filed 2026-08-19 from a product review of
|
|
6997
|
-
`[0.9.1]`–`[0.9.3]`. Three releases in five days each added public surface, and
|
|
6998
|
-
as of `[0.9.3]` the CLI, `./define`, `./inspect`, the archive layout, the
|
|
6999
|
-
judgement schema and any retrieval surface are all simultaneously open. Every
|
|
7000
|
-
entry in this section that is blocked on an adopter — the judgement table above
|
|
7001
|
-
being the sharpest — is asking someone to invest in a surface that is
|
|
7002
|
-
documented as movable. That is a coherent thing to ask under 0.x and it is also
|
|
7003
|
-
the reason the ask may never be answered.
|
|
7004
|
-
*The proposal is a line rather than a milestone:* 1.0 freezes the CLI, `./define`
|
|
7005
|
-
and `./inspect`, and everything currently blocked on an adopter's evidence —
|
|
7006
|
-
the judgement schema, the backlink index, any archive retrieval — is explicitly
|
|
7007
|
-
1.x. What that buys is not a version number; it is the only condition under
|
|
7008
|
-
which "build your own counter-pressure on our export" is a reasonable thing to
|
|
7009
|
-
ask of somebody.
|
|
7010
|
-
*What blocks it.* Freezing `./inspect` five days after shipping it is freezing
|
|
7011
|
-
on one adapter, which is the objection this repository raises against every
|
|
7012
|
-
other premature interface and does not get to waive for its own. And the
|
|
7013
|
-
release-notes discipline cuts both ways here: a 1.0 is a claim about what the
|
|
7014
|
-
cost of adoption will be, and `How an entry is re-measured` records that **what
|
|
7015
|
-
a release costs its adopters is not known on the day it is cut**. There is no
|
|
7016
|
-
measurement to date of anyone adopting either export.
|
|
7017
|
-
|
|
7018
|
-
**Measured on 2026-08-28, before deciding anything. The one-adapter objection
|
|
7019
|
-
splits rather than holds, and a prerequisite this entry does not name has
|
|
7020
|
-
arrived on its own.**
|
|
7021
|
-
|
|
7022
|
-
*The surface has not been moving, which is not what "three releases in five
|
|
7023
|
-
days each added public surface" suggests.* Each entry point's last change to
|
|
7024
|
-
its exported shape, and how many releases have been cut since without touching
|
|
7025
|
-
it:
|
|
7026
|
-
|
|
7027
|
-
| surface | last shape change | releases since |
|
|
7028
|
-
|---|---|---|
|
|
7029
|
-
| CLI commands and options | 2026-08-03 (`--apply`) | 12 |
|
|
7030
|
-
| `.` — the index exports | 2026-07-26 | 19 |
|
|
7031
|
-
| `./define` — public signatures | 2026-08-08 | 7 |
|
|
7032
|
-
| `./inspect` | 2026-08-18 | 2 |
|
|
7033
|
-
|
|
7034
|
-
`src/index.ts` has had its export list edited in **three commits ever**, the
|
|
7035
|
-
last of them five weeks ago. So "freezing on one adapter" is weak for the CLI
|
|
7036
|
-
and `.` — nineteen and twelve releases of evidence that they have stopped
|
|
7037
|
-
moving — and strong exactly where this entry puts it, on `./inspect`, which
|
|
7038
|
-
has two releases, one adapter, and whose single post-shipping change was
|
|
7039
|
-
additive. That argues for a line this entry does not currently offer:
|
|
7040
|
-
**freeze what has stopped moving and explicitly do not freeze `./inspect`.**
|
|
7041
|
-
|
|
7042
|
-
*And the cost of any such line was demonstrated this week, unprompted.* The
|
|
7043
|
-
Versioning section at the head of this file defines breaking to include
|
|
7044
|
-
**rejecting previously-valid input**. There have been four breaking commits in
|
|
7045
|
-
this project's history, the last on 2026-08-03 — and a fifth is sitting in
|
|
7046
|
-
`[Unreleased]` as of 2026-08-28: the `callsAuthoringFn` binding fix, which is
|
|
7047
|
-
breaking *because* it narrows what a static reader accepts, and which says so
|
|
7048
|
-
about itself. Under 1.0 that is a **major** bump. A fix closing a wrong-answer
|
|
7049
|
-
hole in the four commands documented as safe on an untrusted fork MR would
|
|
7050
|
-
have had to ship as 2.0, twenty-five days after 1.0 — or not ship as written.
|
|
7051
|
-
|
|
7052
|
-
*So the decision has a prerequisite, and the prerequisite left this section.*
|
|
7053
|
-
What a narrowing that closes a security hole costs a consumer is undefined,
|
|
7054
|
-
and it is needed whether or not 1.0 is ever cut — so it is under `Planned`
|
|
7055
|
-
above rather than here, where it would have been blocked on everything this
|
|
7056
|
-
entry is blocked on. Until it exists a 1.0 would be promising something whose
|
|
7057
|
-
price nobody has computed; what it does **not** do is unblock the rest of this
|
|
7058
|
-
entry, which turns on evidence no measurement here can supply.
|
|
7059
|
-
|
|
7060
|
-
*What is still unmeasured is unchanged, and it is the whole of the rest.* No
|
|
7061
|
-
adopter of either export, so the thing 1.0 is meant to buy — making "build
|
|
7062
|
-
your own counter-pressure on our export" a reasonable ask — has no measurement
|
|
7063
|
-
behind it in either direction. The `examples/` scaffold under `[Unreleased]`
|
|
7064
|
-
is the other half of that ask and was the cheaper half. Whether the missing
|
|
7065
|
-
half is the freeze, or was never the freeze, is what the next report answers.
|
|
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.
|
|
7593
|
+
**Decided 2026-08-31: build the counting rule, and not the schema.** The
|
|
7594
|
+
blocker was whether an empty frame reads as an invitation or as a form to fill
|
|
7595
|
+
in, it could not be tested here, and a 0.9.1 → 0.11.0 upgrade report from
|
|
7596
|
+
`mine-capablanca` tested it. The answer is **invitation**, and the report names
|
|
7597
|
+
the sentence that did it: not the empty `interface Judgement`, which reads as a
|
|
7598
|
+
stub and sent the reporter looking for what belonged in it, but the comment
|
|
7599
|
+
above it — *"It ships with no columns, and that is the finished state of this
|
|
7600
|
+
file rather than a gap in it."* Declaring the absence finished is what turns a
|
|
7601
|
+
blank into a question, and it is the cheapest sentence in the example.
|
|
7602
|
+
*The forcing move was one the build found rather than one it proposed.* The
|
|
7603
|
+
instruction to add the first column as a **required** field made all 80 rows
|
|
7604
|
+
stop compiling at once, and there was then no path back to green that did not
|
|
7605
|
+
go through having an opinion about all 80 — *"a form gets partially filled in;
|
|
7606
|
+
a type does not"*. That is the type-level forcing move described above,
|
|
7607
|
+
working on somebody who did not write it.
|
|
7608
|
+
*And the frame leaks in one place, which is a defect in the example rather
|
|
7609
|
+
than in the idea.* `pin` is documented as not a judgement column and is the
|
|
7610
|
+
only concrete field in the interface, so the reporter's first act was to
|
|
7611
|
+
populate it for all 80 rows mechanically — *"there is a field, populate it"*.
|
|
7612
|
+
Defensible, since it makes rot-binding bite for 80 rows instead of none, and
|
|
7613
|
+
exactly the form-filling behaviour the frame exists to prevent. Generating
|
|
7614
|
+
`pin` from `inspectProject` rather than leaving a field to be typed is the fix,
|
|
7615
|
+
and it belongs in the example.
|
|
7616
|
+
*What does **not** follow, and the report is why it does not.* Two of the three
|
|
7617
|
+
columns that came back are the same shape as two of this repository's —
|
|
7618
|
+
`clauses` is `OBLIGATIONS`, `quantifier` is a flattened `QUANTIFIED` — and it
|
|
7619
|
+
is tempting to read that as the seam being real. It cannot be read that way:
|
|
7620
|
+
the reporter names the `attest init` workflow document as prior influence, and
|
|
7621
|
+
that document discusses quantifiers and param pinning at length. So the
|
|
7622
|
+
convergence says **something** transmits those two ideas, and the document is
|
|
7623
|
+
at least as likely a channel as the frame. What is uncontaminated is the third
|
|
7624
|
+
column, `venue`, which has no counterpart here: nine of its rows record a
|
|
7625
|
+
promise held by a Playwright suite Attest's scope does not reach. A column this
|
|
7626
|
+
repository could not have written came back, which is a seam behaving like one.
|
|
7627
|
+
*What to build is smaller than a schema, and the three things the reporter
|
|
7628
|
+
wanted and did not get are its specification.* **(1) A counting rule for
|
|
7629
|
+
"obligation" precise enough that two people count the same.** The report
|
|
7630
|
+
distrusts its own `clauses` column above all the others and says why: two
|
|
7631
|
+
tables that count differently are not comparable, which defeats the point of
|
|
7632
|
+
there being a second one. A counting rule is content that does **not** depend
|
|
7633
|
+
on the adopter — the same category as the two mechanical assertions — and it is
|
|
7634
|
+
currently withheld along with the columns, which is the wrong side of the
|
|
7635
|
+
split. **(2) A way to bind a clause to a scenario rather than a row.**
|
|
7636
|
+
Rot-binding binds a row to *a* scenario; nothing binds the sixth obligation of
|
|
7637
|
+
a six-clause requirement to anything, so a six-clauses-three-scenarios row is a
|
|
7638
|
+
number a reader has to notice instead of an assertion that fails. **(3)
|
|
7639
|
+
Somewhere for a row to say the promise is held where Attest cannot see it.**
|
|
7640
|
+
Nine `ui-node` rows and a 902-line Playwright suite, citable only in prose.
|
|
7641
|
+
*So what is planned is (1) and the `pin` fix, and (2) and (3) explicitly are
|
|
7642
|
+
not.* (1) is a paragraph in the example, costs no engine surface, and is the
|
|
7643
|
+
one thing that makes a second table comparable to the first — without it two
|
|
7644
|
+
tables are two vocabularies and the seam cannot be read either way. (2) and (3)
|
|
7645
|
+
are both new schema, and blocker **(1)** of the proposal above — one adapter —
|
|
7646
|
+
is not answered by a report that is the second adapter's first hour. Two hours
|
|
7647
|
+
of one agent is not two adapters. **The `*.judge.ts` schema stays undecided**,
|
|
7648
|
+
and when it moves it will move on a second table that somebody has maintained
|
|
7649
|
+
rather than built, which is a different measurement from the one that arrived.
|
|
7650
|
+
**Both are built under `[1.1.0]`, and moving here on 2026-09-01 is what
|
|
7651
|
+
follows from that.** The counting rule and the anchor fix are the whole of what
|
|
7652
|
+
was decided on 2026-08-31, and they are recorded there. The re-measure before
|
|
7653
|
+
building corrected nothing this entry claims — the report's three asks
|
|
7654
|
+
reproduce as filed — and what it added is the finding the two cases in `How an
|
|
7655
|
+
entry is re-measured` would predict: the obvious implementation of "ship no
|
|
7656
|
+
fields" is an empty interface, and an empty interface admits `0` and `""`, so
|
|
7657
|
+
the frame would have gone on claiming an emptiness its own type did not hold.
|
|
7658
|
+
*The move is the entry's own state changing, not bookkeeping.* `Planned` means
|
|
7659
|
+
decided and unbuilt, and nothing left here is that: the schema, the
|
|
7660
|
+
clause-to-scenario binding and the out-of-scope `venue` column are all waiting
|
|
7661
|
+
on a second table somebody has maintained, which is the definition of this
|
|
7662
|
+
section rather than the one above. What it costs is that `Planned` is empty
|
|
7663
|
+
again — the state that moved `How an entry is re-measured` out of it, so that
|
|
7664
|
+
the method would not go unreachable whenever this happened — and that is the
|
|
7665
|
+
honest reading of where the project stands rather than a gap in the file:
|
|
7666
|
+
nothing is currently decided and unbuilt.
|
|
7114
7667
|
|
|
7115
7668
|
## Considered and rejected
|
|
7116
7669
|
|
|
@@ -7122,6 +7675,611 @@ it sat between 0.2.0 and 0.1.7 for two releases, where standing still meant
|
|
|
7122
7675
|
sinking one version deeper each time a release was cut above it, and a rejection
|
|
7123
7676
|
filed under a version reads as belonging to it.
|
|
7124
7677
|
|
|
7678
|
+
### A backlink index over `archive/`, recording which change introduced or renamed an id
|
|
7679
|
+
|
|
7680
|
+
Proposed from OpenViking's `.relations.json`, restated twice on measurement, and
|
|
7681
|
+
rejected 2026-09-01. It is kept as it was written under *Under consideration*,
|
|
7682
|
+
because what closed it is the argument the entry had already assembled against
|
|
7683
|
+
itself rather than anything that arrived afterwards; the closing paragraphs are
|
|
7684
|
+
at the foot of the entry.
|
|
7685
|
+
|
|
7686
|
+
**A merged requirement carries no trace of the change that produced it, and
|
|
7687
|
+
the rename chain is the part actually lost.** `RequirementSchema` is
|
|
7688
|
+
`statement`, `rationale`, `params`, `outOfScope`; the splice writes those and
|
|
7689
|
+
nothing else, so after `--apply` no field and no sidecar records which change
|
|
7690
|
+
introduced an id, which modified it, or that one id used to be another. The
|
|
7691
|
+
delta that knew is in `archive/`, which nothing reads. Proposed from
|
|
7692
|
+
OpenViking's `.relations.json`: links between memories live in a
|
|
7693
|
+
directory-level sidecar, deliberately kept out of the content, and are
|
|
7694
|
+
rendered on demand at retrieval time (`docs/design/memory-link-design.md`).
|
|
7695
|
+
*The argument for the sidecar over a field is in their competitor analysis
|
|
7696
|
+
rather than their design.* The same document reads GBrain, which writes links
|
|
7697
|
+
into the page body, and records what that costs: rename a page and every
|
|
7698
|
+
inbound link still points at the old slug, with fuzzy matching at the read end
|
|
7699
|
+
as the only mitigation. A `supersedes:` or `introducedBy:` field on a
|
|
7700
|
+
requirement would be that mistake in this repository's terms — the registry is
|
|
7701
|
+
a literal and a single source (§2), and a field naming a folder that a later
|
|
7702
|
+
rename can move is a second source with nothing comparing it against the
|
|
7703
|
+
first. A derived index has no such copy to go stale: it is recomputed from the
|
|
7704
|
+
archived deltas every time it is read.
|
|
7705
|
+
*What blocks the decision, restated on 2026-08-19 because half of what was
|
|
7706
|
+
filed here is no longer true.* This entry was blocked on the empty archive and
|
|
7707
|
+
on the corpus being rename-free, and only the second still holds. `archive/`
|
|
7708
|
+
now has a real change in it — the `open` field, put through the workflow the
|
|
7709
|
+
same day — so the store this would index exists. Its delta carries `added` and
|
|
7710
|
+
nothing else, measured rather than assumed, so a backlink index is still
|
|
7711
|
+
testable only against fixtures for the operation it is actually about. Note
|
|
7712
|
+
what that says about the odds of the blocker clearing on its own: `--apply`
|
|
7713
|
+
refuses RENAMED outright, so no change this repository archives can ever
|
|
7714
|
+
exercise a rename until that refusal is lifted, and lifting it is a separate
|
|
7715
|
+
decision with its own reason (`splice.ts` — deleting an entry has no smaller
|
|
7716
|
+
span to fall back to). The blocker is therefore not "waiting for a rename to
|
|
7717
|
+
happen"; it is downstream of a refusal nobody has proposed removing. And one
|
|
7718
|
+
question the OpenViking reading does not answer, which is whether the chain is
|
|
7719
|
+
wanted at all. §7 applies
|
|
7720
|
+
`RENAMED` to a `Map<id, Requirement>` and the old id is then simply gone —
|
|
7721
|
+
deliberately, as far as anything written says. The case for recovering it is an
|
|
7722
|
+
external reference that outlives the rename, a ticket or a commit message
|
|
7723
|
+
naming a retired id, and nobody has reported one.
|
|
7724
|
+
*Measured against a second corpus on 2026-08-31, and it points the same way.* A
|
|
7725
|
+
0.9.1 → 0.11.0 upgrade report from `mine-capablanca` walked all 41 revisions of
|
|
7726
|
+
that project's single registry and all 49 of its archived changes: **80 ids have
|
|
7727
|
+
ever appeared in it and 80 are in it now — zero removed, zero renamed** — and
|
|
7728
|
+
neither `removed:` nor `renamed:` appears in non-empty form in any delta.
|
|
7729
|
+
Commit messages on every branch, the whole tree and the issue tracker were
|
|
7730
|
+
searched for id-shaped tokens the registry does not define. The external
|
|
7731
|
+
reference this entry waits for did not turn up in sixteen days and forty-nine
|
|
7732
|
+
changes.
|
|
7733
|
+
*One id did outlive itself, from the other direction, and the reason it was left
|
|
7734
|
+
alone is a mechanism this entry did not have.* `AI-10` has never been in that
|
|
7735
|
+
registry, and two committed documents discuss the gap it leaves. The archived
|
|
7736
|
+
proposal's stated reason for not renumbering to close it is that doing so "would
|
|
7737
|
+
mean re-recording a failing run against an implementation that already exists".
|
|
7738
|
+
So `never-red` prices a rename directly: an id cannot be renumbered without
|
|
7739
|
+
re-earning its evidence, and an author facing that bill chose to write about the
|
|
7740
|
+
hole instead. That is a second reason the corpus is rename-free, independent of
|
|
7741
|
+
`--apply`'s refusal, and it argues the rate **stays** at zero rather than that
|
|
7742
|
+
nobody has got round to it.
|
|
7743
|
+
*What this does to the entry:* nothing is refuted, and the blocker is now
|
|
7744
|
+
blocked twice over. It was already downstream of a refusal nobody has proposed
|
|
7745
|
+
removing; it is also downstream of an obligation that makes the operation
|
|
7746
|
+
expensive on purpose. An index over an event that two independent mechanisms
|
|
7747
|
+
discourage is an index over an empty set.
|
|
7748
|
+
*Rejected 2026-09-01, on the entry's own last sentence rather than on anything
|
|
7749
|
+
new.* What closes it is that the two mechanisms are not a coincidence of one
|
|
7750
|
+
corpus and not a stage adoption passes through. `--apply` refuses RENAMED for a
|
|
7751
|
+
reason recorded in `splice.ts` — deleting an entry has no smaller span to fall
|
|
7752
|
+
back to — and `never-red` prices the operation independently, since an id cannot
|
|
7753
|
+
be renumbered without re-earning its evidence. The second is the stronger of the
|
|
7754
|
+
two because it does not depend on a refusal anybody could lift: an author who
|
|
7755
|
+
faced that bill wrote about the hole `AI-10` leaves instead of closing it, which
|
|
7756
|
+
is the mechanism arguing the rate **stays** at zero rather than that nobody has
|
|
7757
|
+
got round to it. An index whose subject two independent mechanisms discourage is
|
|
7758
|
+
not waiting for evidence; it is waiting for a decision nobody has proposed, taken
|
|
7759
|
+
for reasons that have nothing to do with this.
|
|
7760
|
+
|
|
7761
|
+
*What was measured, and it is the whole of the case against.* Across the second
|
|
7762
|
+
corpus: 80 ids have ever appeared and 80 are in it now, over 49 archived changes
|
|
7763
|
+
— zero removed, zero renamed — with neither `removed:` nor `renamed:` appearing
|
|
7764
|
+
in non-empty form in any delta. Commit messages on every branch, the whole tree
|
|
7765
|
+
and the issue tracker were searched for id-shaped tokens the registry does not
|
|
7766
|
+
define. The external reference this entry existed to serve is the one thing that
|
|
7767
|
+
would have justified it, and sixteen days and forty-nine changes produced none.
|
|
7768
|
+
|
|
7769
|
+
*One structural consequence, recorded because it is a change to somebody else's
|
|
7770
|
+
entry.* Two open entries rested on `--apply`'s refusal of RENAMED; closing this
|
|
7771
|
+
one leaves **one**, the third meaning under *Retiring a requirement* below. That
|
|
7772
|
+
matters in the direction of not lifting the refusal: a decision with two
|
|
7773
|
+
dependents is easier to justify than a decision with one, and the count is now
|
|
7774
|
+
one. Both cross-references have been corrected rather than left to be inferred.
|
|
7775
|
+
|
|
7776
|
+
*What would reopen it.* Not a report of the drift this entry describes — that
|
|
7777
|
+
report is what sixteen days and two corpora failed to produce. It reopens if
|
|
7778
|
+
`--apply`'s refusal of RENAMED is lifted for its own reason, because the
|
|
7779
|
+
operation then becomes available and the corpus stops being evidence about a
|
|
7780
|
+
population that cannot exist. That is the one event that changes the argument,
|
|
7781
|
+
and it is not this entry's to trigger.
|
|
7782
|
+
|
|
7783
|
+
### A detector for an assertion that recomputes its expectation from the param under test
|
|
7784
|
+
|
|
7785
|
+
Proposed 2026-08-03 from `mine-capablanca`'s report, carried through three
|
|
7786
|
+
re-measurements, and rejected 2026-08-31. The defect it describes is real and
|
|
7787
|
+
stays undetected: an assertion deriving its expectation from the same param the
|
|
7788
|
+
code under test consumed has no independent term, so the value can move with the
|
|
7789
|
+
suite green, `check` silent and `possible-drift` correctly not firing. *What
|
|
7790
|
+
killed it is the detector's own score, and that is now the whole of the case.*
|
|
7791
|
+
The one anybody built scores **50% on the shape and 0% on the harm**: of 8 flags, 4 are real echoes and
|
|
7792
|
+
none of the 4 is harmful, while the false positives are pinned by a term in
|
|
7793
|
+
another file that a single-body detector cannot see by construction. Precision
|
|
7794
|
+
fell on every addition measured.
|
|
7795
|
+
|
|
7796
|
+
*The frequency half of this rejection was wrong on the day it was written, and it
|
|
7797
|
+
is withdrawn.* It read that frequency is "approximately zero across two
|
|
7798
|
+
codebases", on 8 commits in the reporting repository across which no param value
|
|
7799
|
+
was ever modified. A 0.9.1 → 0.11.0 upgrade report from `mine-capablanca` on
|
|
7800
|
+
2026-08-31 re-ran that count properly — all **41 revisions** of the same
|
|
7801
|
+
registry, the file evaluated at each and compared key by key rather than read out
|
|
7802
|
+
of diff hunks — and it comes back with **seven values changed**. Not
|
|
7803
|
+
approximately zero. The breakdown belongs to the param-value entry under `Under
|
|
7804
|
+
consideration` and is not re-quoted here, because a figure stated in two places
|
|
7805
|
+
goes stale in one. The 8-commit sample was what was reachable on the day and not
|
|
7806
|
+
an answer to the question, and quoting it as one is the failure this file's own
|
|
7807
|
+
method section warns about. 38 revisions of *this* registry with 57 keys added, 0
|
|
7808
|
+
removed and exactly 1 value changed still stands, and is now the outlier rather
|
|
7809
|
+
than the confirmation.
|
|
7810
|
+
|
|
7811
|
+
*What that leaves is a rejection standing on one leg, deliberately.* A defect
|
|
7812
|
+
occurring seven times in six weeks in one adopting repository is not a hazard
|
|
7813
|
+
nobody has committed; it is a thing that happens. The detector is still refused,
|
|
7814
|
+
because scoring 0% on harm is a property of the mechanism that no frequency
|
|
7815
|
+
improves — a rule that flags four true echoes and is right about the danger of
|
|
7816
|
+
none of them is not made useful by there being more echoes to flag. Where the
|
|
7817
|
+
frequency finding goes instead is the param-value entry under `Under
|
|
7818
|
+
consideration`, which was always the entry about the move rather than about the
|
|
7819
|
+
assertion, and which the same report reinforces rather than settles.
|
|
7820
|
+
|
|
7821
|
+
*And the reopening condition was very nearly met, which is worth recording
|
|
7822
|
+
precisely.* It asks for a param whose value moved, whose assertion echoed it, and
|
|
7823
|
+
where the move was **harmful**. The report supplies the first two in one commit:
|
|
7824
|
+
two `AI-14` params changed, the scenarios reading the same values the
|
|
7825
|
+
implementation reads, the whole suite green, and a diff containing no spec file
|
|
7826
|
+
and no implementation file at all. The third leg fails, and fails cleanly — that
|
|
7827
|
+
move was the entire point of the change, argued for in a 63-line proposal
|
|
7828
|
+
committed beside it. So the closest instance anybody has is a **correct** edit
|
|
7829
|
+
that no gate could have objected to, which is evidence about the gate and not
|
|
7830
|
+
about the assertion.
|
|
7831
|
+
|
|
7832
|
+
*The guidance half is not what is rejected here.* `[0.5.0]` replaced "read the
|
|
7833
|
+
param inside the assertion" everywhere it appeared with what the evidence
|
|
7834
|
+
supports — the expectation must not be a function of the param the code under
|
|
7835
|
+
test consumed — and that shipped. What is rejected is the mechanism, and the
|
|
7836
|
+
hole stays open: a tautological assertion is still green and still silent.
|
|
7837
|
+
Documenting a trap is not detecting it, and this section is where that
|
|
7838
|
+
difference is recorded rather than hidden.
|
|
7839
|
+
|
|
7840
|
+
*Two things this leaves standing, neither of them a mechanism.* Design §11
|
|
7841
|
+
carries the property in the reader's form, so an adopter meets it where the
|
|
7842
|
+
method is taught. And the detector stays in
|
|
7843
|
+
`tests/intent-rule-candidates.spec.ts` with its verdict table recomputed on
|
|
7844
|
+
every run: it is the evidence that killed this, and deleting it would leave the
|
|
7845
|
+
rejection resting on a figure nothing checks — the exact failure this file has
|
|
7846
|
+
already had once, when the reach quoted for `compound-requirement` went stale in
|
|
7847
|
+
it.
|
|
7848
|
+
|
|
7849
|
+
*Reopening needs the one thing neither corpus supplied:* a param whose value
|
|
7850
|
+
moved, whose assertion echoed it, and where the move was **harmful**. Precision
|
|
7851
|
+
on the shape is a tuning question. Precision on the harm is the whole question,
|
|
7852
|
+
and telling a rendering fixture from a mine-density band is a judgement about
|
|
7853
|
+
whether the value *means* anything — which is the ground truth §0 refuses to
|
|
7854
|
+
compute for somebody else's registry. Nothing short of running each scenario
|
|
7855
|
+
twice under different values separates an independent pin from an echo, and that
|
|
7856
|
+
is mutation testing, a much larger thing to own.
|
|
7857
|
+
|
|
7858
|
+
The claim and the measurements, kept as they were taken:
|
|
7859
|
+
|
|
7860
|
+
**An assertion that recomputes its expectation from the param the code under
|
|
7861
|
+
test just read is a tautology, and nothing says so.** Reported by
|
|
7862
|
+
`mine-capablanca`, which probed it by mutating the registry and running the
|
|
7863
|
+
full gate on each mutation:
|
|
7864
|
+
|
|
7865
|
+
| mutation | who reads it | `check` | `verify` |
|
|
7866
|
+
|---|---|---|---|
|
|
7867
|
+
| `GEN-1.maxCandidatesPerPiece` 64 → 4 | scenario only | ok | **red** |
|
|
7868
|
+
| `BOARD-1.boardSize` 10 → 12 | app + fixtures | ok | **red** |
|
|
7869
|
+
| `BOARD-2.maxMines` 12 → **40** | app + assertion | ok | **green** |
|
|
7870
|
+
| `BOARD-2.minMines` 9 → **1** | app + assertion | ok | **green** |
|
|
7871
|
+
|
|
7872
|
+
The mine-density band — the one number deciding whether that game is playable —
|
|
7873
|
+
moved by 3.3× with the whole suite green, `check` silent, and `possible-drift`
|
|
7874
|
+
correctly not firing, because a covering scenario *does* read the param.
|
|
7875
|
+
**Read the third and fourth rows against the second, because that is the
|
|
7876
|
+
finding.** The reporter's diagnosis was "app and spec both read it", but row 2
|
|
7877
|
+
is app-and-spec and goes red. What distinguishes the green rows is narrower and
|
|
7878
|
+
more useful: the assertion **derives its expectation from the same param the
|
|
7879
|
+
code under test used**, so both sides move together and the comparison has no
|
|
7880
|
+
independent term. Row 2's expectation lives in fixtures, which do not move when
|
|
7881
|
+
the param does, and the mechanism works. So the failing condition is a property
|
|
7882
|
+
of the assertion, not of who reads the value — which matters, because it means
|
|
7883
|
+
the answer is a rule about how to write the assertion, and *not* the detector
|
|
7884
|
+
the reporter proposed.
|
|
7885
|
+
**What this falsifies is written in this file.** The rejection of a committed
|
|
7886
|
+
params snapshot, below, closes on *"the mechanism that guards a param is a
|
|
7887
|
+
scenario reading it, not a file recording it"* — a sufficiency claim, and rows
|
|
7888
|
+
3 and 4 are counterexamples to it. A scenario reading the param is necessary
|
|
7889
|
+
and not sufficient. That entry stays rejected on its own argument, which was
|
|
7890
|
+
never about detection power, but its fallback clause is now known incomplete
|
|
7891
|
+
and is annotated accordingly. The README's §"killer move" has the same shape:
|
|
7892
|
+
it says noticing a param *moved* is `verify`'s job "only when a scenario asserts
|
|
7893
|
+
on the value it read from `params`" — literally a necessary condition, and read
|
|
7894
|
+
by an adopter as a sufficient one, with the very next line (*"Read the param
|
|
7895
|
+
inside the assertion, not beside it"*) being the instruction that produces the
|
|
7896
|
+
tautology.
|
|
7897
|
+
**The framing that probably resolves it: this is `never-red` wearing different
|
|
7898
|
+
clothes.** A tautological assertion is a scenario that cannot fail, which is
|
|
7899
|
+
the exact defect `archive`'s `never-red` gate exists to catch — and it did
|
|
7900
|
+
catch two of them in this very repo (`CHECK-2`, `FOG-3`, both confirmed
|
|
7901
|
+
correct by the reporter). The reason it did not catch these is structural:
|
|
7902
|
+
**changing the value of an existing param goes through no gate at all.**
|
|
7903
|
+
`archive` gates a change; a param edit is not a change. That is a cleaner
|
|
7904
|
+
statement of the gap than "params can be tautological".
|
|
7905
|
+
*Re-read against `evidence-not-ordering`, which separated that gate's
|
|
7906
|
+
obligation from the ordering it used to be described by.* Nothing here moves:
|
|
7907
|
+
the blocker was never that the observation had to be taken first, it is that a
|
|
7908
|
+
param edit produces no change folder for any observation to be recorded
|
|
7909
|
+
against. If anything the framing gets sharper — what `never-red` demands is
|
|
7910
|
+
evidence that a scenario can fail, which is precisely what is missing for a
|
|
7911
|
+
tautological assertion, and the reason it goes undemanded is still the absence
|
|
7912
|
+
of a gate rather than the absence of a route to the evidence.
|
|
7913
|
+
**That observation is now its own entry at the foot of this section**, split
|
|
7914
|
+
out and measured on 2026-08-18, because it asks what happens when a value
|
|
7915
|
+
moves rather than whether an assertion is a real comparison, and neither
|
|
7916
|
+
answer settles the other. What stays here is the detector.
|
|
7917
|
+
*What blocks the decision — three things, all of them real.* **(1)** Every
|
|
7918
|
+
mutation above is synthetic. Across 8 commits touching `*.reqs.ts` in that
|
|
7919
|
+
repo, **no param value was ever modified** — every `params:` line in every diff
|
|
7920
|
+
is an addition. So the hole is proven as a logical property and its frequency is
|
|
7921
|
+
entirely unmeasured, from one greenfield repo. **(2)** The reporter's proposed
|
|
7922
|
+
mechanism — a WARNING when one param id is read from both a spec and a non-spec
|
|
7923
|
+
file — is the wrong shape twice over: it fires on row 2, which is correct usage
|
|
7924
|
+
and goes red, and it would fire on the arrangement the README actively
|
|
7925
|
+
recommends, which is §9's "usually wrong about staleness" failure mode again.
|
|
7926
|
+
It also needs a static sweep of the whole project for `reqs['X'].params.y`,
|
|
7927
|
+
far past the two registry readers' current surface. **(3)** No mechanism is yet
|
|
7928
|
+
known that separates "the assertion pins this independently" from "the assertion
|
|
7929
|
+
echoes it" without executing the scenario twice under different values — which
|
|
7930
|
+
is mutation testing, a much larger thing to own. *One has since been built and
|
|
7931
|
+
scored, and it does not lift this blocker.* The syntactic candidate — flag a
|
|
7932
|
+
scenario whose expectation is derived from the param the code under test
|
|
7933
|
+
consumed, decided inside one body without executing anything — flags 9 params
|
|
7934
|
+
here, of which **4 are real echoes and 5 are pinned by something outside the
|
|
7935
|
+
body it cannot see**. Under half precision, and the failure is the one
|
|
7936
|
+
predicted above rather than a tuning problem: what pins a value is often a
|
|
7937
|
+
fixture or a second derivation in another file, so a single-body detector is
|
|
7938
|
+
looking in the wrong place by construction. The newest false positive is the
|
|
7939
|
+
clearest instance — `ATX-79.subpath` is compared against `package.json`, which
|
|
7940
|
+
is not the code under test and does not read the registry, so the independent
|
|
7941
|
+
term is in another file entirely. Precision has fallen on every addition
|
|
7942
|
+
measured so far, which is the direction that matters. The score is quoted from the
|
|
7943
|
+
suite's snapshot and re-read with the judgement-table entry above, which is
|
|
7944
|
+
where the same measurement is used for a different decision.
|
|
7945
|
+
**The guidance half has shipped; this entry is now only about the detector.**
|
|
7946
|
+
"Read the param inside the assertion" was insufficient and half-wrong as
|
|
7947
|
+
written, and `[0.5.0]` replaces it everywhere it appeared — README, the
|
|
7948
|
+
`init` skill, `possible-drift`, and design §11 — with what the evidence
|
|
7949
|
+
supports: **the expectation must not be a function of the param the code under
|
|
7950
|
+
test consumed**, pinned in a fixture, a literal, or a second independently
|
|
7951
|
+
derived value. Row 2 is the worked example, and the reporter's own
|
|
7952
|
+
`CHECK-2`/`FOG-3` fixes are the same move. It was held back once because
|
|
7953
|
+
rewording the central claim of the tool is not a line appended; that is what
|
|
7954
|
+
was then done, deliberately and in one pass.
|
|
7955
|
+
**What stays undecided is the mechanism**, and all three blockers above are
|
|
7956
|
+
untouched by the rewording: the frequency is still unmeasured from one
|
|
7957
|
+
greenfield repo, the reporter's proposed detector is still the wrong shape
|
|
7958
|
+
twice over, and nothing short of mutation testing separates an independent pin
|
|
7959
|
+
from an echo. What the guidance does not do is close the hole — a tautological
|
|
7960
|
+
assertion is still green, still silent, and still reachable — so this stays
|
|
7961
|
+
here rather than moving to `Considered and rejected`: documenting a trap is
|
|
7962
|
+
not the same as deciding not to detect it.
|
|
7963
|
+
**Re-measured after `[0.7.1]` widened `params`, on this registry rather than
|
|
7964
|
+
the reporter's — the first time this experiment has been run here.** Twelve
|
|
7965
|
+
mutations, each followed by the whole gate. Two results matter, and they point
|
|
7966
|
+
opposite ways.
|
|
7967
|
+
*The shape is now reachable here, and the widening is why.* `self/render.spec.ts`
|
|
7968
|
+
asserts `expect(md).toContain(JSON.stringify(kinds))` over the same
|
|
7969
|
+
`payloadKinds` it feeds to the renderer — the expectation is a function of the
|
|
7970
|
+
value under test, verbatim row 3, and it could not have been written before
|
|
7971
|
+
the widening because the param could not be an object. The mechanism is
|
|
7972
|
+
sharper than a frequency count and is the strongest thing this entry has
|
|
7973
|
+
gained: **the prescribed remedy costs in proportion to the value's
|
|
7974
|
+
complexity.** Same repo, adjacent files, same value shape — `ATX-7` pins two
|
|
7975
|
+
numbers with literals and goes red; `ATX-63` would have had to hand-write a
|
|
7976
|
+
rendered JSON block, so it echoed. Depth raises the defect rate and the cost
|
|
7977
|
+
of avoiding it along one axis.
|
|
7978
|
+
*And its cost here is nil, which is the honest other half.* Both composite
|
|
7979
|
+
params are fixtures for the renderer and the validator; the numbers stand for
|
|
7980
|
+
nothing, so the green is arguably correct. Blocker **(1)** is therefore only
|
|
7981
|
+
half retired — the shape is reproduced in a second, mature repo, and the count
|
|
7982
|
+
of *harmful* instances is still zero. Blocker **(2)** is reinforced rather than
|
|
7983
|
+
weakened: all four green mutations were on params `src/` never reads, so the
|
|
7984
|
+
reporter's "read from both a spec and a non-spec file" detector would have
|
|
7985
|
+
flagged none of them. Blocker **(3)** is untouched.
|
|
7986
|
+
*The frequency is no longer unmeasured, and it points away from building
|
|
7987
|
+
anything.* Blocker **(1)** rested on 8 commits of one greenfield repo. Parsed
|
|
7988
|
+
at every one of the **38** revisions that have touched this registry — all 38
|
|
7989
|
+
readable by the current static reader, so nothing was skipped — the history is
|
|
7990
|
+
**57 param keys added, 0 removed, and exactly 1 value changed**: `ATX-16.staticCommands`
|
|
7991
|
+
gaining `status`. That single edit is a roster *widening*, the safe direction,
|
|
7992
|
+
and it is on the one list that was already pinned, so it could not have been
|
|
7993
|
+
made without updating the pin. Two independent codebases, one greenfield and
|
|
7994
|
+
one at 64 requirements, and the event this whole entry is about has happened
|
|
7995
|
+
zero times in either. What that argues is not that the hole is unreal — it is
|
|
7996
|
+
reachable, demonstrated above — but that it is **rare and silent**, which is
|
|
7997
|
+
the profile `divergent-param` was rejected on rather than the profile that
|
|
7998
|
+
justifies an engine rule.
|
|
7999
|
+
*The same number says the split-out fix below has also never been triggered.*
|
|
8000
|
+
0 removals ever means no domain has ever been shortened here. Those pins close
|
|
8001
|
+
a live *exposure*, not a live *loss*, and the case for them is that they cost
|
|
8002
|
+
one line each against a failure with no symptom — not that anything was
|
|
8003
|
+
currently broken.
|
|
8004
|
+
*Blocker **(3)** assumed mutation testing was the only separator. A cheaper
|
|
8005
|
+
candidate existed, has now been scored, and does not survive it.* The idea was
|
|
8006
|
+
a **syntactic** discriminator over one test body — flag an assertion whose
|
|
8007
|
+
expected value derives from a param that also reached the arrange/act part —
|
|
8008
|
+
which is exactly the difference between the two cases the widening produced:
|
|
8009
|
+
`renderMarkdown({params: {payloadKinds: kinds}})` … `toContain(JSON.stringify(kinds))`
|
|
8010
|
+
against `expect(kinds.json.maxBytes).toBe(1_048_576)`. Scored the way
|
|
8011
|
+
`divergent-param` should have been and was not: implemented in
|
|
8012
|
+
`tests/intent-rule-candidates.spec.ts`, run over the specs that attest this
|
|
8013
|
+
repo, and then **every param it fires on mutated with the whole gate run —
|
|
8014
|
+
fifteen of them, one gate run each. Four went green; eleven went red.**
|
|
8015
|
+
*Then the detector was found to be wrong, which is the more useful half.* Its
|
|
8016
|
+
first implementation counted an identifier in any position as a read of a
|
|
8017
|
+
local with that name, so `i.code` in `issues.map((i) => i.code)` looked like a
|
|
8018
|
+
use of a param named `code` — and `code` is this repo's commonest param name.
|
|
8019
|
+
Corrected to count reference positions only, it flags **8, not 15**, and the
|
|
8020
|
+
seven it drops are exactly seven the mutations had already shown to be pinned
|
|
8021
|
+
by `src/`. The honest score is therefore **4 of 8, not 4 of 15** — precision
|
|
8022
|
+
50%, not 27%. The conclusion does not move, because it never rested on the
|
|
8023
|
+
precision figure; what moves is how much of the original number was the
|
|
8024
|
+
candidate failing and how much was the measurement being wrong, and that is
|
|
8025
|
+
worth writing down rather than quietly restating.
|
|
8026
|
+
The remaining false positives are not near-misses. They are independent terms
|
|
8027
|
+
the detector cannot see: **the runtime is the term** — `ATX-14`'s
|
|
8028
|
+
`inheritedKey` is `'toString'`, and a mutated spelling is not a prototype key
|
|
8029
|
+
at all, so the behaviour under test changes; **`src/` is the term** —
|
|
8030
|
+
`ATX-51`'s `code` is a second copy of a string the engine emits as a literal;
|
|
8031
|
+
and **arithmetic is the term** — `threshold + 3` builds a derived expectation
|
|
8032
|
+
that mutation breaks.
|
|
8033
|
+
And the four true positives fail the second test, which is the one that
|
|
8034
|
+
decides it: **none is harmful.** `ATX-10` and `ATX-3` assert that a value was
|
|
8035
|
+
substituted or applied, where the identity of the number is not the
|
|
8036
|
+
obligation; `ATX-51`'s is a path prefix and `ATX-63`'s a rendering fixture.
|
|
8037
|
+
Telling those from a mine-density band that must not move is a judgement about
|
|
8038
|
+
whether the value *means* anything — the ground truth §0 refuses to compute
|
|
8039
|
+
for someone else's registry. **50% precision on the shape, 0% on the harm.**
|
|
8040
|
+
Two limits on that number, stated because a score with an unstated method is
|
|
8041
|
+
what this file exists to prevent. The mutation has **one direction per kind**
|
|
8042
|
+
— numbers +1, lists drop-last, strings suffixed — and direction can decide the
|
|
8043
|
+
verdict: `ATX-40.languages` is red at 2→3 and was green at 2→1 before it was
|
|
8044
|
+
pinned, so the true-positive count is a floor. And **recall is unmeasured**:
|
|
8045
|
+
the eight params mutated earlier that this detector does not flag all went
|
|
8046
|
+
red, which is eight correct negatives and not a recall figure.
|
|
8047
|
+
The detector and its verdict table are kept in the measurement suite, with the
|
|
8048
|
+
reach recomputed on every run and the table asserted to cover exactly what
|
|
8049
|
+
fires — because the reach figure quoted for `compound-requirement` went stale
|
|
8050
|
+
in this very file for want of that.
|
|
8051
|
+
*A scoping limit on all of the above.* This repo cannot settle blocker **(2)**.
|
|
8052
|
+
`src/` reads no param at all — every `reqs[…].params` in it is a comment or a
|
|
8053
|
+
doc string — so params here are test vocabulary, while in the reporter's repo
|
|
8054
|
+
they are application constants the app itself consumes. The reporter's
|
|
8055
|
+
detector keys on exactly that difference, so it is inert here and this corpus
|
|
8056
|
+
is evidence neither for nor against it.
|
|
8057
|
+
*What the re-measurement did find is a different defect, and it has been split
|
|
8058
|
+
out below* — the entry is kept whole here because the two were measured in one
|
|
8059
|
+
pass and separating them is the finding.
|
|
8060
|
+
|
|
8061
|
+
### Retiring a requirement
|
|
8062
|
+
|
|
8063
|
+
Filed 2026-08-03 on the cost of permanence, re-measured four times, and rejected
|
|
8064
|
+
2026-08-31 **by its own reopening condition**. The entry nominated the test —
|
|
8065
|
+
reopen if the cost per requirement moves — and the ratio has read 2.56 → 2.78
|
|
8066
|
+
→ 2.80 → 2.82 scenarios per requirement across twenty-five days and
|
|
8067
|
+
twenty-four requirements. Three consecutive readings within 0.05 of each other
|
|
8068
|
+
are not a curve about to move, and the condition has now failed to trigger
|
|
8069
|
+
twice. The cost argument is therefore not waiting for evidence; it was answered
|
|
8070
|
+
in the negative by the thing the entry itself chose to be judged on.
|
|
8071
|
+
|
|
8072
|
+
*The three meanings it ended by separating are why the rest closes rather than
|
|
8073
|
+
merely goes quiet.* **(1) A requirement that turned out wrong** was never a
|
|
8074
|
+
retirement — the record of being wrong belongs in this section, which is what
|
|
8075
|
+
this section is, and removing the id is a manual registry edit nobody has argued
|
|
8076
|
+
against. **(3) A requirement subsumed by a later one** is a rename, blocked
|
|
8077
|
+
downstream of `--apply`'s refusal of RENAMED (`splice.ts`: deleting an entry has
|
|
8078
|
+
no smaller span to fall back to). That is somebody else's blocker, and lifting it
|
|
8079
|
+
is a decision which is not this one. It was shared with the backlink-index entry
|
|
8080
|
+
until 2026-09-01, when that entry was rejected; this is now the only thing
|
|
8081
|
+
resting on the refusal. **(2) A requirement still true and no
|
|
8082
|
+
longer worth a permanently-run scenario** was the only one ever about cost, and
|
|
8083
|
+
it is the one the ratio answers.
|
|
8084
|
+
|
|
8085
|
+
*What (2) left behind was a defect rather than a mechanism, and it is fixed.*
|
|
8086
|
+
`uncovered-requirement` read "Add a scenario, or **mark it deferred**", and
|
|
8087
|
+
`deferred` appeared nowhere else in the project — not a field, not a flag, not
|
|
8088
|
+
a schema key — so the escape hatch this meaning needs had been named in shipped
|
|
8089
|
+
output, in the one place a reader looks when they hit the error, since before
|
|
8090
|
+
anybody asked the question. `[Unreleased]` rewords it, troubleshooting now says
|
|
8091
|
+
what the second exit actually is, and design §11 records the permanence as a
|
|
8092
|
+
property to design against rather than a gap awaiting a mechanism. **This entry
|
|
8093
|
+
is where the trace of the affordance having been intended now lives**, which is
|
|
8094
|
+
why the rewording waited for the rejection instead of going in as a stray fix:
|
|
8095
|
+
changing the message first would have deleted the only written evidence that
|
|
8096
|
+
anybody ever meant to build it.
|
|
8097
|
+
|
|
8098
|
+
*The permanence itself is not rejected and is not a defect.* Deleting an intent
|
|
8099
|
+
in order to save the scenario attesting it inverts the claim the whole tool
|
|
8100
|
+
exists to make. Reopening needs the case (2) names — a requirement whose intent
|
|
8101
|
+
is still true and whose scenario is genuinely not worth running — together with
|
|
8102
|
+
a mechanism that keeps the first while dropping the second. Nobody has proposed
|
|
8103
|
+
one, and the message that implied there already was one is the reason the
|
|
8104
|
+
question looked bigger than it is.
|
|
8105
|
+
|
|
8106
|
+
The measurements, kept as they were taken:
|
|
8107
|
+
|
|
8108
|
+
**Whether a requirement can ever be retired, and what it costs that none can.**
|
|
8109
|
+
The registry is at **85 requirements and 240 scenarios, 2.82 each**, and
|
|
8110
|
+
every one is a permanent obligation: `uncovered-requirement` is an ERROR, so a
|
|
8111
|
+
requirement without a scenario cannot exist, and nothing ever removes one.
|
|
8112
|
+
`delta.removed` exists and `applyDelta` applies it, but `--apply` refuses to
|
|
8113
|
+
write it back, deliberately and with the reason recorded — REMOVED "cannot say
|
|
8114
|
+
which comments belonged to the entry it deletes". So retiring one is a manual
|
|
8115
|
+
registry edit, which is fine as a mechanism and says nothing about whether it
|
|
8116
|
+
is ever the right move.
|
|
8117
|
+
*What the growth actually costs, measured on this session rather than
|
|
8118
|
+
estimated.* One small fix took: a requirement, its scenarios, a hand-judged
|
|
8119
|
+
row in the `OBLIGATIONS` table, two inline snapshots, a `[Unreleased]` entry,
|
|
8120
|
+
History rows in two languages, and a regenerated `SPEC.md`. That is
|
|
8121
|
+
proportionate for a spec framework that has to dogfood — the ceremony *is* the
|
|
8122
|
+
product being used on itself — and it is also a number that only goes up.
|
|
8123
|
+
*What blocks the decision, and it is not the mechanism.* It is that "retire"
|
|
8124
|
+
has at least three meanings this file has never had to separate: a requirement
|
|
8125
|
+
that turned out **wrong** (which should leave a record of being wrong, and
|
|
8126
|
+
`Considered and rejected` is where that already goes); one still **true** but
|
|
8127
|
+
no longer worth a permanently-run scenario; and one **subsumed** by a later
|
|
8128
|
+
requirement, where the scenario should move rather than go. Only the second is
|
|
8129
|
+
really about cost, and it is the one where deleting the intent to save the
|
|
8130
|
+
scenario inverts what the whole tool claims — that intent outlives its tests.
|
|
8131
|
+
Nothing here is urgent at 85. It is filed now because the moment to write the
|
|
8132
|
+
argument down is while the reasoning is available, and because a project whose
|
|
8133
|
+
requirement count only rises should have decided in advance what would make
|
|
8134
|
+
one leave.
|
|
8135
|
+
*The figures above were restated on 2026-08-08, and how they were wrong is
|
|
8136
|
+
itself the section's own warning happening.* As filed on 2026-08-03 this entry
|
|
8137
|
+
said "62 requirements and 160 scenarios, roughly 2.6 each" while closing on
|
|
8138
|
+
"the number is 61" — an inconsistency inside one entry, and the closing half
|
|
8139
|
+
was the right one. Re-measured at the entry's own commit with the current
|
|
8140
|
+
parser: **61 requirements, 156 scenarios**, so both opening figures were wrong
|
|
8141
|
+
on the day they were written, and only the ratio survived. Measured the same
|
|
8142
|
+
way today it is 69 and 192, and the method is the same one `verify self`
|
|
8143
|
+
prints — the control is that today's `self/` counted this way returns exactly
|
|
8144
|
+
what the runner does, which is what makes the 2026-08-03 number trustworthy. The
|
|
8145
|
+
cheap way — `grep -c 'scenario('` — answers 211 against today's true 192, and
|
|
8146
|
+
is what would have been reached for: comments and prose count, which is the
|
|
8147
|
+
`ATX-15` error again.
|
|
8148
|
+
**What moved in five days sharpens the argument rather than dating it.** +8
|
|
8149
|
+
requirements and +36 scenarios, concentrated in three sessions rather than
|
|
8150
|
+
accrued at a rate, so nothing here is a trend line. The part worth keeping is
|
|
8151
|
+
the ratio: 2.56 → 2.78. The cost of a requirement is not flat and it is not
|
|
8152
|
+
falling, which is the premise the whole entry rests on, and it is the first
|
|
8153
|
+
time this file has had two measurements of it to compare.
|
|
8154
|
+
*A third measurement, on 2026-08-18, and it points the other way from the
|
|
8155
|
+
second.* **79 requirements and 221 scenarios**, counted the way `verify self`
|
|
8156
|
+
prints them, which is the control the 2026-08-08 restatement established. The
|
|
8157
|
+
ratio runs 2.56 → 2.78 → **2.80**, so the step that carried the argument was
|
|
8158
|
+
the first one and the second is **+0.015**, which is flat. The premise this
|
|
8159
|
+
entry rests on now has one measurement for it and one against, and the honest
|
|
8160
|
+
statement is the narrower one: per-requirement cost has held at about 2.8
|
|
8161
|
+
scenarios across ten days and ten requirements. What is unchanged is the
|
|
8162
|
+
absolute total, which only rises — that half of the argument is untouched, and
|
|
8163
|
+
it was always the half about permanence rather than about acceleration. What is
|
|
8164
|
+
no longer supported is the acceleration. **Reopening on cost needs the ratio to
|
|
8165
|
+
move again**; reopening on the three meanings of "retire" needs none of these
|
|
8166
|
+
figures, and that is still the part nobody has separated.
|
|
8167
|
+
*A fourth measurement, on 2026-08-28, and the cost half is now closed rather
|
|
8168
|
+
than merely unsupported.* **85 requirements and 240 scenarios**, counted the
|
|
8169
|
+
way `verify self` prints them: the ratio runs 2.56 → 2.78 → 2.80 → **2.82**.
|
|
8170
|
+
Three consecutive readings within 0.05 of each other, across twenty-five days
|
|
8171
|
+
and twenty-four requirements, are not a curve that is about to move. The
|
|
8172
|
+
reopening condition this entry set itself has now failed to trigger twice, so
|
|
8173
|
+
the cost argument is not waiting for evidence — it has been answered, in the
|
|
8174
|
+
negative, by its own test. What survives untouched is the half that was never
|
|
8175
|
+
about the ratio: the absolute total only rises, and permanence is the claim.
|
|
8176
|
+
|
|
8177
|
+
**The three meanings, separated — which is what the entry has been asking for
|
|
8178
|
+
and needs none of the figures above.** They are not one question, and only one
|
|
8179
|
+
of the three is open.
|
|
8180
|
+
|
|
8181
|
+
**(1) A requirement that turned out wrong.** Not a retirement at all, and the
|
|
8182
|
+
mechanism already exists: the record of being wrong belongs in `Considered and
|
|
8183
|
+
rejected`, which is what that section is, and removing the id is a manual
|
|
8184
|
+
registry edit nobody has argued against. Nothing is missing here. Filing it
|
|
8185
|
+
under "retire" was what made the question look bigger than it is.
|
|
8186
|
+
|
|
8187
|
+
**(2) A requirement still true but no longer worth a permanently-run
|
|
8188
|
+
scenario.** The only one of the three that is about cost, and the only one with
|
|
8189
|
+
no mechanism — and the sharpest thing this separation turned up is that the
|
|
8190
|
+
tool has been *advertising* one. `uncovered-requirement` reads "Add a scenario,
|
|
8191
|
+
or **mark it deferred**", and `deferred` appears nowhere else in `src/`: not a
|
|
8192
|
+
field, not a flag, not a schema key. The troubleshooting section for that code
|
|
8193
|
+
quotes the message as its symptom and then explains only the first half,
|
|
8194
|
+
because there is no second half to explain. So the escape hatch this meaning
|
|
8195
|
+
needs has been named in shipped output, and in the one place a reader looks
|
|
8196
|
+
when they hit the error, since before anyone asked the question. That is a
|
|
8197
|
+
defect in its own right — the message is prose and free to reword — but it is
|
|
8198
|
+
recorded here rather than fixed there, because rewording it would delete the
|
|
8199
|
+
only written trace that this affordance was ever intended, which is the thing
|
|
8200
|
+
this entry exists to keep. *That held until the entry itself closed, and no
|
|
8201
|
+
longer applies:* the trace is the paragraph above, the rejection is what makes
|
|
8202
|
+
it durable, and `[Unreleased]` rewords the message. The condition on the
|
|
8203
|
+
rewording was never that it stay wrong — it was that somewhere else had to say
|
|
8204
|
+
the affordance had been meant first.
|
|
8205
|
+
|
|
8206
|
+
**(3) A requirement subsumed by a later one**, where the scenario should move
|
|
8207
|
+
rather than go. That is a rename, and `--apply` refuses RENAMED outright, with
|
|
8208
|
+
the reason recorded in `splice.ts` — deleting an entry has no smaller span to
|
|
8209
|
+
fall back to. So it is blocked downstream of a refusal nobody has proposed
|
|
8210
|
+
lifting, which is the *same* blocker the backlink-index entry above carried until
|
|
8211
|
+
that entry was rejected on 2026-09-01. Two open entries rested on that one
|
|
8212
|
+
refusal and now **one** does, which is worth knowing and cuts the other way from
|
|
8213
|
+
how it was first written: a decision with two dependents is easier to justify
|
|
8214
|
+
than a decision with one, so the case for ever lifting the refusal got weaker
|
|
8215
|
+
rather than the blocker getting closer to clearing. This entry is not the reason
|
|
8216
|
+
the refusal exists, and it is now the only thing waiting on it.
|
|
8217
|
+
|
|
8218
|
+
*So the entry narrows to one meaning.* (1) is answered, (3) is somebody else's
|
|
8219
|
+
blocker, and what is actually open is (2) — whether a requirement can stop
|
|
8220
|
+
owing a permanently-run scenario while keeping its intent, which is the one
|
|
8221
|
+
case where deleting the intent to save the scenario inverts what the whole tool
|
|
8222
|
+
claims. Nothing here says it should be built; what changes is that the question
|
|
8223
|
+
is now one question.
|
|
8224
|
+
|
|
8225
|
+
### Declared prefix ownership instead of inferred
|
|
8226
|
+
|
|
8227
|
+
Filed 2026-08-19 from a spec-kit reading, rejected 2026-08-31 on the two things
|
|
8228
|
+
that moved under it. *What rejected it: the proposal is smaller than it looked,
|
|
8229
|
+
weaker than it looked, and answers a question nobody here has asked.*
|
|
8230
|
+
`loadRegistry` already returns `prefixOwners`, `merge.ts` already uses it to
|
|
8231
|
+
place an ADDED id, and `apply-no-prefix-owner` already refuses a prefix nobody
|
|
8232
|
+
owns — so what was proposed is declaration replacing inference, not a new
|
|
8233
|
+
layer. And `/inspect` shipped in `[0.9.2]`, so the cross-repository half — the
|
|
8234
|
+
one §11 says no command can see across — is now about ten lines of an
|
|
8235
|
+
adopter's own code over two registries. Attest does not have to own it to make
|
|
8236
|
+
it possible.
|
|
8237
|
+
|
|
8238
|
+
The third is what decides it. This repository's five prefixes — `ATX` and the
|
|
8239
|
+
four fixture ones — are each declared by exactly one file, so the allocation
|
|
8240
|
+
question a declaration would answer has never arisen here. Building it now is
|
|
8241
|
+
deciding prefix semantics for everyone from a corpus in which those semantics
|
|
8242
|
+
are unexercised, which is the objection this project raises against every
|
|
8243
|
+
premature interface.
|
|
8244
|
+
|
|
8245
|
+
§11 keeps the property in place unchanged: one flat global id space, the
|
|
8246
|
+
prefix a convention rather than a scope, and `duplicate-prefix` able only to
|
|
8247
|
+
report a collision that already happened. **The cost still arrives exactly when
|
|
8248
|
+
adoption succeeds** — two teams sharing a registry have somebody allocating
|
|
8249
|
+
prefixes by hand — and that is the reopening condition. It is also the
|
|
8250
|
+
condition the two-party audit entry under consideration would satisfy, so the
|
|
8251
|
+
first adopter who shares a registry decides both.
|
|
8252
|
+
|
|
8253
|
+
The reading this was filed from, kept as it was written:
|
|
8254
|
+
|
|
8255
|
+
**Prefix ownership is inferred rather than declared, and the cost arrives only
|
|
8256
|
+
when adoption succeeds.** Design §11 records that the id grammar has no level
|
|
8257
|
+
above the requirement: the prefix is a convention, nothing allocates it, and
|
|
8258
|
+
`duplicate-prefix` can only report a collision that already happened.
|
|
8259
|
+
spec-kit's answer is a roadmap table — a stable id, an intent, a scope
|
|
8260
|
+
boundary, dependencies — authored above the specs it decomposes into. That
|
|
8261
|
+
comparison is stated here rather than referred to, because the entry that
|
|
8262
|
+
introduced the spec-kit reading has since been built and now sits under
|
|
8263
|
+
`[0.9.4]` as the `open` field: an entry whose evidence rests on a
|
|
8264
|
+
neighbour is one a promotion silently empties.
|
|
8265
|
+
*Two things have changed since that reading, and they point opposite ways.*
|
|
8266
|
+
The proposal is **smaller** than it looked: the mechanism partly exists.
|
|
8267
|
+
`loadRegistry` returns `prefixOwners` — first claim in sorted file order —
|
|
8268
|
+
`merge.ts` uses it to decide which file an ADDED id belongs in, and
|
|
8269
|
+
`apply-no-prefix-owner` already refuses a prefix nobody owns. So what is
|
|
8270
|
+
proposed is declaration replacing inference, not a new layer. And it is
|
|
8271
|
+
**weaker**: `/inspect` shipped in `[0.9.2]`, so the cross-repository half —
|
|
8272
|
+
the one §11 says no command can see across — is now something an adopter
|
|
8273
|
+
computes from two registries in about ten lines. Attest does not have to own
|
|
8274
|
+
it to make it possible.
|
|
8275
|
+
*What blocks the decision:* nobody has the problem. §11 states the cost
|
|
8276
|
+
arrives "exactly when adoption succeeds: two teams sharing a registry have
|
|
8277
|
+
someone owning prefix allocation by hand", and no such adoption has been
|
|
8278
|
+
reported. Building the declared form now would be deciding prefix semantics
|
|
8279
|
+
for everyone from one repository whose five prefixes — `ATX` and the four
|
|
8280
|
+
fixture ones — are each declared by exactly one file, so the allocation
|
|
8281
|
+
question this would answer has never been asked here.
|
|
8282
|
+
|
|
7125
8283
|
### An id-keyed retrieval bundle over `archive/` and this file
|
|
7126
8284
|
|
|
7127
8285
|
Proposed 2026-08-19 inside the entry now titled *The decisions worth retrieving
|
|
@@ -7237,6 +8395,17 @@ for the section most likely to be read before a proposal. Nothing was changed
|
|
|
7237
8395
|
about that here: restructuring twelve open entries is a larger edit than the
|
|
7238
8396
|
finding justifies, and the finding is only four events old.
|
|
7239
8397
|
|
|
8398
|
+
*The counts above are the 2026-08-28 measurement and have since moved; recounted
|
|
8399
|
+
2026-08-31.* `Considered and rejected` holds **27 `###` entries**, not 24 —
|
|
8400
|
+
entries were decided into it in the meantime, and `init --check` left it,
|
|
8401
|
+
reopened — and `Under consideration` holds **six** bullets, not twelve. The
|
|
8402
|
+
heading ratio is deliberately not recounted: judging a heading artifact-shaped is
|
|
8403
|
+
a hand call, so re-running it would be a new measurement rather than a repair of
|
|
8404
|
+
this one, and the entry rests on the argument rather than on the ratio. What the
|
|
8405
|
+
recount does settle is the aside above about the section next door — at six
|
|
8406
|
+
bullets the case for giving it headings is weaker than it was at twelve, not
|
|
8407
|
+
stronger.
|
|
8408
|
+
|
|
7240
8409
|
### A per-scenario axis recording which interface a scenario crosses
|
|
7241
8410
|
|
|
7242
8411
|
Prompted by an external write-up (`columns.chicken-house.net`, 2025-11-10, "AI-First
|
|
@@ -7907,27 +9076,6 @@ is the "cleared by deleting the word that triggered it" failure arriving without
|
|
|
7907
9076
|
anyone trying to clear it, which strengthens the rejection rather than
|
|
7908
9077
|
qualifying it.
|
|
7909
9078
|
|
|
7910
|
-
### A freshness gate on the file `attest init` writes (`init --check`)
|
|
7911
|
-
|
|
7912
|
-
`render --check` is the obvious precedent and is the opposite case. A rendering
|
|
7913
|
-
is a function of the *user's registry*, so its staleness means the user moved
|
|
7914
|
-
intent without regenerating — a real signal, with the fix in their hands. The
|
|
7915
|
-
skill is a function of the *installed version*, so a gate on it turns red on a
|
|
7916
|
-
change the user did not make, and is cleared by a commit containing no decision.
|
|
7917
|
-
That is a chore wearing a diagnostic's clothes, and a gate that cannot be about
|
|
7918
|
-
anything the reader did is worse than no gate — the same standard 0.1.6 set when
|
|
7919
|
-
it gave `render` no formatting options at all. An earlier draft of `attest init`
|
|
7920
|
-
spent a second command, this gate and a second self-requirement on it. What that
|
|
7921
|
-
would have bought is immunity to one honest and small exposure: an old file
|
|
7922
|
-
describes an old workflow to an agent that trusts it. The backstop is that every
|
|
7923
|
-
mistake it can cause is already a diagnostic with a fix hint — a registry
|
|
7924
|
-
written the old way is `registry-not-static` — so the agent corrects itself from
|
|
7925
|
-
the report, and `init` is idempotent, which makes re-running it the entire
|
|
7926
|
-
update mechanism. Recorded here rather than left in the 0.3.0 entry that shipped
|
|
7927
|
-
`init`, because the queue above depends on it: a gate is the first thing
|
|
7928
|
-
proposed each time a new agent target is considered, and the answer must not
|
|
7929
|
-
need re-deciding per target.
|
|
7930
|
-
|
|
7931
9079
|
### A marked block in the adopter's `AGENTS.md` / `CLAUDE.md`
|
|
7932
9080
|
|
|
7933
9081
|
The cross-tool convention Codex, Cursor and Copilot read, and the reason it
|
|
@@ -8057,9 +9205,13 @@ was written expecting the uncovered case and the demonstrated one has a covering
|
|
|
8057
9205
|
scenario. **What that changes here is nothing**: this entry was rejected on the
|
|
8058
9206
|
population a snapshot fires on — every legitimate param edit, fix a commit with
|
|
8059
9207
|
no decision in it — and that objection is untouched by the new evidence, which
|
|
8060
|
-
concerns detection the snapshot was never denied having. The live question
|
|
8061
|
-
to `Under consideration
|
|
8062
|
-
|
|
9208
|
+
concerns detection the snapshot was never denied having. The live question
|
|
9209
|
+
moved to `Under consideration`, because the mechanism then in question was a
|
|
9210
|
+
rule about assertions rather than a file beside the registry, and it has since
|
|
9211
|
+
been rejected in its own right — above in this section, on a frequency of about
|
|
9212
|
+
zero across two codebases and a detector scoring 0% on harm. So the hole both
|
|
9213
|
+
entries describe is now recorded as open with nothing watching it, which is a
|
|
9214
|
+
weaker position than either entry claimed on its own and is the honest one.
|
|
8063
9215
|
|
|
8064
9216
|
### Grouping `render`'s output by prefix
|
|
8065
9217
|
|
|
@@ -8251,8 +9403,9 @@ fallback to evaluation. The same answer applies here for the same reason.
|
|
|
8251
9403
|
|
|
8252
9404
|
Worth reopening if an adopter's registry reaches a size where prefix grouping
|
|
8253
9405
|
and `grep` genuinely stop working — but the first thing to try then is the
|
|
8254
|
-
declared prefix ownership
|
|
8255
|
-
|
|
9406
|
+
declared prefix ownership recorded above in this section — rejected on there
|
|
9407
|
+
being no adoption that needs it, which is the same condition this sentence
|
|
9408
|
+
supposes — which addresses, rather than a scorer, which ranks.
|
|
8256
9409
|
|
|
8257
9410
|
## How an entry is re-measured
|
|
8258
9411
|
|
|
@@ -8331,6 +9484,8 @@ path the repair makes reachable; it is about **everything the repair would make
|
|
|
8331
9484
|
|
|
8332
9485
|
## Releases
|
|
8333
9486
|
|
|
9487
|
+
[1.1.0]: https://gitlab.com/Pseudorca/attest/-/tags/v1.1.0
|
|
9488
|
+
[1.0.0]: https://gitlab.com/Pseudorca/attest/-/tags/v1.0.0
|
|
8334
9489
|
[0.11.0]: https://gitlab.com/Pseudorca/attest/-/tags/v0.11.0
|
|
8335
9490
|
[0.10.0]: https://gitlab.com/Pseudorca/attest/-/tags/v0.10.0
|
|
8336
9491
|
[0.9.4]: https://gitlab.com/Pseudorca/attest/-/tags/v0.9.4
|