@am_shork/attest 1.0.0 → 1.1.1
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 +865 -245
- package/README.md +7 -2
- 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/schema.js +1 -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 +6 -3
package/CHANGELOG.md
CHANGED
|
@@ -90,6 +90,391 @@ 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.1] - 2026-09-03
|
|
94
|
+
|
|
95
|
+
### Documentation
|
|
96
|
+
|
|
97
|
+
- **`docs/{en,zh}/intent-quality.md` gains a seventh finding: a param no
|
|
98
|
+
statement interpolates changes with nothing outside the line to show it.**
|
|
99
|
+
`render --check` gives a review surface for the 92.1% of this repository's
|
|
100
|
+
params that a statement does interpolate — editing one moves `SPEC.md`. It
|
|
101
|
+
cannot help with the rest: five are structurally uninterpolatable (a
|
|
102
|
+
kind-to-payload table or a list, which `non-scalar-interpolation` refuses in
|
|
103
|
+
a statement) and two are scalars nobody interpolated. For those, every gate
|
|
104
|
+
treats the edit as fine — `check` sees a schema-valid literal,
|
|
105
|
+
`possible-drift` is satisfied by any covering scenario reading the param at
|
|
106
|
+
all, and `verify` stays green wherever the assertion derives its expectation
|
|
107
|
+
from the same value — so a human reading the registry diff is the only thing
|
|
108
|
+
that catches an unintended move. Split out of the *Under consideration* entry
|
|
109
|
+
on changing an existing param's value, as the cheap half it named while the
|
|
110
|
+
WARNING it also considered stays undecided for want of a second corpus.
|
|
111
|
+
|
|
112
|
+
### Added
|
|
113
|
+
|
|
114
|
+
- **`pre-commit-check` gained §9, a hand sweep for a dependency bump.** Closes
|
|
115
|
+
the *Considered and rejected* entry on a standing `no-deprecated` gate: three
|
|
116
|
+
deprecated call sites across a Zod 3→4, Vitest 3→4 and typescript-eslint
|
|
117
|
+
8.2→8.65 walk was too small a denominator to carry a third tsconfig or an
|
|
118
|
+
`allowJs` admission into the wider typed project, so the check stays a manual
|
|
119
|
+
step — run `@typescript-eslint/no-deprecated` against a throwaway project that
|
|
120
|
+
also covers the five files no tsconfig in this tree includes
|
|
121
|
+
(`eslint.config.js`, `bin/attest.js`, the three `vitest*.config.ts`) — rather
|
|
122
|
+
than a permanent gate.
|
|
123
|
+
|
|
124
|
+
- **A gate on which files inside this tree may name the package by the name it
|
|
125
|
+
publishes under.** A built `dist/` makes this package resolve *itself*:
|
|
126
|
+
`package.json` names the subpaths in `exports`, so `@am_shork/attest/define`
|
|
127
|
+
resolves to `dist/core/registry.js` on any machine that has run a build. CI's
|
|
128
|
+
lint, test and typecheck jobs run before one, and there it resolves nowhere.
|
|
129
|
+
Measured once, on an archived `requirements.delta.ts` written with the
|
|
130
|
+
specifier the workflow document hands *adopters*: it passed `eslint`, both
|
|
131
|
+
typechecks, the whole suite, `check self`, `verify self` and `archive --apply`
|
|
132
|
+
locally — **six gates** — then failed **three CI jobs** on that one line. This
|
|
133
|
+
is the sibling of the `bin/attest.js` loads `dist/` gotcha and the more
|
|
134
|
+
dangerous half of it: there a stale build runs old code, here the build
|
|
135
|
+
vouches for an import that has no other resolution.
|
|
136
|
+
|
|
137
|
+
`tests/import-boundary.spec.ts` now reads the whole checked tree through the
|
|
138
|
+
same AST walk the runner-boundary tests use, and asserts the set of published
|
|
139
|
+
specifiers against a one-entry allowlist. The single legitimate namer is
|
|
140
|
+
`examples/judgement-table/judgement.spec.ts` at `@am_shork/attest/inspect`,
|
|
141
|
+
and it is legitimate for exactly the reason the rule exists: `./inspect` is
|
|
142
|
+
the only subpath with somewhere to resolve *to* inside this tree, via
|
|
143
|
+
`resolve.alias` in `vitest.config.ts` and the matching `paths` entry in
|
|
144
|
+
`tsconfig.typecheck.json`.
|
|
145
|
+
|
|
146
|
+
*Two things about the walk are load-bearing and neither is obvious.* It does
|
|
147
|
+
not use `findFiles`, because `SKIP_DIRS` excludes `changes/` and `archive/` —
|
|
148
|
+
correctly, for a normal scan — while `tsc` and `eslint` are handed `self/**`
|
|
149
|
+
and walk straight into both, and the import this exists for was in an
|
|
150
|
+
*archived* delta; the engine's own scanner would have produced a gate that
|
|
151
|
+
passes over the only file it was written about, which is what the first draft
|
|
152
|
+
of it did. And it covers the repo root as well as the four directories
|
|
153
|
+
`tsconfig.typecheck.json` includes, because `vitest.config.ts`,
|
|
154
|
+
`vitest.self.config.ts` and `vitest.consumer.config.ts` are named by no
|
|
155
|
+
`include` in the tree yet Vitest resolves them **first**, before it can
|
|
156
|
+
collect a single test — a published specifier there fails CI earlier and
|
|
157
|
+
harder than one under `self/`, and the directory-shaped version of this gate
|
|
158
|
+
would not have looked. Each half is pinned by an instrument guard, so a walk
|
|
159
|
+
that quietly stops reading `examples/` or the root fails on the guard rather
|
|
160
|
+
than passing empty.
|
|
161
|
+
|
|
162
|
+
- **A product identity, as delivery artifacts.** `assets/brand/` carries the
|
|
163
|
+
mark, four icon sizes and four lockups as SVG, with `assets/brand/README.md`
|
|
164
|
+
as the guide: the geometry grid, the mortise-and-tenon overlap that must not
|
|
165
|
+
become clearance, the scaling rules, the type stack and the palette. The
|
|
166
|
+
README header and `package.json`'s keywords are the rest of it.
|
|
167
|
+
|
|
168
|
+
**Nothing in `src/` reads any of it, and that is the shape of the change
|
|
169
|
+
rather than a stage it has not reached yet.** A CLI half was built and dropped
|
|
170
|
+
in the same session — a palette module plus a `--help` banner — and the
|
|
171
|
+
argument that closed it is filed under *Considered and rejected*.
|
|
172
|
+
|
|
173
|
+
### Changed
|
|
174
|
+
|
|
175
|
+
- **Three deprecated call sites retired, found by turning
|
|
176
|
+
`@typescript-eslint/no-deprecated` on the whole typed tree rather than by
|
|
177
|
+
reading the diff.** Neither was reachable by the repo's standing gates: both
|
|
178
|
+
compiled, linted and ran green, because a deprecation is a JSDoc tag and
|
|
179
|
+
nothing in `eslint.config.js` was asking for it.
|
|
180
|
+
|
|
181
|
+
`src/core/schema.ts` passed `{ message: … }` to the `statement` refinement.
|
|
182
|
+
Zod 4 deprecated that parameter in favour of `error`, and the rest of the file
|
|
183
|
+
had already moved — `paramValue`, `RegistryRecord` and `RegistrySchema` all
|
|
184
|
+
spell it `error` — so this was the one survivor of an incomplete migration,
|
|
185
|
+
not a considered exception. The wording is unchanged, and by the
|
|
186
|
+
`Issue.message` rule it is prose either way, so nothing observable moved:
|
|
187
|
+
parsed against the built schema, the refusal still comes back with the same
|
|
188
|
+
`message` under the same `custom` code. Design §5.1 quotes this schema in both
|
|
189
|
+
languages and carries the same edit.
|
|
190
|
+
|
|
191
|
+
`tests/registry-types.spec-d.ts` used `expectTypeOf(…).toMatchTypeOf<…>()`,
|
|
192
|
+
deprecated in expect-type 1.2.0 and split into two successors. Both sites
|
|
193
|
+
assert *assignability to a supertype* — `Registry` for the whole registry,
|
|
194
|
+
`Requirement` for one entry — which is `toExtend`, the successor that keeps
|
|
195
|
+
the `extends` relation. `toMatchObjectType` is the other one and would have
|
|
196
|
+
been the wrong reading: it checks a strict subset of keys, which is a
|
|
197
|
+
different claim than the one these two tests were making.
|
|
198
|
+
|
|
199
|
+
`eslint.config.js` called `tseslint.config()`, deprecated in
|
|
200
|
+
typescript-eslint 8.65 now that ESLint core provides the same `extends`
|
|
201
|
+
flattening via `defineConfig()`. Swapped for `defineConfig` + `globalIgnores`
|
|
202
|
+
from `eslint/config`. Because both spellings lint clean, *clean was not
|
|
203
|
+
evidence*: equivalence was established by diffing `eslint --print-config` for
|
|
204
|
+
a file in each of the five trees across the swap — byte-identical — plus the
|
|
205
|
+
linted file set and problem count, 166 and 0 on both sides.
|
|
206
|
+
|
|
207
|
+
**The third one is the interesting one, because the first sweep structurally
|
|
208
|
+
could not see it.** `no-deprecated` is type-aware, so it only reaches files
|
|
209
|
+
some project includes — and `tsconfig.typecheck.json` includes `src`, `tests`,
|
|
210
|
+
`self` and `examples`, which leaves `eslint.config.js`, `bin/attest.js` and
|
|
211
|
+
the three `vitest*.config.ts` named by no `include` in the tree. This is the
|
|
212
|
+
same blind spot `tests/import-boundary.spec.ts` was written about from the
|
|
213
|
+
other direction: the root config files are read *first* by the tooling and
|
|
214
|
+
belong to no project last. Re-run against a throwaway project that includes
|
|
215
|
+
them, the remaining four are clean — and that result was itself checked
|
|
216
|
+
against the failure shape this repo keeps meeting, by re-running the scan on
|
|
217
|
+
the pre-fix `eslint.config.js` and confirming it goes red, since a scan
|
|
218
|
+
matching no files is also silent.
|
|
219
|
+
|
|
220
|
+
With that gap closed — and the installed dependency tree carrying no registry
|
|
221
|
+
deprecation, the adopter-facing samples inside `skill.ts` and every code fence
|
|
222
|
+
in `docs/` and the README checked against the deprecated identifiers harvested
|
|
223
|
+
from the dependency typings, `fixtures/**` scanned under a throwaway project,
|
|
224
|
+
and the suite run under `--pending-deprecation` surfacing no runtime `DEP`
|
|
225
|
+
warning — this is the whole inventory as of Zod 4.4, expect-type 1.4,
|
|
226
|
+
typescript-eslint 8.65, Vitest 4.1, TypeScript 5.9 and Node 24.
|
|
227
|
+
|
|
228
|
+
**Two halves of that are unmeasured rather than clean, and are named here
|
|
229
|
+
rather than left to be inferred from the version list.** `package.json`
|
|
230
|
+
declares `typescript` as `^5.5.0 || ^6.0.0` while 5.9.3 is what is installed,
|
|
231
|
+
so anything TypeScript 6 deprecates has not been looked at; and `engines`
|
|
232
|
+
allows Node 20.19 while the sweep ran on 24, so a `DEP` warning that fires
|
|
233
|
+
only on an older runtime would not have appeared. Nothing gates any of it
|
|
234
|
+
going forward — see *Under consideration*.
|
|
235
|
+
|
|
236
|
+
## [1.1.0] - 2026-09-02
|
|
237
|
+
|
|
238
|
+
### Added
|
|
239
|
+
|
|
240
|
+
- **`ATX-89`, `ATX-90`, `ATX-91`: `check` reports a workflow document the
|
|
241
|
+
installed version no longer writes (`stale-workflow-doc`, INFO).** The
|
|
242
|
+
document `attest init` writes is a pure function of the installed version, and
|
|
243
|
+
the adopter's tree records nothing about which version wrote it —
|
|
244
|
+
`skillContent()` and `workflowBody()` take no argument, deliberately, because
|
|
245
|
+
that is what makes re-running `init` safe. So the file carries no version
|
|
246
|
+
stamp and a diff against the installed build is the only detector its own
|
|
247
|
+
design permits. Measured on a 0.9.1 → 0.11.0 upgrade report: **110 changed
|
|
248
|
+
lines across 9 hunks**, 403 lines against 458, found only because the reporter
|
|
249
|
+
wrote a script importing `workflowBody()` out of `dist/`, with `check`,
|
|
250
|
+
`cover`, `verify` and `render --check` all green.
|
|
251
|
+
*The level is an asymmetry of obligation rather than a severity judgement, and
|
|
252
|
+
that is the whole argument for it.* Keeping the document current is Attest's
|
|
253
|
+
obligation, because Attest is what changes it; taking the update is nobody
|
|
254
|
+
else's, because an adopter is free to run an older workflow, a modified one, or
|
|
255
|
+
none. A finding whose subject the reader owes nothing about is exactly the
|
|
256
|
+
finding that may only inform — so `ok`, the exit code and every gate are
|
|
257
|
+
untouched, and a reader who ignores this forever pays what they pay today.
|
|
258
|
+
*This is not `init --check`*, which stays rejected below. That gate turns red
|
|
259
|
+
on a diff the person who must commit it has no way to review: a regenerated
|
|
260
|
+
`SPEC.md` shows what **you** changed about your promises, and a regenerated
|
|
261
|
+
`SKILL.md` shows what Attest's authors changed. It also fires in CI, where
|
|
262
|
+
nobody can run `init`, blocking whoever pushes next for an upgrade somebody
|
|
263
|
+
else made.
|
|
264
|
+
*Three requirements because there are three ways to be wrong, and this file
|
|
265
|
+
already carves the identical problem up this way for renderings.* `ATX-89` is
|
|
266
|
+
the report; `ATX-90` is that an **absent** document draws nothing, which is the
|
|
267
|
+
one the obvious implementation breaks — `staleIssue` reports a rendering that
|
|
268
|
+
was never generated under its own code, and copying that here would fire on two
|
|
269
|
+
of this repository's own three targets and on the majority of adopters, since
|
|
270
|
+
`init` writes one target by default and nothing obliges a project to run it;
|
|
271
|
+
`ATX-91` is that the comparison is of the document rather than the bytes, which
|
|
272
|
+
is deliberately the **opposite** of `ATX-84` and could not be had by widening
|
|
273
|
+
`ATX-29`. `ATX-84` compares this repository's copy byte-for-byte and may,
|
|
274
|
+
because `.gitattributes` here is committed and pins `eol=lf` — that file is
|
|
275
|
+
Attest's here and nobody's in an adopter's tree, so one rule cannot cover both.
|
|
276
|
+
*What it costs, measured before it was built:* 0.234 ms for all three target
|
|
277
|
+
comparisons, plus 4.19 ms to import `targets.ts` and the 26 KB template behind
|
|
278
|
+
it, against a `check self --json` of 814–1,183 ms — **~0.5%**. No
|
|
279
|
+
`SCHEMA_VERSION` bump: the field exists, the value is new, and `ok` is
|
|
280
|
+
untouched because INFO is below `hasError`.
|
|
281
|
+
*Two things the build found that the proposal did not contain.* The fix hint
|
|
282
|
+
had to name what `init` does to the file and not only the command — `runInit`
|
|
283
|
+
writes with `writeAtomic`, no merge and no backup, so an adopter who edited
|
|
284
|
+
these instructions is being pointed at something destructive. `targets.ts`
|
|
285
|
+
rule 1 makes that defensible, Attest owning the path being the whole reason it
|
|
286
|
+
may overwrite, and it was nowhere stated to the reader. And the two negative
|
|
287
|
+
scenarios had to be **paired with the case that must fire**, in the same
|
|
288
|
+
scenario, because an absence assertion alone is green in a tree where nothing
|
|
289
|
+
is implemented — which is the defect `never-red` exists to name, arriving in
|
|
290
|
+
the scenarios written to attest a rule about not firing.
|
|
291
|
+
*One cost that is not paid and is stated rather than left to be discovered:*
|
|
292
|
+
this repository cannot dogfood it. `check` reads the project root, which here
|
|
293
|
+
is `self/`, while the document sits at the repository root — so `check self`
|
|
294
|
+
finds it absent and is correctly silent. `ATX-84`'s scenario reaches two
|
|
295
|
+
directories up because it is a test; a command cannot. All three scenarios
|
|
296
|
+
build a temporary project, and `self/` gives this rule none of the continuous
|
|
297
|
+
exercise `ATX-84` gets.
|
|
298
|
+
*What the judgement tables did with the three ids, since that is the
|
|
299
|
+
counter-pressure earning its place rather than a chore paid to it.* All four
|
|
300
|
+
demanded rows and got them. `compound-requirement` **missed `ATX-89`** — two
|
|
301
|
+
obligations, "reports it" and "at that level", and the keyword heuristic sees
|
|
302
|
+
one — which is the "never wrong, nearly blind" reading holding at 18 of 42
|
|
303
|
+
rather than a new finding. `divergent-param` produced a **new false positive
|
|
304
|
+
and re-keyed an old one**: `level` joins `code`, `file`, `flag` and `field` as
|
|
305
|
+
a fifth natural word for a kind of constant, and `level:ATX-60,ATX-89` is the
|
|
306
|
+
second key where the two requirements genuinely *are* related — both about the
|
|
307
|
+
severity ladder and the `hasError` predicate — while the rule relates them for
|
|
308
|
+
a reason that is not that. The echo detector flagged both of `ATX-89`'s params
|
|
309
|
+
and both were measured `independent` by mutation, one `verify self` each,
|
|
310
|
+
taking it to 4 true positives of 11.
|
|
311
|
+
- **`ATX-88`: `tests-red` names the timeout the run applied and how many
|
|
312
|
+
scenarios reached it, so a suite that went red on the clock can be told from
|
|
313
|
+
one that went red on its code.** Filed from a 0.9.1 → 0.11.0 upgrade report
|
|
314
|
+
where `npx vitest run` was 170/170 green on the same tree on which `verify`
|
|
315
|
+
was red two runs in three. The standing answer — run the suite yourself under
|
|
316
|
+
your own reporter — is circular in exactly that case, because the
|
|
317
|
+
disagreement between the two runs *is* the failure; what the reporter did
|
|
318
|
+
instead was time every scenario by hand and cross-reference which of eleven
|
|
319
|
+
over four seconds declared no `timeoutMs`. **This is deliberately not the
|
|
320
|
+
pass-through**, which stays open at the foot of this file: carrying the
|
|
321
|
+
child's failures means new fields, a `SCHEMA_VERSION` question and a third
|
|
322
|
+
party's assertion wording in the field consumers are told not to branch on.
|
|
323
|
+
It is the narrow half that is not blocked — the isolated child run drops the
|
|
324
|
+
config that would have raised the timeout, so *did anything run out the clock*
|
|
325
|
+
is the one question a green direct run cannot answer and this one can, and it
|
|
326
|
+
needs nothing the report does not already carry. A count and never a list, for
|
|
327
|
+
the same reason.
|
|
328
|
+
*The number is read from the resolved child config rather than written down,
|
|
329
|
+
and that is the half a constant would have got wrong.* It is the one value
|
|
330
|
+
here Attest does not choose: Vitest's own default when the run is isolated,
|
|
331
|
+
the supplied config's value when `--vitest-config` raised it. `ATX-88.params`
|
|
332
|
+
pins the isolated default at 5000 because two documents quote it in prose —
|
|
333
|
+
the workflow document `init` writes and the troubleshooting page — and until
|
|
334
|
+
now nothing would have failed if a Vitest release moved it.
|
|
335
|
+
*Detected by duration and never by the child's error text.* Vitest does say
|
|
336
|
+
"Test timed out in 5000ms." and matching that would be shorter; it is also the
|
|
337
|
+
category error this project refuses in its own diagnostics, since a message is
|
|
338
|
+
prose in somebody else's package too. Measured rather than assumed: a scenario
|
|
339
|
+
that reaches the ceiling ends `fail` a hair past it — 5015 ms against 5000 —
|
|
340
|
+
while an assertion failure ends `fail` in under a millisecond, so `>=` needs no
|
|
341
|
+
tolerance and a tolerance would start claiming timeouts for slow assertions
|
|
342
|
+
that failed honestly. A missing duration reads as *not* a timeout, which is the
|
|
343
|
+
direction that fails quietly; the other reports every red as a timeout, which
|
|
344
|
+
is a wrong diagnosis printed with confidence.
|
|
345
|
+
*Stated over the run rather than over the two commands that render it*, unlike
|
|
346
|
+
`ATX-51` beside it, and by this project's own rule rather than by preference:
|
|
347
|
+
what `verify` and `archive` add is a sentence in `Issue.message`, which is
|
|
348
|
+
prose and never API, so a scenario asserting it would be asserting wording.
|
|
349
|
+
Additive to `--json` only in the sense that nothing there moved at all — no
|
|
350
|
+
field, no code, no `schemaVersion` question.
|
|
351
|
+
|
|
352
|
+
- **`examples/judgement-table/` states the counting rule, which is the one piece
|
|
353
|
+
of content that decides nothing and without which two tables cannot be
|
|
354
|
+
compared.** Decided under `Planned` on 2026-08-31 on the first table built from
|
|
355
|
+
that example, which distrusted its own obligation column above all its others
|
|
356
|
+
and said why: two tables that count differently are not a second measurement of
|
|
357
|
+
the same thing, they are a second vocabulary. The rule is not a column and
|
|
358
|
+
answers nothing — what a requirement carries is still the adopter's judgement —
|
|
359
|
+
so it sits on the same side of the split as the two mechanical properties, and
|
|
360
|
+
was being withheld along with the answers, which was the wrong side. It fixes
|
|
361
|
+
the test (two clauses are two obligations when an implementation can satisfy
|
|
362
|
+
one completely and fail the other), the six kinds of clause that read as a
|
|
363
|
+
second obligation and are not (scope, criterion, purpose, the other side, a
|
|
364
|
+
consequence, and the states a scenario happens to construct), the
|
|
365
|
+
one-promise-per-emitted-artifact case, and the tiebreak — perturb one half and
|
|
366
|
+
see whether the other half's scenario stays green. Extracted from the labels in
|
|
367
|
+
`tests/intent-rule-candidates.spec.ts` rather than invented, which is why it is
|
|
368
|
+
a description of a rule already in use and not a proposal. It also names what it
|
|
369
|
+
does **not** ask: whether the obligations belong under one id is a different
|
|
370
|
+
question, and keeping the two apart is what lets the count stay a measurement.
|
|
371
|
+
|
|
372
|
+
### Changed
|
|
373
|
+
|
|
374
|
+
- **The example's anchor is no longer a field to be typed, because the first
|
|
375
|
+
table built from that file filled it in for all 80 of its rows mechanically.**
|
|
376
|
+
`Judgement` shipped with an optional `pin`, documented as not a judgement
|
|
377
|
+
column and nevertheless the only concrete field in the interface — and a field
|
|
378
|
+
gets populated. Copying 80 scenario names out of the AST and back into the
|
|
379
|
+
table is not judgement, and it is exactly the form-filling the empty frame
|
|
380
|
+
exists to prevent. `pin` is now out of `Judgement` entirely and sits in a
|
|
381
|
+
sibling `PINS` map, and an anchor on an id declaring fewer than two scenarios is
|
|
382
|
+
**refused**: with one scenario the anchor is a function of the project,
|
|
383
|
+
`inspectProject` already computes it, and the line naming it is transcription
|
|
384
|
+
wearing a judgement's clothes. What survives is the case where the anchor is a
|
|
385
|
+
real choice, which is the case a machine cannot make. *The cost is named rather
|
|
386
|
+
than hidden:* a sole scenario deleted and replaced under a different name is a
|
|
387
|
+
revisit `PINS` will not force. That is what not asking anyone to transcribe
|
|
388
|
+
costs, and it is paid where a rename cannot change which scenario the row meant.
|
|
389
|
+
- **The frame's emptiness is now typed, which the obvious implementation of this
|
|
390
|
+
fix would have left unenforced.** Deleting `pin` leaves `interface Judgement {}`,
|
|
391
|
+
and an empty interface is not an empty row type — it admits `0` and `""`, so
|
|
392
|
+
every row could quietly acquire anything at all while the file claimed to ship
|
|
393
|
+
no fields. `Record<string, never>` is the type that means what the sentence
|
|
394
|
+
says. Caught by `eslint` rather than by reasoning, which is `How an entry is
|
|
395
|
+
re-measured`'s question about what a repair makes reachable arriving one step to
|
|
396
|
+
the side of where it was expected. Measured both directions: a row carrying an
|
|
397
|
+
undeclared field does not compile, and replacing the line with an interface
|
|
398
|
+
holding one **required** column errors on every existing row.
|
|
399
|
+
- **The example's own subject gained a second scenario**, so `EX-1`'s anchor is a
|
|
400
|
+
choice among several rather than a value the machine could have derived — the
|
|
401
|
+
refusal above has nothing to demonstrate on a project where every id declares
|
|
402
|
+
one scenario. It is also the counting rule standing on its own subject: two
|
|
403
|
+
scenarios, one obligation, because "before it is stored" is the criterion for
|
|
404
|
+
"rejected" and not a second promise. All three assertions in the anchor test
|
|
405
|
+
were shown to fail before this was called done — a derivable anchor, a renamed
|
|
406
|
+
scenario, and an anchor with no row.
|
|
407
|
+
|
|
408
|
+
### Documentation
|
|
409
|
+
|
|
410
|
+
- **Three places carried a backstop claim that is now false — `ATX-84`'s
|
|
411
|
+
rationale, `skill.ts`'s header, and design §9 in both languages — and all
|
|
412
|
+
three withdraw it.** The claim is that every mistake a stale workflow
|
|
413
|
+
document can cause is a diagnostic carrying a fix hint the agent corrects
|
|
414
|
+
itself from, and it is one of the two arguments for shipping no freshness gate
|
|
415
|
+
to adopters. An upgrade report measured the counterexample: a
|
|
416
|
+
copy six weeks and one upgrade behind still described `never-red`'s ordering
|
|
417
|
+
rule, which `[0.10.0]` archived as `evidence-not-ordering` because the
|
|
418
|
+
ordering was never in the engine. An agent reading it does something the
|
|
419
|
+
engine does not require — more conservative, not wrong-shaped — so **no issue
|
|
420
|
+
fires at any severity** and there is nothing to correct itself from. The
|
|
421
|
+
backstop covers a stale document producing bad input; it does not cover one
|
|
422
|
+
producing unnecessary work, and the second is what the drift produced.
|
|
423
|
+
*All three, because they are one argument stored three times and this
|
|
424
|
+
repository's whole subject is what happens when those disagree.* Two were
|
|
425
|
+
known from the entry that priced this; the third was found by grepping for the
|
|
426
|
+
claim rather than for the files that discuss it, and design §9 is the copy
|
|
427
|
+
with the widest audience. `ATX-84`'s rationale cites "the decision in
|
|
428
|
+
`skill.ts`", which cites the design — so correcting any one of them alone
|
|
429
|
+
leaves a citation pointing at a document that says something else. The
|
|
430
|
+
workflow document's own bytes are untouched — the claim lives in the module
|
|
431
|
+
header, above `BODY` — so no `init` re-run is owed and `ATX-84` stays green
|
|
432
|
+
byte-for-byte.
|
|
433
|
+
*What does not change is the requirement.* `ATX-84` gates this repository's
|
|
434
|
+
copy and is not adopter-facing, and the surviving objection — that a gate
|
|
435
|
+
charges every adopter a diff they did not ask for — carries it alone. What the
|
|
436
|
+
withdrawal reopens is the adopter-facing question, which is priced under
|
|
437
|
+
`Under consideration` rather than restated in a rationale, since a second copy
|
|
438
|
+
of an argument that is still moving is the failure this entry is about.
|
|
439
|
+
- **The workflow document named one of the two things the isolated child run
|
|
440
|
+
withholds, and the troubleshooting page named both.** Re-measuring this before
|
|
441
|
+
building it is what found that: the ask was filed as "have the workflow
|
|
442
|
+
document say that a scenario near the default needs an explicit `timeoutMs`",
|
|
443
|
+
and that sentence already existed — in `tests-red`'s troubleshooting section,
|
|
444
|
+
worked example included. What did not exist was the pointer. The workflow
|
|
445
|
+
document's own bullet on why a green direct run does not predict the gate lists
|
|
446
|
+
aliases, transforms and a DOM, and stops there; an agent reading it was told
|
|
447
|
+
about the environment and not about the clock, while the section it links to
|
|
448
|
+
covers both. So what was owed was a second bullet rather than a new
|
|
449
|
+
explanation, which is cheaper than the entry priced it.
|
|
450
|
+
- **`tests-red`'s troubleshooting section stops saying the report carries
|
|
451
|
+
nothing about the failure**, in both languages, because it now carries exactly
|
|
452
|
+
one thing. The sentence that had to change is the one that reads as a general
|
|
453
|
+
promise — "not from this report, because it does not carry them" — and the
|
|
454
|
+
worked example beside it says which half crossed and why the other half is a
|
|
455
|
+
decision rather than an omission. History rows on `verify` and `archive` in
|
|
456
|
+
both references, since a reader on 1.0.0 gets the older report.
|
|
457
|
+
- **`CLAUDE.md`'s "it will not tell you why" gotcha gains its exception.** The
|
|
458
|
+
gotcha is still true of the file, the scenario and the assertion; it is no
|
|
459
|
+
longer true of the clock, and a gotcha that overstates itself is how somebody
|
|
460
|
+
stops reading the report.
|
|
461
|
+
|
|
462
|
+
- **`intent-quality.md` step 2 points at the counting rule, and says why that
|
|
463
|
+
one pointer does not contradict the page's own sequencing.** The page asks a
|
|
464
|
+
reader to do step 2 before reading our table, because a table built by someone
|
|
465
|
+
who read ours first is an echo of ours. A rule that fixes the *unit* is the
|
|
466
|
+
opposite case — sharing it is what makes a second table comparable, where
|
|
467
|
+
sharing an answer is what makes it a copy — and a page that withheld both
|
|
468
|
+
without distinguishing them would have been asking for an incomparable
|
|
469
|
+
measurement on principle. Both languages, and a line added to *Read our table
|
|
470
|
+
after step 2, not before* so the exemption is stated where the rule it excepts
|
|
471
|
+
is. The History row is owed for the second reason rather than the first: step 4
|
|
472
|
+
tells a reader to **copy** `examples/judgement-table/`, and that file changed
|
|
473
|
+
under this version, so a copy taken at `[1.0.0]` is no longer the file this
|
|
474
|
+
page describes — which is exactly what that table is for and the kind of row
|
|
475
|
+
easiest to miss, because nothing on the page announces that it hands out a
|
|
476
|
+
file.
|
|
477
|
+
|
|
93
478
|
## [1.0.0] - 2026-09-01
|
|
94
479
|
|
|
95
480
|
### Added
|
|
@@ -6506,211 +6891,6 @@ re-measuring an entry before building it is at the foot of the file, under
|
|
|
6506
6891
|
`How an entry is re-measured` — it applies to all three, and was filed here only
|
|
6507
6892
|
because the first of it was written here.
|
|
6508
6893
|
|
|
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
6894
|
## Under consideration
|
|
6715
6895
|
|
|
6716
6896
|
Candidates with **no decision yet**, between the two sections either side of it
|
|
@@ -6939,6 +7119,31 @@ have to gather again.
|
|
|
6939
7119
|
*What would move it:* a red `verify` on an adopter's tree whose cause is not a
|
|
6940
7120
|
timeout. Every instance anybody has is this one.
|
|
6941
7121
|
|
|
7122
|
+
**Blocker (3)'s two cheap answers are built under `[1.1.0]`, and the
|
|
7123
|
+
entry stays open on the other two.** `ATX-88` has `tests-red` name the timeout
|
|
7124
|
+
that applied and how many scenarios reached it, and the workflow document now
|
|
7125
|
+
says the isolated run drops your `testTimeout` along with the rest of your
|
|
7126
|
+
config. Neither touches (1) or (2): no field was added, no `SCHEMA_VERSION`
|
|
7127
|
+
question was asked, and no assertion text from the child crossed into an
|
|
7128
|
+
`Issue`. What the entry proposes — passing the child's failures through — is
|
|
7129
|
+
unchanged and undecided.
|
|
7130
|
+
*Two things the re-measure corrected, and one is the second cheap answer's
|
|
7131
|
+
whole shape.* The workflow-document line was filed as content to write, and it
|
|
7132
|
+
was already written: `tests-red`'s troubleshooting section carries it, worked
|
|
7133
|
+
example included. What was missing was a pointer from the workflow document's
|
|
7134
|
+
own bullet about why a green direct run does not predict the gate, which lists
|
|
7135
|
+
aliases, transforms and a DOM and stops before the clock. And the first
|
|
7136
|
+
answer's phrasing — "the timeout it **imposed**" — was wrong about who imposes
|
|
7137
|
+
it: Attest sets no timeout, it inherits Vitest's default by isolating, so the
|
|
7138
|
+
number has to be read off the resolved child config rather than stated. That
|
|
7139
|
+
distinction is what makes the answer correct under `--vitest-config` too,
|
|
7140
|
+
where the ceiling is the adopter's.
|
|
7141
|
+
*What this does not do, stated so the entry is not read as smaller than it
|
|
7142
|
+
is.* It answers whether the clock was involved. It does not name the scenario,
|
|
7143
|
+
the file or the assertion, so a red `verify` whose cause is not a timeout is
|
|
7144
|
+
exactly as opaque as it was — which is the case this entry is still waiting on
|
|
7145
|
+
an instance of.
|
|
7146
|
+
|
|
6942
7147
|
- **Changing the value of an existing param passes no gate, and for seven of
|
|
6943
7148
|
them there is no record either.** Split out of the tautology entry that
|
|
6944
7149
|
headed this section on 2026-08-18, and which is now under *Considered and
|
|
@@ -7036,6 +7241,15 @@ have to gather again.
|
|
|
7036
7241
|
`intent-quality.md`, the four things no gate is looking at already being that
|
|
7037
7242
|
page's subject and this being a fifth of the same kind.
|
|
7038
7243
|
|
|
7244
|
+
**The cheap half is written under `[Unreleased]`, and the decision above is
|
|
7245
|
+
unchanged by it.** `docs/{en,zh}/intent-quality.md` now carries a seventh
|
|
7246
|
+
finding: a param no statement interpolates changes with nothing outside the
|
|
7247
|
+
line to show it, so the discipline that catches an unintended move there is a
|
|
7248
|
+
human reading the registry diff. That costs no engine surface and settles
|
|
7249
|
+
nothing this entry is still waiting on — the WARNING in blocker **(2)** is
|
|
7250
|
+
still a rule with no measured population, and this is the fallback the entry
|
|
7251
|
+
itself named for exactly that state, not a smaller version of the rule.
|
|
7252
|
+
|
|
7039
7253
|
- **The intent layer is separable from the repository it describes, and the
|
|
7040
7254
|
verification layer is not — so a third party can audit intent and never
|
|
7041
7255
|
obtain a verdict.** Filed 2026-08-22 from a design evaluation of using Attest
|
|
@@ -7138,6 +7352,124 @@ have to gather again.
|
|
|
7138
7352
|
yet followed a stale instruction and paid for it, and the rejection below is
|
|
7139
7353
|
entitled to that until somebody has.
|
|
7140
7354
|
|
|
7355
|
+
**Priced on 2026-09-02, per `How an entry is re-measured`, and the three
|
|
7356
|
+
questions have answers: `check`, `INFO`, and no — the chore is the exit code
|
|
7357
|
+
rather than the line.**
|
|
7358
|
+
*The re-measure corrected the entry's framing of the detector first.* "A diff
|
|
7359
|
+
against the installed build is the only detector the file's own design
|
|
7360
|
+
permits" reproduces exactly, and what it makes reachable is the opposite of
|
|
7361
|
+
how it reads: the comparison needs no version stamp, because `skillContent()`
|
|
7362
|
+
is already inside the process that would report. Measured against this
|
|
7363
|
+
repository's own copy — byte-equal and document-equal at 26,584 bytes both
|
|
7364
|
+
sides — and against the other two targets, which are **absent**, that being
|
|
7365
|
+
the population the notice has to be right about. Cost is **0.234 ms** per run
|
|
7366
|
+
for all three comparisons plus **4.19 ms** to import `targets.ts` and through
|
|
7367
|
+
it the 26 KB template in `skill.ts`, which `check` does not import today;
|
|
7368
|
+
`check self --json` is 814–1,183 ms over three runs, so the notice is **~0.5%**
|
|
7369
|
+
of the command it rides on.
|
|
7370
|
+
*Which command, and the objection to it is answered by precedent rather than
|
|
7371
|
+
by argument.* `check` is the only command an adopter runs unconditionally and
|
|
7372
|
+
is the one the workflow document tells the agent to run. `status` takes a
|
|
7373
|
+
`<change>`, so it is silent during exactly the brownfield adoption whose
|
|
7374
|
+
document is stalest; `cover` and `render` are narrower; `verify` and `archive`
|
|
7375
|
+
are the expensive verdicts, where a non-verdict line is noise at the worst
|
|
7376
|
+
available price. The objection is that this would be `check`'s first finding
|
|
7377
|
+
that is not about the project's intent layer — and it would not be:
|
|
7378
|
+
`compilerIssue()` is the first statement of `runCheck`, and
|
|
7379
|
+
`compiler-unsupported` is a finding about the *installed environment*, at
|
|
7380
|
+
ERROR. What ATX-16 scopes `check` to is executing nothing, never intent only.
|
|
7381
|
+
*What level, and `WARNING` is wrong for a reason the roster states.* All three
|
|
7382
|
+
existing WARNINGs — `rationale-placeholder`, `requirement-open`,
|
|
7383
|
+
`orphan-from-failed-registry` — are defects in the reader's own intent layer
|
|
7384
|
+
that the static half declines to fail on. A stale document is neither a defect
|
|
7385
|
+
nor theirs. `INFO` has exactly one member, `possible-drift`, and it is this
|
|
7386
|
+
shape precisely: something here may be drifting and nothing can prove it cost
|
|
7387
|
+
you anything — which is also what this entry's own unmeasured half says.
|
|
7388
|
+
`ERROR` is `init --check`, and that stays rejected.
|
|
7389
|
+
*The chore question dissolves at that level, on a fact about when the drift
|
|
7390
|
+
appears.* The rejection's standard is a gate that "turns red on a change the
|
|
7391
|
+
user did not make". Drift appears only when the installed version moves, which
|
|
7392
|
+
is a lockfile edit and is a change they made; a fresh clone at a pinned version
|
|
7393
|
+
compares equal and is silent, so the failure mode that forced `render --check`
|
|
7394
|
+
off byte comparison — calling a fresh clone stale — cannot occur here. What
|
|
7395
|
+
makes a thing a chore is the exit code, not the line, and at INFO there is
|
|
7396
|
+
nothing to clear: a reader who ignores it forever pays what they pay today.
|
|
7397
|
+
*The repair makes one thing reachable that the entry does not contain, which
|
|
7398
|
+
is the question the method exists to ask.* `runInit` writes with `writeAtomic`
|
|
7399
|
+
unconditionally — no merge, no backup — and the notice cannot tell a stale file
|
|
7400
|
+
from an edited one. Today that overwrite happens only when the user chooses to
|
|
7401
|
+
run `init`; a notice prompts them into it on the tool's schedule, so the fix
|
|
7402
|
+
hint has to say that `init` rewrites the file rather than merely naming the
|
|
7403
|
+
command. `targets.ts` rule 1 makes that defensible — Attest owns the path, and
|
|
7404
|
+
that is the whole reason it may overwrite — but defensible and unstated are
|
|
7405
|
+
different things.
|
|
7406
|
+
*And the obvious implementation is wrong twice, both times by copying
|
|
7407
|
+
`staleIssue`, which is the reference implementation this entry points at and
|
|
7408
|
+
therefore the one nobody re-reads.* **(a)** It emits `missing-spec-doc` for an
|
|
7409
|
+
absent file. Here an absent file is a project that never ran `init` — two of
|
|
7410
|
+
this repository's own three targets — and firing on it would be the tool
|
|
7411
|
+
scattering findings about editors nobody uses, which is the argument
|
|
7412
|
+
`DEFAULT_TARGET` already makes. Absent must be silent, so this takes one code
|
|
7413
|
+
and not two. **(b)** `sameDocument` normalises CRLF and has to here, for the
|
|
7414
|
+
reason `render --check` needs it: Attest does not own an adopter's
|
|
7415
|
+
`.gitattributes`. That is the **opposite** of ATX-84, which compares this
|
|
7416
|
+
repository's copy byte-for-byte, and whose rationale already argues why the two
|
|
7417
|
+
must differ. The comparisons are deliberately not the same one.
|
|
7418
|
+
*What the re-measure found that belongs to no proposal: ATX-84's rationale
|
|
7419
|
+
carries the standing version of this argument, and one clause of it is now
|
|
7420
|
+
false.* It records that a gate here does not reverse `skill.ts`'s decision
|
|
7421
|
+
against shipping one, on the grounds that "every mistake an old document can
|
|
7422
|
+
cause is a diagnostic carrying a fix hint the agent corrects itself from".
|
|
7423
|
+
That is the clause the `mine-capablanca` report falsified — the withdrawn
|
|
7424
|
+
`never-red` ordering produces unnecessary work rather than bad input, and
|
|
7425
|
+
nothing fires at any severity. **Withdrawn under `[1.1.0]` on 2026-09-02,
|
|
7426
|
+
in all three places that carried it**, since the correction was owed whether or not
|
|
7427
|
+
the notice ships; what the withdrawal leaves standing is the diff-nobody-asked-for
|
|
7428
|
+
objection alone, which is the argument this entry now has to beat.
|
|
7429
|
+
*One cost the entry did not price: this repository cannot dogfood it.* `check`
|
|
7430
|
+
reads the project root, which here is `self/`, and the document is at the repo
|
|
7431
|
+
root — so `check self` would find the file absent and be correctly silent.
|
|
7432
|
+
ATX-84's scenario reaches two directories up because it is a test; a command
|
|
7433
|
+
cannot. The scenario would land on a fixture, and `self/` would give the notice
|
|
7434
|
+
none of the continuous exercise ATX-84's rationale calls the only detector this
|
|
7435
|
+
document has.
|
|
7436
|
+
*What it does not need, priced because the entries around it spend most of
|
|
7437
|
+
their length on exactly this:* no `SCHEMA_VERSION` bump — the field exists, the
|
|
7438
|
+
value is new, and `ok` is untouched because INFO is below `hasError`, the same
|
|
7439
|
+
reading that has held the version at 1 across all 41 codes. One roster entry
|
|
7440
|
+
and one troubleshooting section per language, both made mandatory by
|
|
7441
|
+
`docs-consistency.spec.ts` rather than by remembering.
|
|
7442
|
+
*The one form still worth arguing is the cheapest one, and it loses on the
|
|
7443
|
+
exposure's own shape.* A dim line after the report — `init`'s own "Re-run
|
|
7444
|
+
`attest init` after upgrading", moved to where it is actionable — needs no
|
|
7445
|
+
code, no roster entry and no section in two languages. It is also invisible
|
|
7446
|
+
under `--json`, and the drift in that report was found by a reader writing a
|
|
7447
|
+
script against `dist/`. A detector a machine cannot read reproduces the
|
|
7448
|
+
invisibility it exists to remove.
|
|
7449
|
+
|
|
7450
|
+
**Built under `[1.1.0]` on 2026-09-02, and the entry stays here because
|
|
7451
|
+
what was built is not what it names.** The priced form shipped exactly as
|
|
7452
|
+
priced — `check`, INFO, one code, silent on an absent file, document rather
|
|
7453
|
+
than bytes — as `ATX-89`, `ATX-90` and `ATX-91`. The re-measure corrected none
|
|
7454
|
+
of the three answers, which is worth recording against the two cases in `How
|
|
7455
|
+
an entry is re-measured` where it corrected an entry outright.
|
|
7456
|
+
*What the build added that the pricing did not have is the argument the
|
|
7457
|
+
approver gave, and it is better than the one written here.* This entry argues
|
|
7458
|
+
the level from the negative — a gate is a chore, so do not gate. The reason
|
|
7459
|
+
actually adopted is positive and is an **asymmetry of obligation**: keeping
|
|
7460
|
+
this document current is Attest's obligation, because Attest is what changes
|
|
7461
|
+
it, and taking the update is nobody else's. A finding whose subject the reader
|
|
7462
|
+
owes nothing about is exactly the finding that may only inform. That reaches
|
|
7463
|
+
the same level by a route that does not depend on calling anything a chore,
|
|
7464
|
+
and it is what `ATX-89`'s rationale carries.
|
|
7465
|
+
*What is left of this entry is the gate alone, and it is unchanged.* A notice
|
|
7466
|
+
is not a weaker gate, it is a different artifact: it can be ignored, and by a
|
|
7467
|
+
CI-only adopter who never reads `check` output it certainly will be. That is
|
|
7468
|
+
the cost of the choice rather than an oversight — the trade taken was "never
|
|
7469
|
+
blocks anyone" against "guaranteed to be seen". The thing that would move the
|
|
7470
|
+
gate is what it always was, and the notice does not supply it: an adopter who
|
|
7471
|
+
followed a stale instruction and paid for it. Nobody has.
|
|
7472
|
+
|
|
7141
7473
|
The rejection, kept as it was written:
|
|
7142
7474
|
|
|
7143
7475
|
`render --check` is the obvious precedent and is the opposite case. A rendering
|
|
@@ -7219,46 +7551,230 @@ have to gather again.
|
|
|
7219
7551
|
cost somebody a round trip. Without the first, this trades a derivable field for
|
|
7220
7552
|
an underivable duplicate.
|
|
7221
7553
|
|
|
7222
|
-
- **
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
*
|
|
7239
|
-
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
7257
|
-
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
|
|
7554
|
+
- **An outward-facing form of the judgement tables, so an adopter can run this
|
|
7555
|
+
counter-pressure on their own registry.**
|
|
7556
|
+
`tests/intent-rule-candidates.spec.ts` maintains four hand-judged tables over
|
|
7557
|
+
this repo's registry and enforces their completeness. Design §11 records the
|
|
7558
|
+
properties they defend and that nothing enforces any of them; what the tables
|
|
7559
|
+
add is a counter-pressure holding for **one** registry whose authors agreed to
|
|
7560
|
+
maintain it. An adopter got none of it, and the reason was sharper
|
|
7561
|
+
than "nobody wrote it for them": the package exported `requirement`,
|
|
7562
|
+
`scenario`, `defineRequirements`, `delta` and types, and nothing else.
|
|
7563
|
+
`parseSpecs`, `findFiles` and `readRegistrySource` were internal, so an
|
|
7564
|
+
adopter **could not enumerate their own scenario names** — the table's central
|
|
7565
|
+
rot check, that a claimed scenario is one a spec really declares, was not
|
|
7566
|
+
merely unwritten downstream but unreachable. **That half shipped under
|
|
7567
|
+
`[0.9.2]` as `@am_shork/attest/inspect`** and is recorded at the foot of
|
|
7568
|
+
this entry; what stays here is the schema built on top of it, and the three
|
|
7569
|
+
blockers below are untouched by the export.
|
|
7570
|
+
*The shape that makes it shippable at all is a split, not a feature.* The
|
|
7571
|
+
engine can compute which ids exist, which statements match the universal
|
|
7572
|
+
quantifier regex, which params are list-valued, and whether a named scenario
|
|
7573
|
+
is declared — all four already, all statically. It cannot compute how many
|
|
7574
|
+
obligations a requirement carries, what a quantifier ranges over, or whether a
|
|
7575
|
+
scenario iterates a param. So the shippable object is **the enforcement, never
|
|
7576
|
+
the judgement**: an opt-in `*.judge.ts` read by the same static reader and
|
|
7577
|
+
under the same literal-only rule the registry is (it is metadata about
|
|
7578
|
+
requirements under review, so `check` must execute nothing to read it), whose
|
|
7579
|
+
contents are entirely the adopter's. Opting in is creating the file; once it
|
|
7580
|
+
exists, `requirement-unjudged`, `orphan-judgement`, `unbound-site` and
|
|
7581
|
+
`quantifier-unjudged` make it complete and keep it from rotting. The
|
|
7582
|
+
asymmetry is the only way this can exist without Attest asserting a ground
|
|
7583
|
+
truth it refuses to compute (§0), and it is the same asymmetry `never-red`
|
|
7584
|
+
already runs on: the archive gate would owe judgement only for ids a change
|
|
7585
|
+
ADDs, so a brownfield adoption is not blocked wholesale.
|
|
7586
|
+
*One part of the existing table cannot ship in any form.* `DOMAINS.pin` comes
|
|
7587
|
+
in two kinds — a scenario name, checkable against the AST, and a **source-text
|
|
7588
|
+
fragment** grepped out of the spec file. The second is asserting on the
|
|
7589
|
+
wording of somebody's test, which is the `Issue.message` category error under
|
|
7590
|
+
another name. Only the scenario-name kind is exportable, and what that costs
|
|
7591
|
+
is real: the grep is what caught a deleted pin that `toContain` had passed.
|
|
7592
|
+
*Why no detector ships with it, and this is the part that is already
|
|
7593
|
+
measured.* Three rule families were built and scored against the whole corpus
|
|
7594
|
+
(104 entries as of 1.1.0, counting a fixture id once per registry file) before any was
|
|
7595
|
+
written into the engine, which is the order `divergent-param` was originally
|
|
7596
|
+
designed in the wrong way round. Every figure below is a quotation of the
|
|
7597
|
+
suite's inline snapshots; the snapshot is the fresh copy, and this paragraph
|
|
7598
|
+
is only ever as fresh as its last edit.
|
|
7599
|
+
**(1) `compound-requirement`** — 42 requirements carry more than one
|
|
7600
|
+
obligation, it flags 18, is **wrong about none**, and misses 24. Never wrong
|
|
7601
|
+
and nearly blind, and the 24 are the form the coverage incentive actually
|
|
7602
|
+
rewards: extra clauses under a single keyword. **(2) The widened variant** —
|
|
7603
|
+
36 correct, but 14 wrong and still 6 missed, and it is silenced by deleting an
|
|
7604
|
+
"and". A rule whose cheapest fix degrades the artifact it protects does not
|
|
7605
|
+
ship, and its precision falls as the registry grows. **(3) `divergent-param`**
|
|
7606
|
+
— 9 divergences on this repo, **all 9 judged wrong**, zero true positives ever
|
|
7607
|
+
recorded. The `code` group now spans 22 ids, and the failure is structural
|
|
7608
|
+
rather than a matter of naming: the key is built by discarding the evidence of
|
|
7609
|
+
relatedness, so the one genuinely paired divergence in the group is the one it
|
|
7610
|
+
cannot show, and the key has churned three times under `flag` on additions
|
|
7611
|
+
that changed no verdict, and once more under `code` at 1.1.0. `level` arriving
|
|
7612
|
+
there as a fifth param name is the second key whose two requirements genuinely
|
|
7613
|
+
*are* related — `ATX-60` and `ATX-89` are both about the severity ladder — and
|
|
7614
|
+
the rule still relates them by the one thing that is not the relation. A fourth candidate — the echo detector, scored at
|
|
7615
|
+
half precision — belongs to the tautology-detector entry now under
|
|
7616
|
+
*Considered and rejected* and is not re-quoted here, because a figure stated in two places goes stale in one. Taken together
|
|
7617
|
+
these are the measured range of what a token or single-body detector over
|
|
7618
|
+
prose can do here, and they are why the proposal is enforcement-only.
|
|
7619
|
+
*What blocks the decision — three things.* **(1) One adapter.** This
|
|
7620
|
+
repository's table is the only one that exists, and its author wrote both the
|
|
7621
|
+
schema and every answer in it. By this project's own rule a second table is
|
|
7622
|
+
what makes the seam real, and freezing a `Judgement` type now is deciding for
|
|
7623
|
+
everyone from 34 requirements written by one person. **(2) The breaking form
|
|
7624
|
+
of this was already rejected** — see *Countable obligations* below — and one
|
|
7625
|
+
of its three reasons survives the separate-file shape intact: **nothing can
|
|
7626
|
+
force the count to be true.** The other two do not survive, which is what is
|
|
7627
|
+
new here: a separate artifact touches no `--json` field, no delta apply and no
|
|
7628
|
+
`render` output, and completeness *across ids* is computable in a way
|
|
7629
|
+
completeness *within* an obligation array never was. So this is a re-proposal
|
|
7630
|
+
with one of three objections answered and the sharpest one standing.
|
|
7631
|
+
**(3) `quantifier-unjudged` reuses a rejected detector as its trigger.**
|
|
7632
|
+
Firing on the quantifier token was rejected below at 43% reach and 70% wrong;
|
|
7633
|
+
the claim here is that the same regex is acceptable when it asks a question an
|
|
7634
|
+
author discharges in one line (`over: 'values'`) rather than issuing a
|
|
7635
|
+
verdict. That claim is untested on anyone who did not also write the regex.
|
|
7636
|
+
*The half that is not blocked has shipped, under `[0.9.2]`.*
|
|
7637
|
+
`@am_shork/attest/inspect` is one function answering what a project declares —
|
|
7638
|
+
AST-only, no runner peer, landing on the existing static/executing boundary.
|
|
7639
|
+
It ships no schema and decides nothing, which is why it could go ahead of the
|
|
7640
|
+
three blockers rather than waiting on them. What it changes about this entry
|
|
7641
|
+
is the *kind* of thing that is now missing: the mechanism is no longer
|
|
7642
|
+
unreachable, only unwritten, so a second table is something an adopter can
|
|
7643
|
+
build rather than something only this repository could.
|
|
7644
|
+
*What would decide it:* one judgement table maintained by someone who did not
|
|
7645
|
+
design it. If it comes back the same shape, the seam is real and the schema
|
|
7646
|
+
follows; if it comes back different, `inspect` was the whole answer and the
|
|
7647
|
+
schema never needs to exist. *(One came back on 2026-08-31, and the disjunction
|
|
7648
|
+
above is too clean to survive it: partly the same shape, partly not, and the
|
|
7649
|
+
same half is the contaminated half. The close of this entry is where that is
|
|
7650
|
+
read.)*
|
|
7651
|
+
**Nothing currently makes that table arrive, which is a separate problem from
|
|
7652
|
+
the three blockers and the one that gates all of them.** Raised in a product
|
|
7653
|
+
review of `[0.9.1]`–`[0.9.3]` on 2026-08-19. The deciding evidence is an
|
|
7654
|
+
artifact only an adopter can produce, and the path to producing it is: a row in
|
|
7655
|
+
the README's document table, then a 214-line page, then a hand pass over four
|
|
7656
|
+
questions, then writing the two mechanical properties from a prose
|
|
7657
|
+
description, then choosing to report back. Five voluntary steps with no
|
|
7658
|
+
scaffold at any of them, against a decision this entry has been waiting on
|
|
7659
|
+
since `[0.9.2]`. An entry blocked on evidence that no shipped surface asks for
|
|
7660
|
+
is blocked indefinitely, and the wait is not itself evidence of anything.
|
|
7661
|
+
*The proposal is scaffolding the mechanism while still withholding the answer,*
|
|
7662
|
+
which is the same split `[0.9.2]` already made and not a retreat from it: an
|
|
7663
|
+
`examples/` directory holding the completeness and rot-binding assertions with
|
|
7664
|
+
**no columns** — the enumeration that forces a row, and the check that a named
|
|
7665
|
+
scenario is one `inspectProject` really declares — and the judgement columns
|
|
7666
|
+
left for the adopter to add. What the page withholds is this repository's four
|
|
7667
|
+
columns, and that stays withheld; what it currently also withholds is the two
|
|
7668
|
+
properties it names as *mechanical*, and those are the half where a copy is not
|
|
7669
|
+
an echo, because their content is fixed and the fourth finding does not apply
|
|
7670
|
+
to them.
|
|
7671
|
+
*What blocks it.* Whether a skeleton with no columns is still a shape that
|
|
7672
|
+
comes back as a copy — the page's argument is that a template makes the answer
|
|
7673
|
+
return its own question, and it is untested whether an adopter reads an empty
|
|
7674
|
+
frame as an invitation or as a form to fill in. And it is a second artifact
|
|
7675
|
+
claiming to be runnable consumer usage of the public API, which `pre-commit-check`
|
|
7676
|
+
§6 already records as the category no gate reaches: the page's three samples
|
|
7677
|
+
are unasserted today, and a fourth in a directory that looks executable would
|
|
7678
|
+
be worse than a fenced block, not better, unless something runs it.
|
|
7679
|
+
**Built under `[0.11.0]`, and the second of those two blockers was the
|
|
7680
|
+
buildable one.** `examples/judgement-table/` is in the default suite, the
|
|
7681
|
+
specifier is aliased rather than relativised so the file stays copyable
|
|
7682
|
+
verbatim, and both assertions were shown to fail before it was called done.
|
|
7683
|
+
So the category `pre-commit-check` §6 names now has exactly one member that a
|
|
7684
|
+
gate reaches, and the page's three prose samples are still not it.
|
|
7685
|
+
*Two things the build found that the proposal did not contain.* A third
|
|
7686
|
+
assertion — `issues` empty, before either of the other two — which is not a
|
|
7687
|
+
refinement but a hole: an unread registry file and a smaller project are the
|
|
7688
|
+
same value, and the two completeness directions pass over both. It was found
|
|
7689
|
+
by an invalid mutation during the bite test rather than by reasoning, which is
|
|
7690
|
+
`How an entry is re-measured`'s question about what a repair makes reachable,
|
|
7691
|
+
arriving from the other end. And a **type-level** forcing move the runtime
|
|
7692
|
+
assertion cannot make: a required field added to `Judgement` fails every
|
|
7693
|
+
existing row at compile time, so the schema-shaped half of "a row cannot
|
|
7694
|
+
arrive unjudged" costs one line and no engine rule. That is worth noting
|
|
7695
|
+
against blocker **(1)** above, which is about freezing a `Judgement` type:
|
|
7696
|
+
what shipped freezes nothing, because the type is in the adopter's copy.
|
|
7697
|
+
**What is untouched is the first blocker, and it is now the only thing between
|
|
7698
|
+
this entry and its evidence.** Whether an empty frame reads as an invitation
|
|
7699
|
+
or as a form to fill in is still untested, and cannot be tested here — the
|
|
7700
|
+
people who would answer it are the ones who have not arrived. What the build
|
|
7701
|
+
changes is only that the five voluntary steps are now four, and the one
|
|
7702
|
+
removed was the one with a fixed answer. If the next report is still nobody,
|
|
7703
|
+
the scaffold was not what was missing, and that is worth knowing too.
|
|
7704
|
+
**Decided 2026-08-31: build the counting rule, and not the schema.** The
|
|
7705
|
+
blocker was whether an empty frame reads as an invitation or as a form to fill
|
|
7706
|
+
in, it could not be tested here, and a 0.9.1 → 0.11.0 upgrade report from
|
|
7707
|
+
`mine-capablanca` tested it. The answer is **invitation**, and the report names
|
|
7708
|
+
the sentence that did it: not the empty `interface Judgement`, which reads as a
|
|
7709
|
+
stub and sent the reporter looking for what belonged in it, but the comment
|
|
7710
|
+
above it — *"It ships with no columns, and that is the finished state of this
|
|
7711
|
+
file rather than a gap in it."* Declaring the absence finished is what turns a
|
|
7712
|
+
blank into a question, and it is the cheapest sentence in the example.
|
|
7713
|
+
*The forcing move was one the build found rather than one it proposed.* The
|
|
7714
|
+
instruction to add the first column as a **required** field made all 80 rows
|
|
7715
|
+
stop compiling at once, and there was then no path back to green that did not
|
|
7716
|
+
go through having an opinion about all 80 — *"a form gets partially filled in;
|
|
7717
|
+
a type does not"*. That is the type-level forcing move described above,
|
|
7718
|
+
working on somebody who did not write it.
|
|
7719
|
+
*And the frame leaks in one place, which is a defect in the example rather
|
|
7720
|
+
than in the idea.* `pin` is documented as not a judgement column and is the
|
|
7721
|
+
only concrete field in the interface, so the reporter's first act was to
|
|
7722
|
+
populate it for all 80 rows mechanically — *"there is a field, populate it"*.
|
|
7723
|
+
Defensible, since it makes rot-binding bite for 80 rows instead of none, and
|
|
7724
|
+
exactly the form-filling behaviour the frame exists to prevent. Generating
|
|
7725
|
+
`pin` from `inspectProject` rather than leaving a field to be typed is the fix,
|
|
7726
|
+
and it belongs in the example.
|
|
7727
|
+
*What does **not** follow, and the report is why it does not.* Two of the three
|
|
7728
|
+
columns that came back are the same shape as two of this repository's —
|
|
7729
|
+
`clauses` is `OBLIGATIONS`, `quantifier` is a flattened `QUANTIFIED` — and it
|
|
7730
|
+
is tempting to read that as the seam being real. It cannot be read that way:
|
|
7731
|
+
the reporter names the `attest init` workflow document as prior influence, and
|
|
7732
|
+
that document discusses quantifiers and param pinning at length. So the
|
|
7733
|
+
convergence says **something** transmits those two ideas, and the document is
|
|
7734
|
+
at least as likely a channel as the frame. What is uncontaminated is the third
|
|
7735
|
+
column, `venue`, which has no counterpart here: nine of its rows record a
|
|
7736
|
+
promise held by a Playwright suite Attest's scope does not reach. A column this
|
|
7737
|
+
repository could not have written came back, which is a seam behaving like one.
|
|
7738
|
+
*What to build is smaller than a schema, and the three things the reporter
|
|
7739
|
+
wanted and did not get are its specification.* **(1) A counting rule for
|
|
7740
|
+
"obligation" precise enough that two people count the same.** The report
|
|
7741
|
+
distrusts its own `clauses` column above all the others and says why: two
|
|
7742
|
+
tables that count differently are not comparable, which defeats the point of
|
|
7743
|
+
there being a second one. A counting rule is content that does **not** depend
|
|
7744
|
+
on the adopter — the same category as the two mechanical assertions — and it is
|
|
7745
|
+
currently withheld along with the columns, which is the wrong side of the
|
|
7746
|
+
split. **(2) A way to bind a clause to a scenario rather than a row.**
|
|
7747
|
+
Rot-binding binds a row to *a* scenario; nothing binds the sixth obligation of
|
|
7748
|
+
a six-clause requirement to anything, so a six-clauses-three-scenarios row is a
|
|
7749
|
+
number a reader has to notice instead of an assertion that fails. **(3)
|
|
7750
|
+
Somewhere for a row to say the promise is held where Attest cannot see it.**
|
|
7751
|
+
Nine `ui-node` rows and a 902-line Playwright suite, citable only in prose.
|
|
7752
|
+
*So what is planned is (1) and the `pin` fix, and (2) and (3) explicitly are
|
|
7753
|
+
not.* (1) is a paragraph in the example, costs no engine surface, and is the
|
|
7754
|
+
one thing that makes a second table comparable to the first — without it two
|
|
7755
|
+
tables are two vocabularies and the seam cannot be read either way. (2) and (3)
|
|
7756
|
+
are both new schema, and blocker **(1)** of the proposal above — one adapter —
|
|
7757
|
+
is not answered by a report that is the second adapter's first hour. Two hours
|
|
7758
|
+
of one agent is not two adapters. **The `*.judge.ts` schema stays undecided**,
|
|
7759
|
+
and when it moves it will move on a second table that somebody has maintained
|
|
7760
|
+
rather than built, which is a different measurement from the one that arrived.
|
|
7761
|
+
**Both are built under `[1.1.0]`, and moving here on 2026-09-01 is what
|
|
7762
|
+
follows from that.** The counting rule and the anchor fix are the whole of what
|
|
7763
|
+
was decided on 2026-08-31, and they are recorded there. The re-measure before
|
|
7764
|
+
building corrected nothing this entry claims — the report's three asks
|
|
7765
|
+
reproduce as filed — and what it added is the finding the two cases in `How an
|
|
7766
|
+
entry is re-measured` would predict: the obvious implementation of "ship no
|
|
7767
|
+
fields" is an empty interface, and an empty interface admits `0` and `""`, so
|
|
7768
|
+
the frame would have gone on claiming an emptiness its own type did not hold.
|
|
7769
|
+
*The move is the entry's own state changing, not bookkeeping.* `Planned` means
|
|
7770
|
+
decided and unbuilt, and nothing left here is that: the schema, the
|
|
7771
|
+
clause-to-scenario binding and the out-of-scope `venue` column are all waiting
|
|
7772
|
+
on a second table somebody has maintained, which is the definition of this
|
|
7773
|
+
section rather than the one above. What it costs is that `Planned` is empty
|
|
7774
|
+
again — the state that moved `How an entry is re-measured` out of it, so that
|
|
7775
|
+
the method would not go unreachable whenever this happened — and that is the
|
|
7776
|
+
honest reading of where the project stands rather than a gap in the file:
|
|
7777
|
+
nothing is currently decided and unbuilt.
|
|
7262
7778
|
|
|
7263
7779
|
## Considered and rejected
|
|
7264
7780
|
|
|
@@ -7270,6 +7786,68 @@ it sat between 0.2.0 and 0.1.7 for two releases, where standing still meant
|
|
|
7270
7786
|
sinking one version deeper each time a release was cut above it, and a rejection
|
|
7271
7787
|
filed under a version reads as belonging to it.
|
|
7272
7788
|
|
|
7789
|
+
### A brand palette and banner in the CLI
|
|
7790
|
+
|
|
7791
|
+
Built, measured green on every gate, and dropped 2026-09-03 in the session that
|
|
7792
|
+
built it. Filed here rather than deleted, because the half that was wrong is not
|
|
7793
|
+
the half that looks wrong, and the next person to propose "let's put the brand
|
|
7794
|
+
colours in the CLI" should reach this before rebuilding it.
|
|
7795
|
+
|
|
7796
|
+
**What it was.** `src/cli/brand.ts`: one module holding the palette behind a
|
|
7797
|
+
semantic interface — `pass`, `drift`, `muted`, `note`, `id`, plus the three
|
|
7798
|
+
marks — with `cli/report.ts` and `cli/index.ts` rewritten onto it so `chalk` was
|
|
7799
|
+
imported in exactly one place. Attest Green for a passing verdict, Drift Orange
|
|
7800
|
+
for a WARNING, Slate for every secondary line, and a four-row ASCII mark above
|
|
7801
|
+
`--help` on a TTY.
|
|
7802
|
+
|
|
7803
|
+
**The deletion test passed, which is why this is worth recording.** Delete the
|
|
7804
|
+
module and the same seven colour decisions reappear across two files and thirty
|
|
7805
|
+
call sites, spelled slightly differently each time — that is not a pass-through,
|
|
7806
|
+
and it is the ordinary reason this repository consolidates something. The gates
|
|
7807
|
+
agreed: lint, both typechecks, the full suite, `check self`, `verify self` and
|
|
7808
|
+
`render --check` were all green, and `attest --help` through a pipe was
|
|
7809
|
+
byte-identical, so `self/cli-surface.spec.ts` saw nothing.
|
|
7810
|
+
|
|
7811
|
+
**What it could not answer is who the interface is for.** A module earns its
|
|
7812
|
+
keep by having callers who vary across it, and this one had two callers that
|
|
7813
|
+
never will: there is no second theme, no `--color` flag, no second renderer, and
|
|
7814
|
+
nothing in the roadmap proposes one. *One adapter is a hypothetical seam.* The
|
|
7815
|
+
identity was consolidated at a place where nothing varies, which buys a
|
|
7816
|
+
re-tunability nobody has asked to use, and every colour in it is invisible to
|
|
7817
|
+
every test in the tree — they strip SGR before asserting, correctly, because
|
|
7818
|
+
`Issue.message` is prose and so is its colour. So the module's whole surface is
|
|
7819
|
+
unassertable: **the interface is the test surface, and this interface had none.**
|
|
7820
|
+
|
|
7821
|
+
**Three findings survive the rejection, and they are the reason it is written
|
|
7822
|
+
out.** They cost a session to learn and none of them is recoverable from the
|
|
7823
|
+
diff, which no longer exists.
|
|
7824
|
+
|
|
7825
|
+
- *Frost (`#E6EAF2`) can never be written by a CLI.* The guide defines it as
|
|
7826
|
+
"text on Void", and a terminal's background belongs to the reader — Frost on a
|
|
7827
|
+
light profile is invisible. The terminal's own foreground already plays that
|
|
7828
|
+
role. The same argument retires Void, Surface and Paper: a CLI that paints its
|
|
7829
|
+
own ground fights the profile the reader picked.
|
|
7830
|
+
- *Drift Orange cannot become the ERROR colour.* The palette has no failure
|
|
7831
|
+
colour, and the guide makes Drift Orange's scarcity part of what the product
|
|
7832
|
+
means — it appears when something is wrong. Spending it on every ERROR spends
|
|
7833
|
+
the meaning. The terminal's red is not a placeholder for a brand colour; it is
|
|
7834
|
+
the right answer.
|
|
7835
|
+
- *A monospace mark must be ASCII, and the reason is width, not encoding.*
|
|
7836
|
+
`█`, `╱`, `╲` and `═` are all East Asian **ambiguous** width, so a CJK
|
|
7837
|
+
terminal — which is where this repository is read — renders them two cells
|
|
7838
|
+
wide, and mixing them with a one-cell `=` or space tears a multi-row figure
|
|
7839
|
+
apart. The CLI's existing `✓`, `✗` and `●` are ambiguous too and survive it
|
|
7840
|
+
only because each is alone on its line with nothing aligned under it. This
|
|
7841
|
+
generalises past branding: it applies to any table, tree or rule the CLI ever
|
|
7842
|
+
draws.
|
|
7843
|
+
|
|
7844
|
+
*One thing found on the way is not a branding decision and does not belong to
|
|
7845
|
+
this rejection*: `chalk.dim` is SGR 2, which some profiles render at 40% opacity
|
|
7846
|
+
and others ignore outright, so the same secondary line is either unreadable or
|
|
7847
|
+
undifferentiated depending on the terminal. That is a legibility defect in
|
|
7848
|
+
output that ships today, and it survives this entry as a candidate on its own
|
|
7849
|
+
terms rather than as a reason to bring the palette back.
|
|
7850
|
+
|
|
7273
7851
|
### A backlink index over `archive/`, recording which change introduced or renamed an id
|
|
7274
7852
|
|
|
7275
7853
|
Proposed from OpenViking's `.relations.json`, restated twice on measurement, and
|
|
@@ -9002,6 +9580,47 @@ declared prefix ownership recorded above in this section — rejected on there
|
|
|
9002
9580
|
being no adoption that needs it, which is the same condition this sentence
|
|
9003
9581
|
supposes — which addresses, rather than a scorer, which ranks.
|
|
9004
9582
|
|
|
9583
|
+
### A standing gate on deprecated API usage
|
|
9584
|
+
|
|
9585
|
+
Priced on 2026-09-02, on the sweep that retired three deprecated call sites
|
|
9586
|
+
under `[Unreleased]` — turning `@typescript-eslint/no-deprecated` on by hand
|
|
9587
|
+
over a dependency walk through Zod 3→4, Vitest 3→4 and typescript-eslint
|
|
9588
|
+
8.2→8.65, none of the three reachable by anything the repo runs on a commit.
|
|
9589
|
+
The obvious move was to promote the rule into `eslint.config.js` beside
|
|
9590
|
+
`no-unnecessary-condition`, itself promoted from `strictTypeChecked` on exactly
|
|
9591
|
+
this argument: it needs the checker, and it found real instances here.
|
|
9592
|
+
|
|
9593
|
+
*What blocked it was not the rule, it was the reach.* `no-deprecated` is
|
|
9594
|
+
type-aware, so it only sees files a project includes, and the one deprecated
|
|
9595
|
+
site with the widest blast radius — `tseslint.config()` in `eslint.config.js`
|
|
9596
|
+
itself — sits in the set no project includes, along with `bin/attest.js` and
|
|
9597
|
+
the three `vitest*.config.ts`. Turning the rule on as-is buys a gate that
|
|
9598
|
+
covers `src`, `tests`, `self` and `examples` and silently *not* the files the
|
|
9599
|
+
tooling reads first, which is the shape this repo has been burned by twice
|
|
9600
|
+
already: `tests/import-boundary.spec.ts` deliberately does not use `findFiles`
|
|
9601
|
+
for the same reason, and its instrument guards exist because a walk that
|
|
9602
|
+
quietly stops reading a tree passes empty. A gate whose coverage is
|
|
9603
|
+
believed-wider-than-it-is is the failure this file keeps recording. Closing the
|
|
9604
|
+
gap meant either growing `tsconfig.typecheck.json` over the root config files —
|
|
9605
|
+
pulling `allowJs` into the wider project for `eslint.config.js` and
|
|
9606
|
+
`bin/attest.js` — or a second small project just for them, a third tsconfig in
|
|
9607
|
+
a repo that deliberately has two and has a comment in each explaining why.
|
|
9608
|
+
|
|
9609
|
+
**Rejected on the denominator.** Three call sites across an entire dependency
|
|
9610
|
+
walk is too small to carry the cost of either tsconfig shape — permanent, on
|
|
9611
|
+
every file the wider project would newly admit — against a defect a hand sweep
|
|
9612
|
+
at upgrade time already catches. `pre-commit-check` §9 records that sweep: run
|
|
9613
|
+
`no-deprecated` against a throwaway project covering what
|
|
9614
|
+
`tsconfig.typecheck.json` does and the five files it does not, and confirm the
|
|
9615
|
+
scan is actually seeing them before trusting a clean result. The check still
|
|
9616
|
+
happens, on the cadence the defect actually arrives on, without a third
|
|
9617
|
+
tsconfig or `allowJs` admitted into a wider project.
|
|
9618
|
+
|
|
9619
|
+
*Worth reopening if the count moves* — a dependency bump that turns up more
|
|
9620
|
+
than a handful of deprecated sites in one sweep is the signal that a hand pass
|
|
9621
|
+
is no longer keeping up, and it is measured the same way this entry was: read
|
|
9622
|
+
the `no-deprecated` output before deciding whether it needs a permanent home.
|
|
9623
|
+
|
|
9005
9624
|
## How an entry is re-measured
|
|
9006
9625
|
|
|
9007
9626
|
What the three sections above have learned about themselves, kept apart from all
|
|
@@ -9079,6 +9698,7 @@ path the repair makes reachable; it is about **everything the repair would make
|
|
|
9079
9698
|
|
|
9080
9699
|
## Releases
|
|
9081
9700
|
|
|
9701
|
+
[1.1.0]: https://gitlab.com/Pseudorca/attest/-/tags/v1.1.0
|
|
9082
9702
|
[1.0.0]: https://gitlab.com/Pseudorca/attest/-/tags/v1.0.0
|
|
9083
9703
|
[0.11.0]: https://gitlab.com/Pseudorca/attest/-/tags/v0.11.0
|
|
9084
9704
|
[0.10.0]: https://gitlab.com/Pseudorca/attest/-/tags/v0.10.0
|