@am_shork/attest 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +615 -55
- package/README.md +32 -11
- package/dist/core/apply.d.ts +15 -0
- package/dist/core/apply.js +43 -1
- package/dist/core/docs.d.ts +1 -1
- package/dist/core/docs.js +2 -0
- package/dist/core/gate.d.ts +40 -3
- package/dist/core/gate.js +56 -6
- package/dist/core/locate.d.ts +25 -4
- package/dist/core/locate.js +34 -11
- package/dist/core/parser.js +0 -0
- package/dist/core/pipeline.d.ts +0 -9
- package/dist/core/pipeline.js +117 -41
- package/dist/core/registry.d.ts +63 -4
- package/dist/core/registry.js +30 -4
- package/dist/core/runner.js +41 -4
- package/dist/core/schema.js +9 -1
- package/dist/core/skill.js +62 -7
- package/dist/core/static-registry.js +6 -2
- package/dist/core/types.d.ts +10 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -13,6 +13,375 @@ input, removes/renames a public API or `--json` field, or changes a default
|
|
|
13
13
|
runtime behavior an existing invocation relies on — diagnostic message text is
|
|
14
14
|
not API.
|
|
15
15
|
|
|
16
|
+
## [0.5.0] - 2026-08-03
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- **Design §11 now states what earns a `params` entry, and the README no longer
|
|
21
|
+
states a rule that produces the defect it warns about.** Two prose changes,
|
|
22
|
+
one subject, and both came out of the same adoption report.
|
|
23
|
+
*What earns a param:* **params for promises, plain constants for tuning.** A
|
|
24
|
+
value belongs in `params` when the requirement promises it — a timeout the user
|
|
25
|
+
is owed, a budget someone would file a bug about; a value that only tunes
|
|
26
|
+
behaviour stays an ordinary constant, because nothing is owed to anyone when a
|
|
27
|
+
tuning knob changes. `mine-capablanca` arrived at this unaided and applied it
|
|
28
|
+
consistently across 34 requirements (11 carrying params, 19 values), which is
|
|
29
|
+
the best available evidence that it is the natural rule — and it was written
|
|
30
|
+
down nowhere. It pushes back on the second of the two ways intent bloats, the
|
|
31
|
+
one §11's lumping bullet does not cover: a registry that accumulates every knob
|
|
32
|
+
in the project. Folded into the same paragraph, because it is the same rule
|
|
33
|
+
stated over the *type* rather than the value: what a param may therefore be — a
|
|
34
|
+
scalar, or an array of scalars. Stating those apart is what let one adopter
|
|
35
|
+
derive the first unaided and meet the second as `Invalid input`.
|
|
36
|
+
*And the rule that was half-wrong:* "Read the param inside the assertion, not
|
|
37
|
+
beside it" appeared in the README, the `init` skill and the `possible-drift`
|
|
38
|
+
section, and it is **necessary but not sufficient** — an assertion that
|
|
39
|
+
recomputes its expectation from the same param the code under test just read
|
|
40
|
+
has no independent term, so both sides move together and no edit can make it
|
|
41
|
+
fail. Measured, not theorised: a mine-density band moved by 3.3× with the whole
|
|
42
|
+
suite green, `check` silent, and the drift heuristic correctly quiet, because a
|
|
43
|
+
covering scenario did read the param. Every site now says what the evidence
|
|
44
|
+
supports — *the expectation must not be a function of the param the code under
|
|
45
|
+
test consumed* — and `possible-drift` carries a worked ✗/✓ pair. §11 records
|
|
46
|
+
the sharper statement of the hole: a tautological assertion is a scenario that
|
|
47
|
+
cannot fail, which the red-before-implement gate exists to catch, and it does
|
|
48
|
+
catch them in a change — but **changing an existing param's value goes through
|
|
49
|
+
no gate at all**.
|
|
50
|
+
No behaviour changed and no gate moved; the detector for the tautological form
|
|
51
|
+
stays under `Under consideration`, with all three of its blockers intact.
|
|
52
|
+
Documenting a trap is not the same as deciding not to detect it.
|
|
53
|
+
|
|
54
|
+
- **A change's spec files now live at the location they will be merged to,
|
|
55
|
+
marked `*.proposed.spec.ts`.** `changes/<name>/` keeps `proposal.md`,
|
|
56
|
+
`requirements.delta.ts` and `first-run.json`; the scenarios sit beside the code
|
|
57
|
+
they attest, and merging one is a rename in place. Reported by
|
|
58
|
+
`mine-capablanca`: every one of eight merges rewrote the relative imports of
|
|
59
|
+
the change's specs, and the imports were the only thing that moved.
|
|
60
|
+
**"Depth" was the wrong word for it, and finding that out changed the
|
|
61
|
+
design.** The entry that queued this said the specs sat at a *different depth*
|
|
62
|
+
from where they land. They do not — `changes/<name>/specs/` and
|
|
63
|
+
`lib/game/specs/` are both three levels down. What differs is the **path**, so
|
|
64
|
+
no layout that keeps a spec under `changes/` can make one relative specifier
|
|
65
|
+
resolve in both places; mirroring the destination inside the change folder
|
|
66
|
+
fails for the same reason, one level further out. The only fix that costs no
|
|
67
|
+
mechanism is to write the file where it will live, which is what shipped.
|
|
68
|
+
*Two jobs the folder used to do at once, now done separately.* Keeping a
|
|
69
|
+
proposal's specs out of the base suite is by **name**, because it has to hold
|
|
70
|
+
for commands that never heard of a change: those scenarios are red by
|
|
71
|
+
construction, and a `verify` that swept them up would call every project with
|
|
72
|
+
a change in flight broken. Deciding which change a spec belongs to is by
|
|
73
|
+
**id** — a change claims the proposed specs declaring a scenario for an id it
|
|
74
|
+
ADDs, renames to, or MODIFIEs — because a file name carries no change, and the
|
|
75
|
+
delta already enumerates what the change is about. That keeps the property the
|
|
76
|
+
folder had and a `specs: []` list in the delta would have lost: nothing names a
|
|
77
|
+
path, so nothing can fall out of step with the files. REMOVED ids claim
|
|
78
|
+
nothing; a file is claimed whole or not at all, because the run scope is a set
|
|
79
|
+
of files.
|
|
80
|
+
*What it cost, stated rather than netted off.* A change is no longer one
|
|
81
|
+
folder a reviewer can read end to end — the delta still enumerates every id and
|
|
82
|
+
`attest status <name>` still answers what a change contains, but the specs are
|
|
83
|
+
now found by their marker rather than by looking in one place. And separating
|
|
84
|
+
the two jobs opened a gap the folder did not have, which is the `Added` entry
|
|
85
|
+
below. Held by `ATX-45` (excluded by name) and `ATX-46` (claimed by id), split
|
|
86
|
+
because they fail independently.
|
|
87
|
+
*Migration, for anyone with an open change directory:* move each spec from
|
|
88
|
+
`changes/<name>/specs/` to where it will merge, rename it to
|
|
89
|
+
`*.proposed.spec.ts`, and fix its imports once. That is the rewrite the merge
|
|
90
|
+
was going to charge anyway, done earlier and once. `first-run.json` is
|
|
91
|
+
unaffected — it is keyed by requirement id and scenario name, neither of which
|
|
92
|
+
moves.
|
|
93
|
+
Gone with the folder: `changeExcludeGlobs`, the sibling-exclusion globs that
|
|
94
|
+
kept one proposal's specs out of another's gate run. A directory glob could not
|
|
95
|
+
have told two proposals apart once the specs left `changes/`, and keeping one
|
|
96
|
+
beside the claim check would be a second scoping rule able to disagree with it.
|
|
97
|
+
|
|
98
|
+
### Added
|
|
99
|
+
|
|
100
|
+
- **A change delta now reads as the registry of what it adds.** `delta(…)`
|
|
101
|
+
returns the delta *and* a record keyed by the ids it ADDs, each carrying the
|
|
102
|
+
params its author wrote — so a stage-1 scenario reads a proposed param with
|
|
103
|
+
the expression a merged one uses:
|
|
104
|
+
```ts
|
|
105
|
+
import reqs from '../../changes/add-2fa/requirements.delta.ts'; // ← only this line changes at merge
|
|
106
|
+
const window = reqs['AUTH-7'].params.totpWindowSec; // typed at 30, no cast
|
|
107
|
+
```
|
|
108
|
+
Reported by `mine-capablanca`, which hit this on 3 of 8 changes and wrote a
|
|
109
|
+
throwaway accessor each time, then rewrote every import at merge.
|
|
110
|
+
**The reason this was a defect and not an ergonomic complaint is which stage
|
|
111
|
+
it landed on.** Stage 1 is where the scenario must be written and must go red —
|
|
112
|
+
the entire content of the red-before-implement gate — so the tool pushed the
|
|
113
|
+
author off `params`, the single source the rest of it exists to reward, at
|
|
114
|
+
precisely the moment that source is load-bearing, and then billed them the
|
|
115
|
+
rewrite at merge for having complied. Every hand-written accessor is a second
|
|
116
|
+
place the value lives, open during the only stage where the assertion is being
|
|
117
|
+
authored against a value nobody has implemented yet.
|
|
118
|
+
*Why one value can be both.* A requirement id matches `/^[A-Z]+-\d+$/`, so no
|
|
119
|
+
id can ever be spelled `added`, `modified`, `removed` or `renamed` — the union
|
|
120
|
+
is safe by grammar rather than by convention, and neither surface shadows the
|
|
121
|
+
other. Both readers build it through one function, so the differential suite in
|
|
122
|
+
`tests/static-registry.spec.ts` still finds them agreeing about what a delta is.
|
|
123
|
+
*Deliberately not readable this way:* MODIFIED ids and rename targets. A
|
|
124
|
+
modified requirement's end state is the base entry with the patch applied, the
|
|
125
|
+
base is not in that file, and a view of the patch alone would answer one key
|
|
126
|
+
and `undefined` for every other key the requirement has — a shape that reads as
|
|
127
|
+
the merged requirement and is not one. Still no validation either: a delta
|
|
128
|
+
carrying an id the registry would refuse stays the gate's verdict (`ATX-41`).
|
|
129
|
+
Held by `ATX-48`, attested by compiling a fixture — a type has no runtime
|
|
130
|
+
footprint — with the assertion written as an equality against the merged read,
|
|
131
|
+
because sameness is the property that makes merging a change of import.
|
|
132
|
+
*Behaviour change, for whom:* the object `delta()` returns now carries the
|
|
133
|
+
added ids as own enumerable keys, so `Object.keys(d)` and a deep-equality
|
|
134
|
+
assertion over a whole delta see them. Nothing in Attest reads a delta that
|
|
135
|
+
way — `applyDelta` and both registry readers name the four sections — but a
|
|
136
|
+
consumer that snapshots a delta value will see its snapshot move. Reading
|
|
137
|
+
`.added`, `.modified`, `.removed` and `.renamed` is unchanged.
|
|
138
|
+
|
|
139
|
+
- **`added-id-unmerged`: the gate now names why a spec file failed to load,
|
|
140
|
+
instead of blaming the spec.** Reported by `mine-capablanca`. `archive`
|
|
141
|
+
applies the delta in memory and gates the result; the suite is a child process
|
|
142
|
+
that imports `*.reqs.ts` from disk. So implementation code doing what the
|
|
143
|
+
skill's rule 3 requires — `reqs['FOG-4'].params.modestRisk` for an id the
|
|
144
|
+
change *adds* — throws at import, every spec transitively importing that module
|
|
145
|
+
fails to load, and the gate reported `tests-red` plus a `declared-not-run` per
|
|
146
|
+
scenario, whose message sends the reader to look for a `skip` or an `.only`
|
|
147
|
+
that is not there. **The change could not be made green by the documented
|
|
148
|
+
workflow, and the verdict pointed away from the reason.**
|
|
149
|
+
The gate now carries load failures out of the run alongside the coverage, and
|
|
150
|
+
when the change adds ids the on-disk registry lacks it reports one ERROR per
|
|
151
|
+
unloaded file, naming the file and the ids. The `declared-not-run` lines it
|
|
152
|
+
replaces are dropped — the absence has been explained, and restating it as a
|
|
153
|
+
guess about a skip costs the reader attention on files that are not at fault.
|
|
154
|
+
Only in the gate: `verify` keeps reporting them, having no diagnosis to put in
|
|
155
|
+
their place, and a wrong message still beats silence.
|
|
156
|
+
**This is the diagnosis and deliberately not the fix.** The way through is to
|
|
157
|
+
merge the added requirement into the registry before running the gate, which
|
|
158
|
+
the idempotent ADDED rule makes free — the `added:` entry stays and still
|
|
159
|
+
documents the intent. The alternative considered was to show the child run the
|
|
160
|
+
applied registry through an alias or a scratch overlay: it unblocks the
|
|
161
|
+
workflow and spends the property that the registry the suite ran against is the
|
|
162
|
+
file on disk, on the one command path that already evaluates project code. That
|
|
163
|
+
property cannot be given back by a later diagnostic; the convenience can.
|
|
164
|
+
*The condition is a conjunction and therefore a heuristic* — a module can fail
|
|
165
|
+
to import for reasons unrelated to any requirement id — so the message states
|
|
166
|
+
the two facts and the inference between them rather than asserting a cause.
|
|
167
|
+
Held by `ATX-49` (name the file and the ids) and `ATX-50` (withdraw the lines
|
|
168
|
+
it replaces), which are two requirements rather than one for the reason
|
|
169
|
+
`ATX-24` was split out of `ATX-23`: pushing one more issue satisfies the first
|
|
170
|
+
in full and leaves every misleading line sitting underneath it, so an
|
|
171
|
+
implementation can pass it and fail the reader exactly as before.
|
|
172
|
+
*Behaviour change, for whom:* no run goes from green to red — the new ERROR
|
|
173
|
+
only fires on a run that was already failing, because a file that did not load
|
|
174
|
+
is a failing run. What does move is what a machine consumer reads: for this
|
|
175
|
+
case `archive` now emits `added-id-unmerged` where it emitted one
|
|
176
|
+
`declared-not-run` per scenario. `verify` is untouched, and `declared-not-run`
|
|
177
|
+
is unchanged everywhere else.
|
|
178
|
+
|
|
179
|
+
- **`proposed-spec-unclaimed`: a proposed spec no change claims is an ERROR from
|
|
180
|
+
`attest check`.** This is the cost of the split above, made loud. A file the
|
|
181
|
+
base suite skips by name and every gate skips by id executes nowhere, and both
|
|
182
|
+
skips are individually correct, so neither command is positioned to notice.
|
|
183
|
+
`check` is, because it is the only one that reads the deltas without being
|
|
184
|
+
about a single change — and reading them is something it already owed, since a
|
|
185
|
+
change delta is intent and the reporting commands read intent from source
|
|
186
|
+
(design §5.1). Reported per file, driven off the scanned tree rather than the
|
|
187
|
+
parsed plan so a proposed spec declaring no scenario at all is caught too. A
|
|
188
|
+
delta that cannot be read is reported as itself rather than turned into
|
|
189
|
+
accusations against its specs: not knowing what a change claims is a different
|
|
190
|
+
finding from knowing it claims nothing. Held by `ATX-47`.
|
|
191
|
+
|
|
192
|
+
### Fixed
|
|
193
|
+
|
|
194
|
+
- **`add-invalid` and `modify-invalid` now name the field that failed.** Both
|
|
195
|
+
quoted the schema's message and dropped the path it came from, so a
|
|
196
|
+
requirement whose `params` the schema refused reported `Invalid input` and
|
|
197
|
+
nothing else — no key, no accepted shape. Reported by an adopter who found the
|
|
198
|
+
failing param by reading `node_modules/@am_shork/attest/dist/core/schema.d.ts`,
|
|
199
|
+
which is the only place the constraint was written down.
|
|
200
|
+
*The form is not new, and that is the sharpest part of it.* `troubleshooting.md`
|
|
201
|
+
has quoted `Added requirement "AUTH-7" is invalid: rationale: Required` since
|
|
202
|
+
the section was written, and `RegistryValidationError` has spelled paths
|
|
203
|
+
`a.b.c` since the registry shipped — so the documented sample was one this
|
|
204
|
+
code could not produce, and the two sites that format the same zod failure
|
|
205
|
+
disagreed. The fix is the path, in the spelling already documented, from the
|
|
206
|
+
one function both callers go through. Where there is no path — the id itself,
|
|
207
|
+
under `introducedIdIssue` — nothing is prefixed rather than `(root)`, because
|
|
208
|
+
the message's own prefix has already named the thing.
|
|
209
|
+
Also on the way through: the `params` value union now carries its own message
|
|
210
|
+
instead of a bare `Invalid input`, so the refusal states what is accepted at
|
|
211
|
+
the moment it fires, and `registry-invalid` gains the same sentence for free.
|
|
212
|
+
Message text, so not a behaviour change under this file's own Versioning rule
|
|
213
|
+
— the codes and the accepted values are untouched, and no existing invocation
|
|
214
|
+
changes its exit code. Held by `ATX-44`, which states the contract over the
|
|
215
|
+
*path* rather than the sentence: the separator is the one property a scenario
|
|
216
|
+
can pin without asserting a word of the wording, which is what keeps
|
|
217
|
+
`Issue.message` prose while making its information content falsifiable.
|
|
218
|
+
|
|
219
|
+
## [0.4.3] - 2026-08-01
|
|
220
|
+
|
|
221
|
+
### Added
|
|
222
|
+
|
|
223
|
+
- **The README's two getting-started samples are now the fixture the packaging
|
|
224
|
+
test installs and runs.** `tests/docs-consistency.spec.ts` opened by naming
|
|
225
|
+
this hole: every `*.md` outside the generated `SPEC.md` is hand-written prose
|
|
226
|
+
that no mechanism binds to the code, "the framework's own blind spot (design
|
|
227
|
+
§11)". Every check in that file until now gated a version string or a name;
|
|
228
|
+
this is the first that gates *meaning* — a registry or a spec on the page that
|
|
229
|
+
the engine would now reject was undetectable, which for a tool whose whole
|
|
230
|
+
claim is reporting drift between intent and what the tests execute is the
|
|
231
|
+
failure that costs the most.
|
|
232
|
+
The mechanism is a naming split rather than new machinery, and most of it was
|
|
233
|
+
already there and unnamed: `fixtures/consumer/` imports by bare specifier from
|
|
234
|
+
`@am_shork/attest` and `@am_shork/attest/define` because it is installed from a
|
|
235
|
+
real tarball outside the repo — which is exactly what a reader has to be able
|
|
236
|
+
to paste. So the two files lost their fixture-specific commentary, gained the
|
|
237
|
+
README's teaching comments, and the README now quotes them; the new assertion
|
|
238
|
+
is byte-equality between the fenced block and the file, newline-normalised so a
|
|
239
|
+
CRLF checkout cannot call a clean tree stale. The fixture is the original and
|
|
240
|
+
the page is the quote: breaking either side fails.
|
|
241
|
+
*What moved in the README:* the spec sample is now `session.spec.ts` beside
|
|
242
|
+
`session.ts` rather than `auth/session.spec.ts`, matching the layout the
|
|
243
|
+
packaging test actually runs, and it shows both of the requirement's scenarios
|
|
244
|
+
because the installed run asserts `scenarioCount: 2`.
|
|
245
|
+
*Limits, both deliberate:* the gate covers 2 of the 23 fenced `ts` blocks in
|
|
246
|
+
the README and `docs/en/**` — the rest are elided, ✓/✗-annotated, or
|
|
247
|
+
deliberately rejected by the engine, and none of those can equal a runnable
|
|
248
|
+
file. And the two claims are joined only on a release: this gate runs in
|
|
249
|
+
`pnpm test` and proves the page and the fixture agree, while the run that
|
|
250
|
+
proves the fixture still *works* is `pnpm test:consumer`, which needs a network
|
|
251
|
+
and a tarball. Both remaining halves are in `Planned`.
|
|
252
|
+
|
|
253
|
+
- **The `docs/en` ↔ `docs/zh` mirror rule is now a gate rather than a paragraph
|
|
254
|
+
in `CLAUDE.md`.** Two assertions in `tests/docs-consistency.spec.ts`, both
|
|
255
|
+
scoped to what is comparable without comparing prose. The file set: a page
|
|
256
|
+
added on one side only is not a stale translation, it is a page that does not
|
|
257
|
+
exist, and nothing in the repository would have noticed. And the structure of
|
|
258
|
+
each pair — the heading tree as levels in document order, and the sequence of
|
|
259
|
+
fence info strings — which catches a section dropped or reordered in
|
|
260
|
+
translation, a code sample present in one language only, and a ```ts that
|
|
261
|
+
became ```bash.
|
|
262
|
+
*The parse is the part with a trap in it.* A `#` at the start of a line inside
|
|
263
|
+
a ```bash sample is a shell comment, and these pages are full of them, so the
|
|
264
|
+
reader tracks fences and skips their contents; a closing fence must repeat the
|
|
265
|
+
character that opened it, or a ``` quoted inside a ~~~ block ends the wrong
|
|
266
|
+
thing. An unterminated fence is reported rather than ignored, because it hides
|
|
267
|
+
every heading after it — two documents can agree by being truncated. All three
|
|
268
|
+
are asserted directly, on the standard this file already applies to its other
|
|
269
|
+
parses: a check whose parser can silently match nothing is not a check.
|
|
270
|
+
*One third of the queued entry turned out to be already covered and was not
|
|
271
|
+
written.* It asked for the `##` issue-code headings of `troubleshooting.md` to
|
|
272
|
+
be held equal between languages; the roster check that predates it already
|
|
273
|
+
asserts each language against `ISSUE_CODES` in both directions, so their
|
|
274
|
+
equality follows and a third assertion would catch nothing they let through.
|
|
275
|
+
*The limit, stated because it is not small:* a page whose structure held while
|
|
276
|
+
its meaning went stale passes everything here. Translation is not a diff. Same
|
|
277
|
+
shape as the limit the History gate carries — it can check that a row cites a
|
|
278
|
+
real version, not that the row is true.
|
|
279
|
+
|
|
280
|
+
### Documentation
|
|
281
|
+
|
|
282
|
+
- **The agent-facing documents gained a diagnosis discipline, a review pass, and
|
|
283
|
+
a design vocabulary**, adapted from an external skill collection rather than
|
|
284
|
+
copied from it — the parts that survived are the ones this repository's own
|
|
285
|
+
defect history had already paid for.
|
|
286
|
+
*New skill, `.claude/skills/diagnose/`.* Its one rule is that no hypothesis may
|
|
287
|
+
be formed before a command exists that has been run at least once and can go
|
|
288
|
+
**red** on the bug in hand. Every defect this repository has recorded failed
|
|
289
|
+
silently rather than loudly, so that bar is higher here than the phrase
|
|
290
|
+
suggests, and the skill's local content is the questions that tell a green run
|
|
291
|
+
apart from one that never happened: read the test count rather than the colour,
|
|
292
|
+
since a `beforeAll` throw reports a file as *skipped* (`[0.4.2]`); confirm the
|
|
293
|
+
file you care about actually ran, since a glob that matched nothing is a clean
|
|
294
|
+
run over code that never loaded; and name which half a command covers before
|
|
295
|
+
trusting it. The ways that goes wrong are the Gotchas in `CLAUDE.md`, pointed
|
|
296
|
+
at rather than restated — a second copy of them would only be a second copy to
|
|
297
|
+
drift. The remainder is the standard loop: minimise until every element is
|
|
298
|
+
load-bearing, three to five falsifiable hypotheses before testing any,
|
|
299
|
+
`[DEBUG-…]`-tagged probes, and "no correct seam is itself the finding".
|
|
300
|
+
*`pre-commit-check` gained §8, a reading pass on two axes*, kept unmerged and
|
|
301
|
+
unranked against each other because a change can pass either and fail the
|
|
302
|
+
other. The Spec axis is mostly machine-checked here and nowhere else — `check`,
|
|
303
|
+
`cover` and `verify` *are* drift detection — so it reads only for what those
|
|
304
|
+
cannot reach: whether an `ATX-n`'s text states the contract, whether `params`
|
|
305
|
+
pin the values the scenario should be pinned to, and whether a green scenario
|
|
306
|
+
attests its requirement or merely passes. The Standards axis carries a twelve-
|
|
307
|
+
smell baseline (Fowler, *Refactoring* ch.3) as labelled heuristics that any
|
|
308
|
+
documented rule here overrides, with two weighted for this repository:
|
|
309
|
+
duplicated path-shaped code means a second copy that did not go through
|
|
310
|
+
`src/core/paths.ts`, and speculative generality is the other face of §1's rule
|
|
311
|
+
that every requirement costs a scenario forever.
|
|
312
|
+
*`CLAUDE.md` gained a design vocabulary* — module, interface, depth, seam,
|
|
313
|
+
adapter, and the three tests that settle most arguments — which exists because
|
|
314
|
+
the two structures it names were already these things and had no word for it.
|
|
315
|
+
`src/core/paths.ts` is the repository's deepest module, and the deletion test
|
|
316
|
+
is not hypothetical for it: every silent path defect in the Gotchas is what
|
|
317
|
+
this codebase looked like when that knowledge lived at the call sites. The two
|
|
318
|
+
registry readers are two adapters at one real seam, which is what makes the
|
|
319
|
+
differential suite possible — and the entry marks where the analogy stops,
|
|
320
|
+
since which adapter runs is fixed by the command and never a fallback, that
|
|
321
|
+
being a security property rather than a design preference.
|
|
322
|
+
*And a section on where a decision goes*, which is this file. It is stated in
|
|
323
|
+
`CLAUDE.md` rather than only in `pre-commit-check` because the moment to write
|
|
324
|
+
one is when the decision happens; a design evaluated and dropped mid-session is
|
|
325
|
+
what is most likely to be lost, and by commit time the reasoning that killed it
|
|
326
|
+
is already gone.
|
|
327
|
+
*The candidate that did not survive is the reason the section exists.* The
|
|
328
|
+
proposal was a `docs/adr/` directory holding three decisions lifted out of the
|
|
329
|
+
Gotchas — why `fixtures/nested-spec/` may not be flattened, why `render
|
|
330
|
+
--check` compares the document rather than the bytes, why one `spawn` helper.
|
|
331
|
+
All three were already recorded here, in more detail than the proposal would
|
|
332
|
+
have carried, so the directory's whole effect would have been to split a store
|
|
333
|
+
this file keeps deliberately single. Recorded rather than filed as work, since
|
|
334
|
+
no such directory exists: had it been put under `docs/en/`, the mirror gate
|
|
335
|
+
added above would not have seen it at all — `pages()` is a non-recursive
|
|
336
|
+
`readdir` filtered to `*.md`, so a subdirectory is invisible to the file-set
|
|
337
|
+
check, the structure check, and the version-bearing sample scan alike. A
|
|
338
|
+
documentation subdirectory is therefore an unmirrored one, silently, which is
|
|
339
|
+
this repository's signature failure shape wearing documentation's clothes.
|
|
340
|
+
|
|
341
|
+
### Fixed
|
|
342
|
+
|
|
343
|
+
- **The packaging test installed into a directory Windows spells two ways, and
|
|
344
|
+
read the result as the package being broken.** `tests/consumer.spec.ts` staged
|
|
345
|
+
its throwaway project under `os.tmpdir()`, which on Windows reads `TEMP` — set
|
|
346
|
+
by the OS to the 8.3 short form, `C:\Users\JOHN_C~1\...`. Vite resolves the
|
|
347
|
+
peer `vitest` from the *long* form of the same directory, so the consumer's
|
|
348
|
+
spec file and the installed `@am_shork/attest` imported two module instances of
|
|
349
|
+
it; `describe` ran against an uninitialised runner and the spec died at import
|
|
350
|
+
with `Cannot read properties of undefined (reading 'config')`, before a
|
|
351
|
+
scenario executed. What that surfaced as is the part worth recording: `attest
|
|
352
|
+
verify` reported `tests-red` plus `declared-not-run` for every scenario —
|
|
353
|
+
a report indistinguishable from a genuinely broken package, produced by a
|
|
354
|
+
package that is fine. Staging through `realpath` fixes it.
|
|
355
|
+
This is `CLAUDE.md`'s first failure family one form further out — not a
|
|
356
|
+
separator inside a path, but two spellings of one directory — and it was
|
|
357
|
+
unreachable until `[0.4.2]` made this suite run on Windows at all. CI is Linux,
|
|
358
|
+
where `os.tmpdir()` has one spelling, so the pipeline was never going to say
|
|
359
|
+
otherwise.
|
|
360
|
+
|
|
361
|
+
- **The AST parser's own source file was binary, so every change to it since the
|
|
362
|
+
initial commit was unreviewable.** `src/core/parser.ts` separated the two
|
|
363
|
+
halves of its param-ref dedupe key with a **raw NUL byte** rather than the
|
|
364
|
+
`\x00` escape. One character no editor renders reclassified the whole file:
|
|
365
|
+
`git diff` answered `Binary files a/… and b/… differ` for every change to it,
|
|
366
|
+
`git grep` and `grep` skipped it in silence, and `.gitattributes`' `eol=lf`
|
|
367
|
+
never applied, because `text=auto` tests for binary first.
|
|
368
|
+
*What it cost is the reviewing, not the running.* A string escape is resolved
|
|
369
|
+
by the compiler, so the key is the same three code units on every platform —
|
|
370
|
+
no behaviour changes, and no requirement, scenario or schema moves. What was
|
|
371
|
+
lost is that every text sweep over this repository silently excluded the one
|
|
372
|
+
module that turns spec source into the plan every command reads. A pre-commit
|
|
373
|
+
pass grepping `src/` for a rule — a `path.relative` outside `paths.ts`, a
|
|
374
|
+
stray `spawn` — was answering about 27 of 28 files and reporting no gap, which
|
|
375
|
+
is worse than not having run it.
|
|
376
|
+
*And it shipped.* `files` carries `dist`, and `tsc` copies a template
|
|
377
|
+
literal's contents verbatim, so `dist/core/parser.js` put the byte into every
|
|
378
|
+
installed copy.
|
|
379
|
+
Recorded at this length because the defect is not the byte but its invisibility:
|
|
380
|
+
this is `CLAUDE.md`'s silent-failure shape with no platform in it at all, and
|
|
381
|
+
the tools anyone would bring to look for it are the ones it disables. The last
|
|
382
|
+
thing it costs is its own diff — the old side of this commit is still the
|
|
383
|
+
binary blob, so this entry is the only readable account of the change.
|
|
384
|
+
|
|
16
385
|
## [0.4.2] - 2026-07-30
|
|
17
386
|
|
|
18
387
|
### Added
|
|
@@ -1880,26 +2249,17 @@ feedback reports are triaged into issues or `Unreleased` and deliberately not
|
|
|
1880
2249
|
kept in this tree; that rule stands, and holds for anything with a reporter, a
|
|
1881
2250
|
repro, or a state to track. What is kept here is the narrower thing this file
|
|
1882
2251
|
already keeps one section of: a **decision with an argument attached**, where
|
|
1883
|
-
losing the argument would cost more than losing the task. The first
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
and already says in its own header why it checks only the two couplings tight
|
|
1895
|
-
enough to assert cheaply; this is a third. What is comparable without
|
|
1896
|
-
comparing prose: the heading tree (level, order, count), the count of fenced
|
|
1897
|
-
code blocks, and — for `troubleshooting.md` — the set of `##` issue-code
|
|
1898
|
-
headings, which is machine-derived on both sides and so must be equal
|
|
1899
|
-
exactly. Translation is not a diff and nothing here should pretend otherwise,
|
|
1900
|
-
which is also the limit: a page whose structure held while its meaning went
|
|
1901
|
-
stale passes. Same shape as the limit the History gate already carries — it
|
|
1902
|
-
can check that a row cites a real version, not that the row is true.
|
|
2252
|
+
losing the argument would cost more than losing the task. The first below came out
|
|
2253
|
+
of building something queued here, which is one way an entry arrives: a gate that
|
|
2254
|
+
finds a defect on its first run files the defect. The second came out of reading
|
|
2255
|
+
another repository against this one — `msw`. The last came out of an adoption
|
|
2256
|
+
report (`mine-capablanca`, 0.4.2, 34 requirements / 56 scenarios / greenfield),
|
|
2257
|
+
which is the third way: not a defect, but a shape the tool made an adopter work
|
|
2258
|
+
around eight times without ever erroring. Two further entries from that report
|
|
2259
|
+
left this section by shipping in `[0.5.0]` — a change's specs moving to their
|
|
2260
|
+
merged location, and the delta reading as the registry of what it adds — and
|
|
2261
|
+
their arguments are recorded there, including the part of the first one that
|
|
2262
|
+
turned out to be wrong.
|
|
1903
2263
|
|
|
1904
2264
|
- **A diagnosable refusal when the resolved compiler has no AST API.**
|
|
1905
2265
|
Discovered by building the `typescript` matrix (see `[0.4.2]`), which is
|
|
@@ -1928,40 +2288,36 @@ run files the defect.
|
|
|
1928
2288
|
it yet — a separate decision, with the honest answer for now being that the
|
|
1929
2289
|
supported range has a ceiling and says so.
|
|
1930
2290
|
|
|
1931
|
-
- **
|
|
1932
|
-
`
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
byte-equal to a living fixture while the paragraph above it describes the old
|
|
1962
|
-
behaviour still passes — the same limit already written down for the History
|
|
1963
|
-
rows, and the same answer: this binds what is machine-derivable and leaves the
|
|
1964
|
-
rest to a reader.
|
|
2291
|
+
- **Region-marked samples, so a partial quote can be bound too.** The whole-file
|
|
2292
|
+
half of this shipped in `[0.4.3]`: the README's two getting-started blocks
|
|
2293
|
+
are now byte-equal to `fixtures/consumer/`. That buys 2 of the 23 fenced `ts`
|
|
2294
|
+
blocks in the README and `docs/en/**`, and the ceiling is structural rather
|
|
2295
|
+
than lazy — the other 21 are elided (`{ /* … */ }`), annotated (`// ✓ the single
|
|
2296
|
+
source`, `// ✗ a literal that can drift`), or deliberately rejected by the engine
|
|
2297
|
+
(`params: { maxMb: MAX_MB }`), and none of those can equal a runnable file.
|
|
2298
|
+
**What raises it is marking spans, not a second mechanism.** A
|
|
2299
|
+
`// #region readme:registry` and its close inside the fixture, with the
|
|
2300
|
+
assertion running block against dedented span, lets one file feed several
|
|
2301
|
+
passages and makes a partial quote bindable by construction. Where that
|
|
2302
|
+
actually reaches, counted rather than hoped: `troubleshooting.md`'s
|
|
2303
|
+
single-source pair is two verbatim lines of `fixtures/consumer/session.spec.ts`
|
|
2304
|
+
and its `AUTH-3` block is the registry entry at a different indent — so a
|
|
2305
|
+
handful more, not the remainder. Anything carrying a `✓`/`✗` is authored
|
|
2306
|
+
commentary and stays a fragment; that is what it is for.
|
|
2307
|
+
*The ✗ blocks are a separate question, and worth not conflating.* A sample
|
|
2308
|
+
showing input the engine must reject could be bound to a fixture whose
|
|
2309
|
+
*rejection* is asserted — which is what `fixtures/eval-invalid-registry`
|
|
2310
|
+
already is. Binding those is the more valuable half, because a counterexample
|
|
2311
|
+
that stopped being a counterexample is the one kind of stale sample that
|
|
2312
|
+
actively teaches the wrong thing. It is also the one that needs a fixture per
|
|
2313
|
+
diagnostic, so it is not free the way the regions are.
|
|
2314
|
+
*The mirror rule still caps what can be claimed on the Chinese side.*
|
|
2315
|
+
`docs/zh/**` matches `docs/en/**` down to the comments inside its code samples,
|
|
2316
|
+
so byte-equality holds on one side only and the zh blocks stay translated prose
|
|
2317
|
+
around the same code — the parity gate in `[0.4.3]` is what covers them now,
|
|
2318
|
+
and it covers structure, not meaning. Unchanged from the whole-file half: the residual
|
|
2319
|
+
risk recorded with it in `[0.4.3]` — that the gate and the run proving the
|
|
2320
|
+
fixture still works live in different suites — applies here identically.
|
|
1965
2321
|
|
|
1966
2322
|
## Under consideration
|
|
1967
2323
|
|
|
@@ -2001,6 +2357,158 @@ have to gather again.
|
|
|
2001
2357
|
someone hits an `EMFILE` from `attest check`, the diagnosis is already written
|
|
2002
2358
|
down rather than rediscovered.
|
|
2003
2359
|
|
|
2360
|
+
- **An assertion that recomputes its expectation from the param the code under
|
|
2361
|
+
test just read is a tautology, and nothing says so.** Reported by
|
|
2362
|
+
`mine-capablanca`, which probed it by mutating the registry and running the
|
|
2363
|
+
full gate on each mutation:
|
|
2364
|
+
|
|
2365
|
+
| mutation | who reads it | `check` | `verify` |
|
|
2366
|
+
|---|---|---|---|
|
|
2367
|
+
| `GEN-1.maxCandidatesPerPiece` 64 → 4 | scenario only | ok | **red** |
|
|
2368
|
+
| `BOARD-1.boardSize` 10 → 12 | app + fixtures | ok | **red** |
|
|
2369
|
+
| `BOARD-2.maxMines` 12 → **40** | app + assertion | ok | **green** |
|
|
2370
|
+
| `BOARD-2.minMines` 9 → **1** | app + assertion | ok | **green** |
|
|
2371
|
+
|
|
2372
|
+
The mine-density band — the one number deciding whether that game is playable —
|
|
2373
|
+
moved by 3.3× with the whole suite green, `check` silent, and `possible-drift`
|
|
2374
|
+
correctly not firing, because a covering scenario *does* read the param.
|
|
2375
|
+
**Read the third and fourth rows against the second, because that is the
|
|
2376
|
+
finding.** The reporter's diagnosis was "app and spec both read it", but row 2
|
|
2377
|
+
is app-and-spec and goes red. What distinguishes the green rows is narrower and
|
|
2378
|
+
more useful: the assertion **derives its expectation from the same param the
|
|
2379
|
+
code under test used**, so both sides move together and the comparison has no
|
|
2380
|
+
independent term. Row 2's expectation lives in fixtures, which do not move when
|
|
2381
|
+
the param does, and the mechanism works. So the failing condition is a property
|
|
2382
|
+
of the assertion, not of who reads the value — which matters, because it means
|
|
2383
|
+
the answer is a rule about how to write the assertion, and *not* the detector
|
|
2384
|
+
the reporter proposed.
|
|
2385
|
+
**What this falsifies is written in this file.** The rejection of a committed
|
|
2386
|
+
params snapshot, below, closes on *"the mechanism that guards a param is a
|
|
2387
|
+
scenario reading it, not a file recording it"* — a sufficiency claim, and rows
|
|
2388
|
+
3 and 4 are counterexamples to it. A scenario reading the param is necessary
|
|
2389
|
+
and not sufficient. That entry stays rejected on its own argument, which was
|
|
2390
|
+
never about detection power, but its fallback clause is now known incomplete
|
|
2391
|
+
and is annotated accordingly. The README's §"killer move" has the same shape:
|
|
2392
|
+
it says noticing a param *moved* is `verify`'s job "only when a scenario asserts
|
|
2393
|
+
on the value it read from `params`" — literally a necessary condition, and read
|
|
2394
|
+
by an adopter as a sufficient one, with the very next line (*"Read the param
|
|
2395
|
+
inside the assertion, not beside it"*) being the instruction that produces the
|
|
2396
|
+
tautology.
|
|
2397
|
+
**The framing that probably resolves it: this is `never-red` wearing different
|
|
2398
|
+
clothes.** A tautological assertion is a scenario that cannot fail, which is
|
|
2399
|
+
the exact defect `archive`'s red-before-implement gate exists to catch — and it
|
|
2400
|
+
did catch two of them in this very repo (`CHECK-2`, `FOG-3`, both confirmed
|
|
2401
|
+
correct by the reporter). The reason it did not catch these is structural:
|
|
2402
|
+
**changing the value of an existing param goes through no gate at all.**
|
|
2403
|
+
`archive` gates a change; a param edit is not a change. That is a cleaner
|
|
2404
|
+
statement of the gap than "params can be tautological".
|
|
2405
|
+
*What blocks the decision — three things, all of them real.* **(1)** Every
|
|
2406
|
+
mutation above is synthetic. Across 8 commits touching `*.reqs.ts` in that
|
|
2407
|
+
repo, **no param value was ever modified** — every `params:` line in every diff
|
|
2408
|
+
is an addition. So the hole is proven as a logical property and its frequency is
|
|
2409
|
+
entirely unmeasured, from one greenfield repo. **(2)** The reporter's proposed
|
|
2410
|
+
mechanism — a WARNING when one param id is read from both a spec and a non-spec
|
|
2411
|
+
file — is the wrong shape twice over: it fires on row 2, which is correct usage
|
|
2412
|
+
and goes red, and it would fire on the arrangement the README actively
|
|
2413
|
+
recommends, which is §9's "usually wrong about staleness" failure mode again.
|
|
2414
|
+
It also needs a static sweep of the whole project for `reqs['X'].params.y`,
|
|
2415
|
+
far past the two registry readers' current surface. **(3)** No mechanism is yet
|
|
2416
|
+
known that separates "the assertion pins this independently" from "the assertion
|
|
2417
|
+
echoes it" without executing the scenario twice under different values — which
|
|
2418
|
+
is mutation testing, a much larger thing to own.
|
|
2419
|
+
**The guidance half has shipped; this entry is now only about the detector.**
|
|
2420
|
+
"Read the param inside the assertion" was insufficient and half-wrong as
|
|
2421
|
+
written, and `[0.5.0]` replaces it everywhere it appeared — README, the
|
|
2422
|
+
`init` skill, `possible-drift`, and design §11 — with what the evidence
|
|
2423
|
+
supports: **the expectation must not be a function of the param the code under
|
|
2424
|
+
test consumed**, pinned in a fixture, a literal, or a second independently
|
|
2425
|
+
derived value. Row 2 is the worked example, and the reporter's own
|
|
2426
|
+
`CHECK-2`/`FOG-3` fixes are the same move. It was held back once because
|
|
2427
|
+
rewording the central claim of the tool is not a line appended; that is what
|
|
2428
|
+
was then done, deliberately and in one pass.
|
|
2429
|
+
**What stays undecided is the mechanism**, and all three blockers above are
|
|
2430
|
+
untouched by the rewording: the frequency is still unmeasured from one
|
|
2431
|
+
greenfield repo, the reporter's proposed detector is still the wrong shape
|
|
2432
|
+
twice over, and nothing short of mutation testing separates an independent pin
|
|
2433
|
+
from an echo. What the guidance does not do is close the hole — a tautological
|
|
2434
|
+
assertion is still green, still silent, and still reachable — so this stays
|
|
2435
|
+
here rather than moving to `Considered and rejected`: documenting a trap is
|
|
2436
|
+
not the same as deciding not to detect it.
|
|
2437
|
+
|
|
2438
|
+
- **`attest archive <change> --apply`, finishing the merge the gate approved.**
|
|
2439
|
+
`mine-capablanca`'s single loudest item: eight changes, eight hand-written
|
|
2440
|
+
Python scripts, and the report quotes one — it locates the delta's block by
|
|
2441
|
+
`split('added: {', 1)`, trims two spaces of indent per line, and re-terminates
|
|
2442
|
+
the registry on `assert t.endswith('});')`. After the gate goes green the
|
|
2443
|
+
adopter hand-splices the delta into the registry, moves specs into the suite,
|
|
2444
|
+
rewrites their imports, moves the folder to `archive/` and re-renders `SPEC.md`.
|
|
2445
|
+
**The cost is the smaller half of the argument. The hole is that the gate's
|
|
2446
|
+
verdict does not cover the step that acts on it** — `archive` proves the delta
|
|
2447
|
+
is green, covered and drift-free, and then a human transcribes it by hand with
|
|
2448
|
+
nothing checking the transcription. A delta can be spliced in wrong, partially,
|
|
2449
|
+
or twice, and the next `check` sees only whatever ended up in the file.
|
|
2450
|
+
**The objection that has to be answered is the `AGENTS.md` block under
|
|
2451
|
+
`Considered and rejected`.**
|
|
2452
|
+
The `AGENTS.md` block was rejected because "that is a merge tool, and each of
|
|
2453
|
+
its failure modes is destructive on a file the user cannot regenerate". The
|
|
2454
|
+
argument for why it does not transfer, which needs to be right before this
|
|
2455
|
+
ships: there, Attest would edit prose whose grammar it does not own; here the
|
|
2456
|
+
registry is a **literal** Attest defines, statically parseable, with a
|
|
2457
|
+
differential suite already asserting two readers agree on it — so the result of
|
|
2458
|
+
a splice is checkable by re-reading it, and the delta was validated by the gate
|
|
2459
|
+
that just ran. Idempotence is verifiable rather than assumed. That reasoning
|
|
2460
|
+
covers delta→registry, the folder move and the re-render. It used **not** to
|
|
2461
|
+
cover import rewriting, which is editing arbitrary TypeScript and is squarely
|
|
2462
|
+
the rejected shape.
|
|
2463
|
+
*That blocker is gone, and it was removed rather than solved.* Proposed specs
|
|
2464
|
+
now live at their merged location under `*.proposed.spec.ts` (`[0.5.0]`), so
|
|
2465
|
+
the merge step for a spec is a rename in place — there are no specifiers to
|
|
2466
|
+
rewrite, and `--apply` no longer inherits the one part of the job that could
|
|
2467
|
+
not be made safe. What is left for it is the delta→registry splice, the rename,
|
|
2468
|
+
the folder move to `archive/` and the re-render, all of which the paragraph
|
|
2469
|
+
above argues are checkable by re-reading the result.
|
|
2470
|
+
*What still blocks the decision:* two questions with no answer yet — what
|
|
2471
|
+
`--apply` does when the working tree is dirty (`archive` already has a position
|
|
2472
|
+
on this; whether a file-mutating flag takes the same one is not obvious), and
|
|
2473
|
+
whether a partially applied merge is recoverable or whether the flag must be
|
|
2474
|
+
all-or-nothing against a scratch copy.
|
|
2475
|
+
*The frequency blocker has moved.* This entry used to close on "one report, one
|
|
2476
|
+
repo: the cost evidence is strong and the frequency evidence is a single
|
|
2477
|
+
adopter." It is now two. This project's own maintainer proposed the same command
|
|
2478
|
+
independently, without the entry in front of them, having felt the same step by
|
|
2479
|
+
hand — which is the second data point that clause was waiting for. Not
|
|
2480
|
+
conclusive about frequency in the field, and it does not touch either question
|
|
2481
|
+
above, but it removes the reason to keep waiting for someone else to ask.
|
|
2482
|
+
|
|
2483
|
+
*A narrower shape was proposed with it and is rejected — `attest merge <change>`,
|
|
2484
|
+
moving the change folder to `archive/<date>-<name>/` and nothing else.* Three
|
|
2485
|
+
reasons, and the third is the one that decides it.
|
|
2486
|
+
**(1) It targets the cheap half.** The expensive step in the report is the
|
|
2487
|
+
delta→registry splice — the quoted Python locates the block by
|
|
2488
|
+
`split('added: {', 1)`, trims two spaces per line, and re-terminates on
|
|
2489
|
+
`assert t.endswith('});')`. The folder move is `mv`. A new command that saves a
|
|
2490
|
+
shell invocation and leaves the TypeScript-literal surgery is the wrong half to
|
|
2491
|
+
automate. The date it would stamp is real ergonomics but small: `archive/` is
|
|
2492
|
+
named in three places and all three are exclusions (`locate.ts`'s SKIP_DIRS,
|
|
2493
|
+
and the two exclude globs), so nothing reads the directory and a mis-stamped
|
|
2494
|
+
date has no mechanical consequence at all.
|
|
2495
|
+
**(2) It would make two command names mislead instead of one.** `attest archive`
|
|
2496
|
+
archives nothing today — it is purely the gate. Adding a second verb that does
|
|
2497
|
+
the archiving leaves `archive` still lying and `merge` doing its job; `--apply`
|
|
2498
|
+
goes the other way and makes the existing verb finally mean what it says.
|
|
2499
|
+
**(3) A move that does not re-run the gate can file an unfinished change as
|
|
2500
|
+
done.** That is the one thing this tool must not ship: the whole claim is that
|
|
2501
|
+
"done" has a hard definition, and a command able to mark completion around that
|
|
2502
|
+
definition removes it. The alternative is that `merge` re-runs the gate — at
|
|
2503
|
+
which point it *is* `--apply` and should be spelled that way. There is no third
|
|
2504
|
+
option, which is why this is a rejection of the shape rather than of the idea.
|
|
2505
|
+
*Recorded here rather than under `Considered and rejected`,* because nothing
|
|
2506
|
+
was decided against: the idea is this entry, and only the narrow spelling of it
|
|
2507
|
+
died. Also worth stating because it needed no release to ride — a new command
|
|
2508
|
+
changes no exit code, rejects no previously-valid input, and removes no API, so
|
|
2509
|
+
it is additive under this file's own Versioning rule and was never blocked on a
|
|
2510
|
+
version bump.
|
|
2511
|
+
|
|
2004
2512
|
## Considered and rejected
|
|
2005
2513
|
|
|
2006
2514
|
Decisions **not** to build something, kept where they can be found before the
|
|
@@ -2247,6 +2755,21 @@ no scenario caught it, and `possible-drift` was **not** already printing —
|
|
|
2247
2755
|
because if it was printing, what failed was reading the report, and a second
|
|
2248
2756
|
gate does not fix that.
|
|
2249
2757
|
|
|
2758
|
+
*Annotated after `mine-capablanca` (0.4.2): the last two sentences above are
|
|
2759
|
+
wrong and the rejection is not.* "A scenario reading it" is necessary and not
|
|
2760
|
+
sufficient — where the assertion recomputes its expectation from the same param
|
|
2761
|
+
the code under test consumed, the value moved 3.3× with the suite green and
|
|
2762
|
+
`possible-drift` correctly silent, so "the only condition under which a moved
|
|
2763
|
+
param goes unnoticed" names one condition out of two. The reopen clause is
|
|
2764
|
+
therefore satisfied in substance while failing on its own literal terms, since it
|
|
2765
|
+
was written expecting the uncovered case and the demonstrated one has a covering
|
|
2766
|
+
scenario. **What that changes here is nothing**: this entry was rejected on the
|
|
2767
|
+
population a snapshot fires on — every legitimate param edit, fix a commit with
|
|
2768
|
+
no decision in it — and that objection is untouched by the new evidence, which
|
|
2769
|
+
concerns detection the snapshot was never denied having. The live question moved
|
|
2770
|
+
to `Under consideration` above, where it belongs, because the mechanism now in
|
|
2771
|
+
question is a rule about assertions rather than a file beside the registry.
|
|
2772
|
+
|
|
2250
2773
|
### Grouping `render`'s output by prefix
|
|
2251
2774
|
|
|
2252
2775
|
The *detection* half of this is already rejected above (every automatic
|
|
@@ -2271,7 +2794,44 @@ requirement, and human review at propose is still the whole answer. What is
|
|
|
2271
2794
|
rejected is grouping as a way to assist it, and this reopens only on a
|
|
2272
2795
|
contradiction that grouping would have caught.
|
|
2273
2796
|
|
|
2274
|
-
|
|
2797
|
+
### Nested objects in `params`, so a lookup table can be registry-owned
|
|
2798
|
+
|
|
2799
|
+
Raised by `mine-capablanca` (0.4.2) and explicitly not as a request, which is
|
|
2800
|
+
why it is recorded with its own answer rather than left as a complaint about a
|
|
2801
|
+
message. A `params` value is a scalar or a list of scalars; a table — piece kind
|
|
2802
|
+
to weight, tier to limit — is refused, and the only way to register one is to
|
|
2803
|
+
split it across parallel arrays, which reads worse in the rendered spec than it
|
|
2804
|
+
buys. *What rejected it: the two properties a param has, and a table has
|
|
2805
|
+
neither.* A param is **rendered into `SPEC.md` as a value a human checks at a
|
|
2806
|
+
glance**, and it is **read by a scenario as the one place that value lives**. A
|
|
2807
|
+
nested object fails the first by construction — there is no reading of a
|
|
2808
|
+
five-row table that a reviewer verifies the way they verify `30`. It fails the
|
|
2809
|
+
second more quietly: a table has many readers by definition, so "the one place"
|
|
2810
|
+
becomes "the one file", which is what an ordinary module already is. The
|
|
2811
|
+
schema's own comment records where the line was drawn and why arrays were let
|
|
2812
|
+
in — list constants are the most drift-prone values, and an array still has one
|
|
2813
|
+
owner read by one scenario. A table does not clear that bar; the reason arrays
|
|
2814
|
+
did is exactly the reason it does not.
|
|
2815
|
+
*The reporter's own resolution is the argument's strongest form.* The table went
|
|
2816
|
+
to a shared module, is not registry-owned, and **nothing was lost** — because a
|
|
2817
|
+
piece-value table is tuning, not a promise. That is the rule design §11 now
|
|
2818
|
+
states — *params for promises, plain constants for tuning* — derived
|
|
2819
|
+
independently by the same adopter before it was written anywhere. Under it this
|
|
2820
|
+
candidate is not a limitation of `params` at all: it is a value that was never a
|
|
2821
|
+
requirement's to hold, and §11 carries this rejection's own argument in the same
|
|
2822
|
+
paragraph, as the rule stated over the type rather than over the value.
|
|
2823
|
+
*What ships instead*, in `[0.5.0]`: the refusal now says what is accepted
|
|
2824
|
+
instead of `Invalid input`, and `troubleshooting.md` states the constraint and
|
|
2825
|
+
where such a value belongs — the reporter's actual ask, which was for the
|
|
2826
|
+
decision to be visible rather than discovered by reading the shipped `.d.ts`.
|
|
2827
|
+
The permanent half goes to design §11 with the params rule, not here.
|
|
2828
|
+
*This reopens on* a report where the value is genuinely promised — a table an
|
|
2829
|
+
adopter owes their users, whose rows a reviewer would want in the rendered spec.
|
|
2830
|
+
None has been seen; the one that raised it was tuning.
|
|
2831
|
+
|
|
2832
|
+
[Unreleased]: https://gitlab.com/Pseudorca/attest/-/compare/v0.5.0...main
|
|
2833
|
+
[0.5.0]: https://gitlab.com/Pseudorca/attest/-/tags/v0.5.0
|
|
2834
|
+
[0.4.3]: https://gitlab.com/Pseudorca/attest/-/tags/v0.4.3
|
|
2275
2835
|
[0.4.2]: https://gitlab.com/Pseudorca/attest/-/tags/v0.4.2
|
|
2276
2836
|
[0.4.1]: https://gitlab.com/Pseudorca/attest/-/tags/v0.4.1
|
|
2277
2837
|
[0.4.0]: https://gitlab.com/Pseudorca/attest/-/tags/v0.4.0
|