@am_shork/attest 1.0.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 +611 -205
- 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/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -90,6 +90,248 @@ above and 110 stale lines on this one.
|
|
|
90
90
|
security hole takes a major, on whatever schedule a hole is found. That price is
|
|
91
91
|
now stated rather than unknown, which is what made the line decidable at all.
|
|
92
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
|
+
|
|
93
335
|
## [1.0.0] - 2026-09-01
|
|
94
336
|
|
|
95
337
|
### Added
|
|
@@ -6506,211 +6748,6 @@ re-measuring an entry before building it is at the foot of the file, under
|
|
|
6506
6748
|
`How an entry is re-measured` — it applies to all three, and was filed here only
|
|
6507
6749
|
because the first of it was written here.
|
|
6508
6750
|
|
|
6509
|
-
- **An outward-facing form of the judgement tables, so an adopter can run this
|
|
6510
|
-
counter-pressure on their own registry.**
|
|
6511
|
-
`tests/intent-rule-candidates.spec.ts` maintains four hand-judged tables over
|
|
6512
|
-
this repo's registry and enforces their completeness. Design §11 records the
|
|
6513
|
-
properties they defend and that nothing enforces any of them; what the tables
|
|
6514
|
-
add is a counter-pressure holding for **one** registry whose authors agreed to
|
|
6515
|
-
maintain it. An adopter got none of it, and the reason was sharper
|
|
6516
|
-
than "nobody wrote it for them": the package exported `requirement`,
|
|
6517
|
-
`scenario`, `defineRequirements`, `delta` and types, and nothing else.
|
|
6518
|
-
`parseSpecs`, `findFiles` and `readRegistrySource` were internal, so an
|
|
6519
|
-
adopter **could not enumerate their own scenario names** — the table's central
|
|
6520
|
-
rot check, that a claimed scenario is one a spec really declares, was not
|
|
6521
|
-
merely unwritten downstream but unreachable. **That half shipped under
|
|
6522
|
-
`[0.9.2]` as `@am_shork/attest/inspect`** and is recorded at the foot of
|
|
6523
|
-
this entry; what stays here is the schema built on top of it, and the three
|
|
6524
|
-
blockers below are untouched by the export.
|
|
6525
|
-
*The shape that makes it shippable at all is a split, not a feature.* The
|
|
6526
|
-
engine can compute which ids exist, which statements match the universal
|
|
6527
|
-
quantifier regex, which params are list-valued, and whether a named scenario
|
|
6528
|
-
is declared — all four already, all statically. It cannot compute how many
|
|
6529
|
-
obligations a requirement carries, what a quantifier ranges over, or whether a
|
|
6530
|
-
scenario iterates a param. So the shippable object is **the enforcement, never
|
|
6531
|
-
the judgement**: an opt-in `*.judge.ts` read by the same static reader and
|
|
6532
|
-
under the same literal-only rule the registry is (it is metadata about
|
|
6533
|
-
requirements under review, so `check` must execute nothing to read it), whose
|
|
6534
|
-
contents are entirely the adopter's. Opting in is creating the file; once it
|
|
6535
|
-
exists, `requirement-unjudged`, `orphan-judgement`, `unbound-site` and
|
|
6536
|
-
`quantifier-unjudged` make it complete and keep it from rotting. The
|
|
6537
|
-
asymmetry is the only way this can exist without Attest asserting a ground
|
|
6538
|
-
truth it refuses to compute (§0), and it is the same asymmetry `never-red`
|
|
6539
|
-
already runs on: the archive gate would owe judgement only for ids a change
|
|
6540
|
-
ADDs, so a brownfield adoption is not blocked wholesale.
|
|
6541
|
-
*One part of the existing table cannot ship in any form.* `DOMAINS.pin` comes
|
|
6542
|
-
in two kinds — a scenario name, checkable against the AST, and a **source-text
|
|
6543
|
-
fragment** grepped out of the spec file. The second is asserting on the
|
|
6544
|
-
wording of somebody's test, which is the `Issue.message` category error under
|
|
6545
|
-
another name. Only the scenario-name kind is exportable, and what that costs
|
|
6546
|
-
is real: the grep is what caught a deleted pin that `toContain` had passed.
|
|
6547
|
-
*Why no detector ships with it, and this is the part that is already
|
|
6548
|
-
measured.* Three rule families were built and scored against the whole corpus
|
|
6549
|
-
(86 entries, counting a fixture id once per registry file) before any was
|
|
6550
|
-
written into the engine, which is the order `divergent-param` was originally
|
|
6551
|
-
designed in the wrong way round. Every figure below is a quotation of the
|
|
6552
|
-
suite's inline snapshots; the snapshot is the fresh copy, and this paragraph
|
|
6553
|
-
is only ever as fresh as its last edit.
|
|
6554
|
-
**(1) `compound-requirement`** — 34 requirements carry more than one
|
|
6555
|
-
obligation, it flags 16, is **wrong about none**, and misses 18. Never wrong
|
|
6556
|
-
and nearly blind, and the 18 are the form the coverage incentive actually
|
|
6557
|
-
rewards: extra clauses under a single keyword. **(2) The widened variant** —
|
|
6558
|
-
29 correct, but 14 wrong and still 5 missed, and it is silenced by deleting an
|
|
6559
|
-
"and". A rule whose cheapest fix degrades the artifact it protects does not
|
|
6560
|
-
ship, and its precision falls as the registry grows. **(3) `divergent-param`**
|
|
6561
|
-
— 5 divergences on this repo, **all 5 judged wrong**, zero true positives ever
|
|
6562
|
-
recorded. The `code` group now spans 19 ids, and the failure is structural
|
|
6563
|
-
rather than a matter of naming: the key is built by discarding the evidence of
|
|
6564
|
-
relatedness, so the one genuinely paired divergence in the group is the one it
|
|
6565
|
-
cannot show, and the key has churned three times under `flag` on additions
|
|
6566
|
-
that changed no verdict. A fourth candidate — the echo detector, scored at
|
|
6567
|
-
half precision — belongs to the tautology-detector entry now under
|
|
6568
|
-
*Considered and rejected* and is not re-quoted here, because a figure stated in two places goes stale in one. Taken together
|
|
6569
|
-
these are the measured range of what a token or single-body detector over
|
|
6570
|
-
prose can do here, and they are why the proposal is enforcement-only.
|
|
6571
|
-
*What blocks the decision — three things.* **(1) One adapter.** This
|
|
6572
|
-
repository's table is the only one that exists, and its author wrote both the
|
|
6573
|
-
schema and every answer in it. By this project's own rule a second table is
|
|
6574
|
-
what makes the seam real, and freezing a `Judgement` type now is deciding for
|
|
6575
|
-
everyone from 34 requirements written by one person. **(2) The breaking form
|
|
6576
|
-
of this was already rejected** — see *Countable obligations* below — and one
|
|
6577
|
-
of its three reasons survives the separate-file shape intact: **nothing can
|
|
6578
|
-
force the count to be true.** The other two do not survive, which is what is
|
|
6579
|
-
new here: a separate artifact touches no `--json` field, no delta apply and no
|
|
6580
|
-
`render` output, and completeness *across ids* is computable in a way
|
|
6581
|
-
completeness *within* an obligation array never was. So this is a re-proposal
|
|
6582
|
-
with one of three objections answered and the sharpest one standing.
|
|
6583
|
-
**(3) `quantifier-unjudged` reuses a rejected detector as its trigger.**
|
|
6584
|
-
Firing on the quantifier token was rejected below at 43% reach and 70% wrong;
|
|
6585
|
-
the claim here is that the same regex is acceptable when it asks a question an
|
|
6586
|
-
author discharges in one line (`over: 'values'`) rather than issuing a
|
|
6587
|
-
verdict. That claim is untested on anyone who did not also write the regex.
|
|
6588
|
-
*The half that is not blocked has shipped, under `[0.9.2]`.*
|
|
6589
|
-
`@am_shork/attest/inspect` is one function answering what a project declares —
|
|
6590
|
-
AST-only, no runner peer, landing on the existing static/executing boundary.
|
|
6591
|
-
It ships no schema and decides nothing, which is why it could go ahead of the
|
|
6592
|
-
three blockers rather than waiting on them. What it changes about this entry
|
|
6593
|
-
is the *kind* of thing that is now missing: the mechanism is no longer
|
|
6594
|
-
unreachable, only unwritten, so a second table is something an adopter can
|
|
6595
|
-
build rather than something only this repository could.
|
|
6596
|
-
*What would decide it:* one judgement table maintained by someone who did not
|
|
6597
|
-
design it. If it comes back the same shape, the seam is real and the schema
|
|
6598
|
-
follows; if it comes back different, `inspect` was the whole answer and the
|
|
6599
|
-
schema never needs to exist. *(One came back on 2026-08-31, and the disjunction
|
|
6600
|
-
above is too clean to survive it: partly the same shape, partly not, and the
|
|
6601
|
-
same half is the contaminated half. The close of this entry is where that is
|
|
6602
|
-
read.)*
|
|
6603
|
-
**Nothing currently makes that table arrive, which is a separate problem from
|
|
6604
|
-
the three blockers and the one that gates all of them.** Raised in a product
|
|
6605
|
-
review of `[0.9.1]`–`[0.9.3]` on 2026-08-19. The deciding evidence is an
|
|
6606
|
-
artifact only an adopter can produce, and the path to producing it is: a row in
|
|
6607
|
-
the README's document table, then a 214-line page, then a hand pass over four
|
|
6608
|
-
questions, then writing the two mechanical properties from a prose
|
|
6609
|
-
description, then choosing to report back. Five voluntary steps with no
|
|
6610
|
-
scaffold at any of them, against a decision this entry has been waiting on
|
|
6611
|
-
since `[0.9.2]`. An entry blocked on evidence that no shipped surface asks for
|
|
6612
|
-
is blocked indefinitely, and the wait is not itself evidence of anything.
|
|
6613
|
-
*The proposal is scaffolding the mechanism while still withholding the answer,*
|
|
6614
|
-
which is the same split `[0.9.2]` already made and not a retreat from it: an
|
|
6615
|
-
`examples/` directory holding the completeness and rot-binding assertions with
|
|
6616
|
-
**no columns** — the enumeration that forces a row, and the check that a named
|
|
6617
|
-
scenario is one `inspectProject` really declares — and the judgement columns
|
|
6618
|
-
left for the adopter to add. What the page withholds is this repository's four
|
|
6619
|
-
columns, and that stays withheld; what it currently also withholds is the two
|
|
6620
|
-
properties it names as *mechanical*, and those are the half where a copy is not
|
|
6621
|
-
an echo, because their content is fixed and the fourth finding does not apply
|
|
6622
|
-
to them.
|
|
6623
|
-
*What blocks it.* Whether a skeleton with no columns is still a shape that
|
|
6624
|
-
comes back as a copy — the page's argument is that a template makes the answer
|
|
6625
|
-
return its own question, and it is untested whether an adopter reads an empty
|
|
6626
|
-
frame as an invitation or as a form to fill in. And it is a second artifact
|
|
6627
|
-
claiming to be runnable consumer usage of the public API, which `pre-commit-check`
|
|
6628
|
-
§6 already records as the category no gate reaches: the page's three samples
|
|
6629
|
-
are unasserted today, and a fourth in a directory that looks executable would
|
|
6630
|
-
be worse than a fenced block, not better, unless something runs it.
|
|
6631
|
-
**Built under `[0.11.0]`, and the second of those two blockers was the
|
|
6632
|
-
buildable one.** `examples/judgement-table/` is in the default suite, the
|
|
6633
|
-
specifier is aliased rather than relativised so the file stays copyable
|
|
6634
|
-
verbatim, and both assertions were shown to fail before it was called done.
|
|
6635
|
-
So the category `pre-commit-check` §6 names now has exactly one member that a
|
|
6636
|
-
gate reaches, and the page's three prose samples are still not it.
|
|
6637
|
-
*Two things the build found that the proposal did not contain.* A third
|
|
6638
|
-
assertion — `issues` empty, before either of the other two — which is not a
|
|
6639
|
-
refinement but a hole: an unread registry file and a smaller project are the
|
|
6640
|
-
same value, and the two completeness directions pass over both. It was found
|
|
6641
|
-
by an invalid mutation during the bite test rather than by reasoning, which is
|
|
6642
|
-
`How an entry is re-measured`'s question about what a repair makes reachable,
|
|
6643
|
-
arriving from the other end. And a **type-level** forcing move the runtime
|
|
6644
|
-
assertion cannot make: a required field added to `Judgement` fails every
|
|
6645
|
-
existing row at compile time, so the schema-shaped half of "a row cannot
|
|
6646
|
-
arrive unjudged" costs one line and no engine rule. That is worth noting
|
|
6647
|
-
against blocker **(1)** above, which is about freezing a `Judgement` type:
|
|
6648
|
-
what shipped freezes nothing, because the type is in the adopter's copy.
|
|
6649
|
-
**What is untouched is the first blocker, and it is now the only thing between
|
|
6650
|
-
this entry and its evidence.** Whether an empty frame reads as an invitation
|
|
6651
|
-
or as a form to fill in is still untested, and cannot be tested here — the
|
|
6652
|
-
people who would answer it are the ones who have not arrived. What the build
|
|
6653
|
-
changes is only that the five voluntary steps are now four, and the one
|
|
6654
|
-
removed was the one with a fixed answer. If the next report is still nobody,
|
|
6655
|
-
the scaffold was not what was missing, and that is worth knowing too.
|
|
6656
|
-
**Decided 2026-08-31: build the counting rule, and not the schema.** The
|
|
6657
|
-
blocker was whether an empty frame reads as an invitation or as a form to fill
|
|
6658
|
-
in, it could not be tested here, and a 0.9.1 → 0.11.0 upgrade report from
|
|
6659
|
-
`mine-capablanca` tested it. The answer is **invitation**, and the report names
|
|
6660
|
-
the sentence that did it: not the empty `interface Judgement`, which reads as a
|
|
6661
|
-
stub and sent the reporter looking for what belonged in it, but the comment
|
|
6662
|
-
above it — *"It ships with no columns, and that is the finished state of this
|
|
6663
|
-
file rather than a gap in it."* Declaring the absence finished is what turns a
|
|
6664
|
-
blank into a question, and it is the cheapest sentence in the example.
|
|
6665
|
-
*The forcing move was one the build found rather than one it proposed.* The
|
|
6666
|
-
instruction to add the first column as a **required** field made all 80 rows
|
|
6667
|
-
stop compiling at once, and there was then no path back to green that did not
|
|
6668
|
-
go through having an opinion about all 80 — *"a form gets partially filled in;
|
|
6669
|
-
a type does not"*. That is the type-level forcing move described above,
|
|
6670
|
-
working on somebody who did not write it.
|
|
6671
|
-
*And the frame leaks in one place, which is a defect in the example rather
|
|
6672
|
-
than in the idea.* `pin` is documented as not a judgement column and is the
|
|
6673
|
-
only concrete field in the interface, so the reporter's first act was to
|
|
6674
|
-
populate it for all 80 rows mechanically — *"there is a field, populate it"*.
|
|
6675
|
-
Defensible, since it makes rot-binding bite for 80 rows instead of none, and
|
|
6676
|
-
exactly the form-filling behaviour the frame exists to prevent. Generating
|
|
6677
|
-
`pin` from `inspectProject` rather than leaving a field to be typed is the fix,
|
|
6678
|
-
and it belongs in the example.
|
|
6679
|
-
*What does **not** follow, and the report is why it does not.* Two of the three
|
|
6680
|
-
columns that came back are the same shape as two of this repository's —
|
|
6681
|
-
`clauses` is `OBLIGATIONS`, `quantifier` is a flattened `QUANTIFIED` — and it
|
|
6682
|
-
is tempting to read that as the seam being real. It cannot be read that way:
|
|
6683
|
-
the reporter names the `attest init` workflow document as prior influence, and
|
|
6684
|
-
that document discusses quantifiers and param pinning at length. So the
|
|
6685
|
-
convergence says **something** transmits those two ideas, and the document is
|
|
6686
|
-
at least as likely a channel as the frame. What is uncontaminated is the third
|
|
6687
|
-
column, `venue`, which has no counterpart here: nine of its rows record a
|
|
6688
|
-
promise held by a Playwright suite Attest's scope does not reach. A column this
|
|
6689
|
-
repository could not have written came back, which is a seam behaving like one.
|
|
6690
|
-
*What to build is smaller than a schema, and the three things the reporter
|
|
6691
|
-
wanted and did not get are its specification.* **(1) A counting rule for
|
|
6692
|
-
"obligation" precise enough that two people count the same.** The report
|
|
6693
|
-
distrusts its own `clauses` column above all the others and says why: two
|
|
6694
|
-
tables that count differently are not comparable, which defeats the point of
|
|
6695
|
-
there being a second one. A counting rule is content that does **not** depend
|
|
6696
|
-
on the adopter — the same category as the two mechanical assertions — and it is
|
|
6697
|
-
currently withheld along with the columns, which is the wrong side of the
|
|
6698
|
-
split. **(2) A way to bind a clause to a scenario rather than a row.**
|
|
6699
|
-
Rot-binding binds a row to *a* scenario; nothing binds the sixth obligation of
|
|
6700
|
-
a six-clause requirement to anything, so a six-clauses-three-scenarios row is a
|
|
6701
|
-
number a reader has to notice instead of an assertion that fails. **(3)
|
|
6702
|
-
Somewhere for a row to say the promise is held where Attest cannot see it.**
|
|
6703
|
-
Nine `ui-node` rows and a 902-line Playwright suite, citable only in prose.
|
|
6704
|
-
*So what is planned is (1) and the `pin` fix, and (2) and (3) explicitly are
|
|
6705
|
-
not.* (1) is a paragraph in the example, costs no engine surface, and is the
|
|
6706
|
-
one thing that makes a second table comparable to the first — without it two
|
|
6707
|
-
tables are two vocabularies and the seam cannot be read either way. (2) and (3)
|
|
6708
|
-
are both new schema, and blocker **(1)** of the proposal above — one adapter —
|
|
6709
|
-
is not answered by a report that is the second adapter's first hour. Two hours
|
|
6710
|
-
of one agent is not two adapters. **The `*.judge.ts` schema stays undecided**,
|
|
6711
|
-
and when it moves it will move on a second table that somebody has maintained
|
|
6712
|
-
rather than built, which is a different measurement from the one that arrived.
|
|
6713
|
-
|
|
6714
6751
|
## Under consideration
|
|
6715
6752
|
|
|
6716
6753
|
Candidates with **no decision yet**, between the two sections either side of it
|
|
@@ -6939,6 +6976,31 @@ have to gather again.
|
|
|
6939
6976
|
*What would move it:* a red `verify` on an adopter's tree whose cause is not a
|
|
6940
6977
|
timeout. Every instance anybody has is this one.
|
|
6941
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
|
+
|
|
6942
7004
|
- **Changing the value of an existing param passes no gate, and for seven of
|
|
6943
7005
|
them there is no record either.** Split out of the tautology entry that
|
|
6944
7006
|
headed this section on 2026-08-18, and which is now under *Considered and
|
|
@@ -7138,6 +7200,124 @@ have to gather again.
|
|
|
7138
7200
|
yet followed a stale instruction and paid for it, and the rejection below is
|
|
7139
7201
|
entitled to that until somebody has.
|
|
7140
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
|
+
|
|
7141
7321
|
The rejection, kept as it was written:
|
|
7142
7322
|
|
|
7143
7323
|
`render --check` is the obvious precedent and is the opposite case. A rendering
|
|
@@ -7260,6 +7440,231 @@ have to gather again.
|
|
|
7260
7440
|
committed. If that count is zero the discipline was already working and the hook
|
|
7261
7441
|
is overhead; if it is not, the number is the case for `init` writing one.
|
|
7262
7442
|
|
|
7443
|
+
- **An outward-facing form of the judgement tables, so an adopter can run this
|
|
7444
|
+
counter-pressure on their own registry.**
|
|
7445
|
+
`tests/intent-rule-candidates.spec.ts` maintains four hand-judged tables over
|
|
7446
|
+
this repo's registry and enforces their completeness. Design §11 records the
|
|
7447
|
+
properties they defend and that nothing enforces any of them; what the tables
|
|
7448
|
+
add is a counter-pressure holding for **one** registry whose authors agreed to
|
|
7449
|
+
maintain it. An adopter got none of it, and the reason was sharper
|
|
7450
|
+
than "nobody wrote it for them": the package exported `requirement`,
|
|
7451
|
+
`scenario`, `defineRequirements`, `delta` and types, and nothing else.
|
|
7452
|
+
`parseSpecs`, `findFiles` and `readRegistrySource` were internal, so an
|
|
7453
|
+
adopter **could not enumerate their own scenario names** — the table's central
|
|
7454
|
+
rot check, that a claimed scenario is one a spec really declares, was not
|
|
7455
|
+
merely unwritten downstream but unreachable. **That half shipped under
|
|
7456
|
+
`[0.9.2]` as `@am_shork/attest/inspect`** and is recorded at the foot of
|
|
7457
|
+
this entry; what stays here is the schema built on top of it, and the three
|
|
7458
|
+
blockers below are untouched by the export.
|
|
7459
|
+
*The shape that makes it shippable at all is a split, not a feature.* The
|
|
7460
|
+
engine can compute which ids exist, which statements match the universal
|
|
7461
|
+
quantifier regex, which params are list-valued, and whether a named scenario
|
|
7462
|
+
is declared — all four already, all statically. It cannot compute how many
|
|
7463
|
+
obligations a requirement carries, what a quantifier ranges over, or whether a
|
|
7464
|
+
scenario iterates a param. So the shippable object is **the enforcement, never
|
|
7465
|
+
the judgement**: an opt-in `*.judge.ts` read by the same static reader and
|
|
7466
|
+
under the same literal-only rule the registry is (it is metadata about
|
|
7467
|
+
requirements under review, so `check` must execute nothing to read it), whose
|
|
7468
|
+
contents are entirely the adopter's. Opting in is creating the file; once it
|
|
7469
|
+
exists, `requirement-unjudged`, `orphan-judgement`, `unbound-site` and
|
|
7470
|
+
`quantifier-unjudged` make it complete and keep it from rotting. The
|
|
7471
|
+
asymmetry is the only way this can exist without Attest asserting a ground
|
|
7472
|
+
truth it refuses to compute (§0), and it is the same asymmetry `never-red`
|
|
7473
|
+
already runs on: the archive gate would owe judgement only for ids a change
|
|
7474
|
+
ADDs, so a brownfield adoption is not blocked wholesale.
|
|
7475
|
+
*One part of the existing table cannot ship in any form.* `DOMAINS.pin` comes
|
|
7476
|
+
in two kinds — a scenario name, checkable against the AST, and a **source-text
|
|
7477
|
+
fragment** grepped out of the spec file. The second is asserting on the
|
|
7478
|
+
wording of somebody's test, which is the `Issue.message` category error under
|
|
7479
|
+
another name. Only the scenario-name kind is exportable, and what that costs
|
|
7480
|
+
is real: the grep is what caught a deleted pin that `toContain` had passed.
|
|
7481
|
+
*Why no detector ships with it, and this is the part that is already
|
|
7482
|
+
measured.* Three rule families were built and scored against the whole corpus
|
|
7483
|
+
(104 entries as of 1.1.0, counting a fixture id once per registry file) before any was
|
|
7484
|
+
written into the engine, which is the order `divergent-param` was originally
|
|
7485
|
+
designed in the wrong way round. Every figure below is a quotation of the
|
|
7486
|
+
suite's inline snapshots; the snapshot is the fresh copy, and this paragraph
|
|
7487
|
+
is only ever as fresh as its last edit.
|
|
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
|
|
7491
|
+
rewards: extra clauses under a single keyword. **(2) The widened variant** —
|
|
7492
|
+
36 correct, but 14 wrong and still 6 missed, and it is silenced by deleting an
|
|
7493
|
+
"and". A rule whose cheapest fix degrades the artifact it protects does not
|
|
7494
|
+
ship, and its precision falls as the registry grows. **(3) `divergent-param`**
|
|
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
|
|
7497
|
+
rather than a matter of naming: the key is built by discarding the evidence of
|
|
7498
|
+
relatedness, so the one genuinely paired divergence in the group is the one it
|
|
7499
|
+
cannot show, and the key has churned three times under `flag` on additions
|
|
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
|
|
7506
|
+
these are the measured range of what a token or single-body detector over
|
|
7507
|
+
prose can do here, and they are why the proposal is enforcement-only.
|
|
7508
|
+
*What blocks the decision — three things.* **(1) One adapter.** This
|
|
7509
|
+
repository's table is the only one that exists, and its author wrote both the
|
|
7510
|
+
schema and every answer in it. By this project's own rule a second table is
|
|
7511
|
+
what makes the seam real, and freezing a `Judgement` type now is deciding for
|
|
7512
|
+
everyone from 34 requirements written by one person. **(2) The breaking form
|
|
7513
|
+
of this was already rejected** — see *Countable obligations* below — and one
|
|
7514
|
+
of its three reasons survives the separate-file shape intact: **nothing can
|
|
7515
|
+
force the count to be true.** The other two do not survive, which is what is
|
|
7516
|
+
new here: a separate artifact touches no `--json` field, no delta apply and no
|
|
7517
|
+
`render` output, and completeness *across ids* is computable in a way
|
|
7518
|
+
completeness *within* an obligation array never was. So this is a re-proposal
|
|
7519
|
+
with one of three objections answered and the sharpest one standing.
|
|
7520
|
+
**(3) `quantifier-unjudged` reuses a rejected detector as its trigger.**
|
|
7521
|
+
Firing on the quantifier token was rejected below at 43% reach and 70% wrong;
|
|
7522
|
+
the claim here is that the same regex is acceptable when it asks a question an
|
|
7523
|
+
author discharges in one line (`over: 'values'`) rather than issuing a
|
|
7524
|
+
verdict. That claim is untested on anyone who did not also write the regex.
|
|
7525
|
+
*The half that is not blocked has shipped, under `[0.9.2]`.*
|
|
7526
|
+
`@am_shork/attest/inspect` is one function answering what a project declares —
|
|
7527
|
+
AST-only, no runner peer, landing on the existing static/executing boundary.
|
|
7528
|
+
It ships no schema and decides nothing, which is why it could go ahead of the
|
|
7529
|
+
three blockers rather than waiting on them. What it changes about this entry
|
|
7530
|
+
is the *kind* of thing that is now missing: the mechanism is no longer
|
|
7531
|
+
unreachable, only unwritten, so a second table is something an adopter can
|
|
7532
|
+
build rather than something only this repository could.
|
|
7533
|
+
*What would decide it:* one judgement table maintained by someone who did not
|
|
7534
|
+
design it. If it comes back the same shape, the seam is real and the schema
|
|
7535
|
+
follows; if it comes back different, `inspect` was the whole answer and the
|
|
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.)*
|
|
7540
|
+
**Nothing currently makes that table arrive, which is a separate problem from
|
|
7541
|
+
the three blockers and the one that gates all of them.** Raised in a product
|
|
7542
|
+
review of `[0.9.1]`–`[0.9.3]` on 2026-08-19. The deciding evidence is an
|
|
7543
|
+
artifact only an adopter can produce, and the path to producing it is: a row in
|
|
7544
|
+
the README's document table, then a 214-line page, then a hand pass over four
|
|
7545
|
+
questions, then writing the two mechanical properties from a prose
|
|
7546
|
+
description, then choosing to report back. Five voluntary steps with no
|
|
7547
|
+
scaffold at any of them, against a decision this entry has been waiting on
|
|
7548
|
+
since `[0.9.2]`. An entry blocked on evidence that no shipped surface asks for
|
|
7549
|
+
is blocked indefinitely, and the wait is not itself evidence of anything.
|
|
7550
|
+
*The proposal is scaffolding the mechanism while still withholding the answer,*
|
|
7551
|
+
which is the same split `[0.9.2]` already made and not a retreat from it: an
|
|
7552
|
+
`examples/` directory holding the completeness and rot-binding assertions with
|
|
7553
|
+
**no columns** — the enumeration that forces a row, and the check that a named
|
|
7554
|
+
scenario is one `inspectProject` really declares — and the judgement columns
|
|
7555
|
+
left for the adopter to add. What the page withholds is this repository's four
|
|
7556
|
+
columns, and that stays withheld; what it currently also withholds is the two
|
|
7557
|
+
properties it names as *mechanical*, and those are the half where a copy is not
|
|
7558
|
+
an echo, because their content is fixed and the fourth finding does not apply
|
|
7559
|
+
to them.
|
|
7560
|
+
*What blocks it.* Whether a skeleton with no columns is still a shape that
|
|
7561
|
+
comes back as a copy — the page's argument is that a template makes the answer
|
|
7562
|
+
return its own question, and it is untested whether an adopter reads an empty
|
|
7563
|
+
frame as an invitation or as a form to fill in. And it is a second artifact
|
|
7564
|
+
claiming to be runnable consumer usage of the public API, which `pre-commit-check`
|
|
7565
|
+
§6 already records as the category no gate reaches: the page's three samples
|
|
7566
|
+
are unasserted today, and a fourth in a directory that looks executable would
|
|
7567
|
+
be worse than a fenced block, not better, unless something runs it.
|
|
7568
|
+
**Built under `[0.11.0]`, and the second of those two blockers was the
|
|
7569
|
+
buildable one.** `examples/judgement-table/` is in the default suite, the
|
|
7570
|
+
specifier is aliased rather than relativised so the file stays copyable
|
|
7571
|
+
verbatim, and both assertions were shown to fail before it was called done.
|
|
7572
|
+
So the category `pre-commit-check` §6 names now has exactly one member that a
|
|
7573
|
+
gate reaches, and the page's three prose samples are still not it.
|
|
7574
|
+
*Two things the build found that the proposal did not contain.* A third
|
|
7575
|
+
assertion — `issues` empty, before either of the other two — which is not a
|
|
7576
|
+
refinement but a hole: an unread registry file and a smaller project are the
|
|
7577
|
+
same value, and the two completeness directions pass over both. It was found
|
|
7578
|
+
by an invalid mutation during the bite test rather than by reasoning, which is
|
|
7579
|
+
`How an entry is re-measured`'s question about what a repair makes reachable,
|
|
7580
|
+
arriving from the other end. And a **type-level** forcing move the runtime
|
|
7581
|
+
assertion cannot make: a required field added to `Judgement` fails every
|
|
7582
|
+
existing row at compile time, so the schema-shaped half of "a row cannot
|
|
7583
|
+
arrive unjudged" costs one line and no engine rule. That is worth noting
|
|
7584
|
+
against blocker **(1)** above, which is about freezing a `Judgement` type:
|
|
7585
|
+
what shipped freezes nothing, because the type is in the adopter's copy.
|
|
7586
|
+
**What is untouched is the first blocker, and it is now the only thing between
|
|
7587
|
+
this entry and its evidence.** Whether an empty frame reads as an invitation
|
|
7588
|
+
or as a form to fill in is still untested, and cannot be tested here — the
|
|
7589
|
+
people who would answer it are the ones who have not arrived. What the build
|
|
7590
|
+
changes is only that the five voluntary steps are now four, and the one
|
|
7591
|
+
removed was the one with a fixed answer. If the next report is still nobody,
|
|
7592
|
+
the scaffold was not what was missing, and that is worth knowing too.
|
|
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.
|
|
7667
|
+
|
|
7263
7668
|
## Considered and rejected
|
|
7264
7669
|
|
|
7265
7670
|
Decisions **not** to build something, kept where they can be found before the
|
|
@@ -9079,6 +9484,7 @@ path the repair makes reachable; it is about **everything the repair would make
|
|
|
9079
9484
|
|
|
9080
9485
|
## Releases
|
|
9081
9486
|
|
|
9487
|
+
[1.1.0]: https://gitlab.com/Pseudorca/attest/-/tags/v1.1.0
|
|
9082
9488
|
[1.0.0]: https://gitlab.com/Pseudorca/attest/-/tags/v1.0.0
|
|
9083
9489
|
[0.11.0]: https://gitlab.com/Pseudorca/attest/-/tags/v0.11.0
|
|
9084
9490
|
[0.10.0]: https://gitlab.com/Pseudorca/attest/-/tags/v0.10.0
|