provared 0.5.0 → 0.7.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 CHANGED
@@ -1,5 +1,1501 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.0 (1 November 2026)
4
+
5
+ Version 0.7.0 makes three more document names part of the format (section 27 of the format description: `provared.tool-name`, `provared.tool-call-id` and `provared.tool-result`), and lets whoever checks a record name the addresses at which a permission must have been signed (`issuerOrigins`, Python `issuer_origins`, `--origin` on the command line, and a box on the checking page), so that a permission that another page under the passkey's website name had the passkey sign, by presenting it as a sign-in, is refused. Beside the library, two programs are published on npm for the first time, each at 0.1.0: `provared-mcp-executor`, an executor for MCP tool servers, which holds the tool servers' credentials, refuses a tool call the permission does not allow, and countersigns each receipt with keys of its own; and `provared-claude-code`, the recorder beside Claude Code, which can now also be installed as a Claude Code plugin. Releases are now published from the author's signed tag by a GitHub workflow, and each npm version waits for the author's approval with a passkey. What a reader must know: no record's bytes change, and a book written by 0.6.0 checks as it did, since 0.6.0 refused the three new names and so never wrote them; but a book that names any of the three new names checks in full only with a checker of 0.7.0 or later, because a checker of 0.5.0 or 0.6.0 refuses it (`bad-field`, as for every `provared.` name it does not know) and one of 0.4.0 or earlier reads the names as labels. The MCP executor writes two of them in every receipt, so its books need 0.7.0. The framework packages `provared-langchain` and `pydantic-ai-provared` stay at 0.2.0, unchanged: they ask for `provared` 0.6.0 or later, and what they write is the same with 0.7.0.
6
+
7
+ - **Versions.** The library is 0.7.0, in `package.json` and `python/pyproject.toml`. Ranges on the library: `provared-mcp-executor` and the OpenTelemetry bridge (not published) now ask for `^0.7.0`, where they asked for `>0.6.0 <0.8.0`; the recorder beside Claude Code, the gateway and the sealer (neither of the last two published) ask for `>=0.6.0 <0.8.0`, where they asked for `^0.6.0`, which left 0.7.0 out (the recorder's `package-lock.json`, which a Claude Code plugin installs from, still holds 0.6.0, which the new range accepts, and the plugin's test of that lockfile now takes a range of that form as well as a caret range); the Hermes plugin (not published) asks for `>=0.4.0,<0.8`, where it asked for `<0.7`. The README and the guide name the two packages now on npm, with their install lines.
8
+
9
+ - The gateway's test "M3: an agent may have only so many calls waiting"
10
+ no longer depends on how busy the computer is: each wait has 120
11
+ seconds, not 20, and the status is shown to answer without waiting for a
12
+ call by the upstream still holding that call, not by a time under one
13
+ second. It checks the same limit.
14
+ - **Three more reserved document names** (format description, section
15
+ 27), in both languages: `provared.tool-name` (the fingerprint of the
16
+ tool's name, exactly as the agent's software called it, which is not the
17
+ slip's action name), `provared.tool-call-id` (the fingerprint of the
18
+ identifier the model gave the tool call, exactly as received) and
19
+ `provared.tool-result` (the fingerprint of the tool's result in the one
20
+ form of RFC 8785, as `provared.arguments` is of the arguments; never the
21
+ result itself). Each may be named once in a record; the three
22
+ identifiers of 0.5.0 are still the only names that may be named more
23
+ than once. The first two are fingerprinted with `identifierFingerprint`
24
+ (`identifier_fingerprint`), the third with `argumentsFingerprint`
25
+ (`arguments_fingerprint`); no new function. `recordTools`,
26
+ `record_tools` and `record_function` still name the arguments only, so
27
+ what the published framework packages write is unchanged. The MCP
28
+ executor writes `provared.tool-name` and `provared.tool-result` in place
29
+ of its labels `mcp.tool` and `mcp.result`; the OpenTelemetry bridge
30
+ writes `provared.tool-call-id` where a tool gives `callId`, which before
31
+ went on the span only, and still never writes `provared.event-id`, the
32
+ platform's identifier, which only the caller can name. Both packages now
33
+ need 0.7.0 or later (`^0.7.0`). Shared test files:
34
+ `stub-contents.json`, `http-fingerprints.json` and `denial-contents.json`
35
+ remade, with the three names accepted once, refused twice, and names
36
+ close to them (`provared.Tool-result`, `provared.tool_result`,
37
+ `provared.tool-call`) refused as unknown. **Compatibility:** no book
38
+ written by version 0.6.0 fails its check because of this change, since
39
+ 0.6.0 refused these names and so never wrote them; a checker of version
40
+ 0.5.0 or 0.6.0 refuses a book that names any of the three
41
+ (`bad-field`, as for every `provared.` name it does not know), and a
42
+ checker of 0.4.0 or earlier, from before the names were reserved, reads
43
+ them as labels; so a book written with them checks in full only with a
44
+ later version. The guide, the Python README, the list of threats and the
45
+ type declarations say so.
46
+ - **An executor for MCP tool servers**, `mcp-executor/`
47
+ (`provared-mcp-executor` 0.1.0), stage 1: the stdio
48
+ transport. An executor, in the words of the Internet-Draft
49
+ `draft-izmaylov-agent-permission-receipts-02`, is software other than
50
+ the agent that holds what its actions need and refuses an action the
51
+ permission does not allow. The agent's MCP client starts the executor as
52
+ if it were the tool server; the executor starts the real server with its
53
+ credential (from a file or the executor's own environment, never the
54
+ agent's), and passes every message of either revision of the protocol
55
+ through unchanged except two. `tools/call` is matched to an action by
56
+ the tool's name in a rules file, checked by the check before acting
57
+ through the agent's stub writer, which the executor holds; a call the
58
+ permission allows is sent on and gets a receipt that names the executor
59
+ in `with`, binds the arguments and the server's result by fingerprint,
60
+ and is countersigned with the executor's own key set once the outcome is
61
+ known; a call it does not allow is answered as a tool error that says
62
+ why, and written as a denial. `tools/list` comes back without the tools
63
+ that have no rule. Beside the book the executor keeps its own copy, one
64
+ tagged line before each call is sent (made sure to be on the disk
65
+ first) and one once its receipt is in the book, and `compare` holds that
66
+ copy against a book to find a receipt that was left out, which the draft
67
+ leaves unspecified. A call the executor cannot record is not sent. A
68
+ call cut off by a stop gets its receipt at the next start, from the copy,
69
+ naming `provared.error`. Lines from the agent are read strictly (no
70
+ batch, no member name given twice, an id that is text or a whole number),
71
+ and refused otherwise. The design, the HTTP transport and the content
72
+ rules of the plan's F-157 (design only), and 36 numbered choices for
73
+ review are in `mcp-executor/DESIGN.md`. Nothing in the library changes.
74
+ The test workflow runs the package's tests, and ESLint covers the folder.
75
+ An independent review of the first draft found one serious fault and
76
+ six of medium weight, each now fixed and a test (`test/review1.test.mjs`):
77
+ a receipt lost for good where the book file could not be written after a
78
+ call (the copy now keeps the outcome and the next start writes the
79
+ receipt from it); a late answer from a server that ignores cancellation
80
+ taken for a later call's under the same client id (each call now goes to
81
+ the server under an id of the executor's own); a `tools/call` sent as a
82
+ notification forwarded unchecked (dropped); the process staying alive
83
+ after its server ended (it ends with the server's code); no cap on calls
84
+ or list requests waiting (`maxWaiting`, default 16; 256 lists); a bare
85
+ program name found through the client's `PATH` (named by path, or
86
+ `server.path`, with a warning otherwise); a result that cannot be
87
+ fingerprinted leaving no outcome in the receipt (`provared.error` with a
88
+ fixed text). Smaller points: each copy line carries its number, so a
89
+ line taken out, put twice or moved stops the start; `compare` tells a
90
+ receipt the executor could not write apart from one the book lacks, does
91
+ not agree where the book has a problem, and makes no key; a whole number
92
+ beyond 2^53 - 1 anywhere in a line is refused; `check` writes nothing;
93
+ a permission naming other services beside the executor is warned about.
94
+ The founder's decision F-158 (content rules in the executor's settings,
95
+ fingerprinted into receipts and into the permission's `agent.software`;
96
+ the automatic late receipt kept; reserved names for a tool's name and
97
+ result to come in the library and draft -03) is recorded in `DESIGN.md`.
98
+ A narrow second review of those fixes found one serious and one medium
99
+ fault in the id mapping, both fixed and tests (`test/review2.test.mjs`):
100
+ a server answer holding a whole number beyond 2^53 - 1 was refused as
101
+ unreadable, so the call timed out and the receipt named a false timeout
102
+ (the server's lines are now read without that rule, reach the client in
103
+ the server's own bytes with only the id replaced, and such a result is
104
+ named as one that cannot be fingerprinted; an answer that cannot be read
105
+ at all still reaches the client, and the receipt says so); and only tool
106
+ calls were renamed, with guessable ids, so a `ping` under a call's id
107
+ could settle the call with the ping's answer (every request now goes
108
+ under an id with a random part). Requests the server never answers are
109
+ forgotten past 1,000. A receipt written at a restart names
110
+ `executor.written-late`, so that the book itself says so. A final check
111
+ found two small points, fixed: a server line naming `id` twice at its
112
+ top level, or a batch, is matched with no call and handed on as it is,
113
+ and the call ends by the timeout with a "no answer" receipt.
114
+
115
+ - **The recorder beside Claude Code as a Claude Code plugin.** It can now
116
+ be installed, with its hooks, in two commands in Claude Code:
117
+ `/plugin marketplace add provared/provared`, then
118
+ `/plugin install provared-recorder@provared`. A marketplace file at the
119
+ top of the repository (`.claude-plugin/marketplace.json`, the place
120
+ `/plugin marketplace add` looks) lists one plugin, the folder
121
+ `integrations/claude-code`, which gains `.claude-plugin/plugin.json`
122
+ (the name `provared-recorder`, the package's version) and
123
+ `hooks/hooks.json`: the hooks `init` writes, each run through the
124
+ plugin's entry, `plugin.mjs`, by the plugin's own paths
125
+ (`${CLAUDE_PLUGIN_ROOT}`), and at the start of a session a new command,
126
+ `session-start`. It starts the recorder as `ensure` does and, where
127
+ something stands in the way (no library, no permission yet, a recorder
128
+ that does not start or is still starting, the recorder's hooks in force
129
+ twice), tells the person and Claude in Claude Code's own fields for
130
+ that, within 27 seconds; where all is well it says nothing. Nothing
131
+ signs for the person: they run `init` (which, run through the plugin,
132
+ writes no hooks), sign at https://sign.prova.red/ with their own
133
+ passkey, and run `use`, as the first session tells them. Before each
134
+ call the hook, where no permission was ever put in place, now says that,
135
+ with the same steps, rather than only that the recorder is not running,
136
+ and it waits for the recorder for at most 25 seconds from its own start.
137
+
138
+ - **The plugin's library, by an exact path.** Claude Code 2.1.167 copied a
139
+ plugin fetched from a git repository into its cache without the library
140
+ `provared`, so every call was refused and the start of a session failed;
141
+ and Node.js would have looked for the name in the folders above the
142
+ plugin, such as the person's home folder. The plugin's entry
143
+ (`plugin.mjs`, with `plugin-library.mjs`) now takes the library from
144
+ the plugin's own `node_modules` where Claude Code put it there, or else
145
+ installs it itself, at a session's start or at a command the person runs,
146
+ with `npm ci --ignore-scripts` from the folder's new `package-lock.json`
147
+ (exactly 0.6.0, the package checked by npm against the fingerprint
148
+ the lockfile holds, no install script run, npm's own program run by Node.js
149
+ with no shell and stopped for certain at its time limit), into the
150
+ plugin's data folder, and loads it from there alone, in this program and
151
+ in every program it starts. After that, the copy is trusted by where it
152
+ sits, and is not checked again before each use. Whatever goes wrong in
153
+ the entry itself (a damaged copy, a missing lockfile, a Node.js without
154
+ `module.registerHooks`), a call is refused before it is taken and Claude
155
+ Code is told after it, as the hook itself does. A settings file with
156
+ comments is looked through for the recorder's hooks as JSON with
157
+ comments. Where it cannot, the session says so,
158
+ every call is refused, and each refusal gives the one command that mends
159
+ it (`plugin.mjs install-library`). Checked with Claude Code 2.1.167's own
160
+ `claude plugin validate`, with and without `--strict`, and by installing
161
+ the plugin, from a marketplace whose entry names a git copy of this
162
+ branch, into a Claude Code settings folder of its own, then running its
163
+ hooks and `session-start` from the copy in the plugin cache.
164
+
165
+ - **Not recorded twice.** Claude Code runs a plugin's hook and the same
166
+ hook in a settings file side by side. Before each call, the plugin's
167
+ hook (and only it: Claude Code names the plugin's folder to it) looks in
168
+ the person's and the project's settings files for a hook that runs a
169
+ recorder's `hook.mjs`, in the forms the README lists (a word joined from
170
+ quoted and unquoted parts as a shell joins it; the project's and the
171
+ home folder written as variables or `~`; a copy since deleted, on a path
172
+ that names Provared); where it finds one, it refuses the call and says
173
+ how to take one of the two out. It reads no settings file larger than
174
+ 1 MB, looks at no more than 20,000 words, for no more than 2 seconds,
175
+ and refuses the call past any of these. After a call it records it
176
+ whatever it finds. A new command, `remove-hooks`, takes the hooks `init`
177
+ wrote, from any copy of the recorder, out of a project's
178
+ `.claude/settings.local.json`, and nothing else, by writing a new file
179
+ that then takes the old one's place, and only where the file did not
180
+ change while it was read. `init` gains `--no-hooks`, refuses any other
181
+ word after it, and warns where it writes hooks while the plugin is
182
+ installed. The new file `settings-hooks.mjs` is in the npm package.
183
+
184
+ - **The heading of the top fingerprint.** The checking page heads the card
185
+ of a record's top fingerprint "The top fingerprint (the Seal)" only where
186
+ the record holds a seal, and "The top fingerprint" where it holds none
187
+ (`topFingerprintHeading` in `page/render.js`). The "try it" page no
188
+ longer relabels the heading itself. Nothing else the page shows changes,
189
+ and the command-line checker, the Python version and the shared test
190
+ files never held these words.
191
+
192
+ - **OpenSSF Scorecard.** A workflow, `.github/workflows/scorecard.yml`,
193
+ runs the Open Source Security Foundation's Scorecard on each push to
194
+ main, every Sunday and when a branch protection rule changes, and
195
+ publishes the result to api.scorecard.dev and to this repository's code
196
+ scanning page. Laid out as the Scorecard project's own example; no
197
+ permission at the top, and only `security-events: write` and
198
+ `id-token: write` in its one job. It is the first workflow here to use
199
+ an action that is not GitHub's own (`ossf/scorecard-action` v2.5.0,
200
+ pinned to its commit). Pinning fixes the action's own files, not all
201
+ that runs: at that commit the action runs the container
202
+ `ghcr.io/ossf/scorecard-action:v2.5.0`, chosen by its tag (on
203
+ 31 October 2026 the tag named the image
204
+ sha256:1e1481c65fc2407d3fd6ab18a23c741eb4d1c0d9359ebafc4b3ab13aa61d8627).
205
+ It cannot be pinned by that fingerprint, because the Scorecard service
206
+ takes a result only from the named action. The job's token can only
207
+ send results to the code scanning page and ask for an identity token,
208
+ so at worst a changed container could publish a false result. The
209
+ README shows the Scorecard badge.
210
+
211
+ - **Static analysis.** `.github/workflows/tests.yml` gains a job that runs
212
+ ESLint over the JavaScript and Ruff over the Python on every push and
213
+ pull request, with rules that look for faults (eval, unsafe regular
214
+ expressions, child processes, weak fingerprint methods, subprocess with
215
+ shell=True and the like), not style. Both are development tools only,
216
+ kept in `tools/lint/` at exact versions (its `package-lock.json`, and
217
+ `requirements.txt` with each file's fingerprint), and are in neither
218
+ published package. A finding that is not a fault is set aside on its
219
+ own line, with a comment giving the reason. Faults found and fixed, none
220
+ changing what the library does: the Hermes plugin's `cli.py` used a
221
+ backslash inside an f-string's braces, which Python 3.11, the oldest
222
+ the plugin supports, cannot read; three Python tests expected any
223
+ exception where the library raises `Refusal`, and now expect
224
+ `Refusal`; three imports were never used. ESLint also covers
225
+ `conformance/`, and Ruff also covers `tools/release/`; there
226
+ `compare_wheel.py` now reads only `https://` addresses, since the
227
+ second address it reads comes from the index's own answer.
228
+
229
+ - **Warnings are failures.** The Python tests run with `python -W error`
230
+ (with two filters: a warning raised inside a LangChain dependency on
231
+ Python 3.14, and, in the Hermes plugin's job, the plugin's own notices to
232
+ the person, which it raises inside Hermes on purpose). The JavaScript tests no longer hide Node.js's warnings,
233
+ and a step after each run fails on any warning other than Node.js's
234
+ notice that ML-DSA or SLH-DSA is experimental
235
+ (`tools/lint/node-warnings.sh`); each run keeps its own exit code
236
+ (`set -o pipefail`), so a failing run is not hidden by the step that
237
+ saves its output. One was found and fixed: the seal
238
+ writer handed a Web Crypto key of another method to Node.js's
239
+ `createPrivateKey`, which Node.js has deprecated (DEP0203); such a key
240
+ is now refused before it is handed over, with the same answer as before.
241
+
242
+ - **Smaller changes.** The site's publishing workflow gives each job only
243
+ the permissions it needs (`pages: write` and `id-token: write` in the
244
+ deploy job alone) and checks out without keeping a token.
245
+ `CONTRIBUTING.md` names GitHub's private reporting form beside email
246
+ for security faults, and says that a change in behaviour comes with
247
+ tests.
248
+
249
+ - **A reader may name the address a permission must have been signed
250
+ at** (25 to 31 October 2026), in both languages. A passkey can be asked
251
+ to sign by any page under the website name it was made for (its relying
252
+ party), and what it signs is a fingerprint the person cannot read on the
253
+ device's prompt; so a page served under that name, or a script on one,
254
+ could have a passkey made for `prova.red` sign a permission it wrote, by
255
+ presenting the permission's fingerprint as a sign-in challenge, and a
256
+ checker, which compared the address only with what the permission itself
257
+ said, would pass it (the list of threats, section 3, said so since 0.6.0,
258
+ with no option to close it). Now the checker's option `issuerOrigins`
259
+ (Python `issuer_origins`; the command line `--origin`, which may be
260
+ repeated; a box on the checking page) names the addresses at which
261
+ permissions must have been signed, each in the one form the format
262
+ allows for `issuer.origin` (`isPageAddress`, Python `is_page_address`,
263
+ gives the form). A permission signed anywhere else is refused with the
264
+ new code `issuer-origin-not-expected`, after the passkey has been
265
+ compared as before, and the stubs under it are not compared; a
266
+ permission signed at an address named gets a note that says so; a list
267
+ that is not in that form, or longer than 64, is a problem with the whole
268
+ check (`bad-field`), never set aside. An approval and a cancellation are
269
+ held to the permission's address by the passkey check already. The
270
+ command-line checker says "Signed at an address you named?" where
271
+ `--origin` was given, and refuses a value not in its plain form. Nothing
272
+ changes for a reader who names no address, and no record's bytes change.
273
+ Format description, section 4.4; the list of threats, sections 3, 7 and
274
+ 8; the guide, "How signing works". Shared test files: `slips.json` is
275
+ made again with eleven new cases (every record in it is signed afresh),
276
+ `denial-constants.json` with the new code, and `issuer-origins.json`
277
+ and `issuer-origin-shows.json` are new. Tests:
278
+ `test/issuer-origins.test.mjs`, `python/tests/test_issuer_origins.py`,
279
+ and cases in `test/cli.test.mjs`, `test/page.test.mjs` and
280
+ `test/refusals.test.mjs`.
281
+
282
+ - **The signing page says why it is served at a name of its own**, and
283
+ the one tool that writes that site. The page (`page/sign.html`) now
284
+ says that the passkey it makes belongs to the page's website name and
285
+ to nothing else, why the page is served at a name that serves nothing
286
+ else, and that whoever checks a record can name that address; shown
287
+ inside another page, it now says to open sign.prova.red in a tab of its
288
+ own (before: prova.red/sign/; the pages name no address with its
289
+ scheme, which a test holds). `tools/make-sign-site.mjs`
290
+ writes the whole of the repository `provared/sign`, from which
291
+ sign.prova.red is served, from this repository: under `site/` the page
292
+ as `index.html`, its script, the renderer, the style sheet and every
293
+ module of the library, each byte for byte, with `site/files.sha256` in
294
+ the form `sha256sum --check` reads; and the repository's own README,
295
+ publishing workflow (with the actions pinned to the commits this
296
+ repository's workflow pins) and licence. It takes `--from <tag or
297
+ commit>`, which the README and the workflow name as where the copies
298
+ come from, and reads every file with git at that tag or commit, never
299
+ from the working tree: a file not committed, or changed and not
300
+ committed, cannot reach the site, and the modules copied are those git
301
+ holds directly under `src/` there. A name that git cannot find is
302
+ refused (code 2), and nothing is written. A tag is named as the tag;
303
+ any other name as the commit's full hash. With `--check` it compares a
304
+ folder written before with the same files, read the same way, and names
305
+ what differs. Run against the copy made by hand on 25 October 2026 from
306
+ tag v0.6.0, with `--from v0.6.0` it finds every file equal; with
307
+ `--from` this branch's commit it finds the seven files that this change
308
+ and the one above change (the page, its script, the renderer, and
309
+ `src/book.js`, `check.js`, `denial.js` and `slip.js`) and their list of
310
+ fingerprints different, and the README and the workflow, which name
311
+ where the copies come from. Test: `test/sign-site.test.mjs`, which
312
+ builds a small repository of its own with a tag, so that it needs
313
+ neither this repository's tags nor its history.
314
+ The guide's table of folders names the tool.
315
+
316
+ - **The recorder beside Claude Code and the Hermes plugin print links to
317
+ https://sign.prova.red/** (their `SIGNING_PAGE`), where they printed
318
+ links to prova.red/sign/; their READMEs say what a link printed by an
319
+ earlier version begins with and how to open it at the new address.
320
+
321
+ - **The recorder beside Claude Code: a passkey can be retired.** The
322
+ recorder's list of passkeys, `trusted-passkeys.json`, served two
323
+ purposes with one list: which passkeys may sign a new permission, and
324
+ which passkeys' permissions it opens its books with (the book in force
325
+ and the kept ones, for late receipts and for counting limits across
326
+ books), and it refuses to start where the book in force holds a
327
+ permission from a passkey not on it. So a passkey that had signed
328
+ permissions in the record could not be taken off the list without
329
+ stopping the recorder. Now the file holds two lists: `passkeys`, every
330
+ passkey whose permissions are in the record, written and added to as
331
+ before and never shortened; and among them `retired`, written by the
332
+ new commands `retire-passkey <thumbprint>` and
333
+ `admit-passkey <thumbprint>`. A retired passkey's permissions stand,
334
+ the recorder still starts on the books that hold them and still counts
335
+ limits across those books, and a new permission from it is refused,
336
+ with or without `--new-passkey`, by `renew`, by `use`, by the running
337
+ service and at start-up where a new permission was put in place by
338
+ hand, until the person admits it again; only a passkey on the list can
339
+ be retired. The retired list is read from the file at each renewal, so
340
+ the command counts at once, while the recorder runs or not. The list is
341
+ read again just before it is written, and the two are merged: every
342
+ passkey on the file and every one added, never fewer, and the retired
343
+ list with the one change asked for, written to a temporary file of its
344
+ own and renamed into place; so `retire-passkey` run at the same moment
345
+ as a renewal that adds a new passkey keeps both changes (a narrow
346
+ window remains between that reading and the renaming, since no lock is
347
+ held); a list that would hold no passkey is never written. At start-up,
348
+ only the permission last in force is taken without the checks: the
349
+ last permission line of `book.jsonl`, or, where a switch to a new book
350
+ was interrupted and there is no `book.jsonl`, the last permission line
351
+ of the last kept book. Any other permission put in `slip.json`, even one
352
+ that either book holds further up, goes through the checks, a retired
353
+ passkey's among them, and no line is added for it. What is left: a
354
+ program on this computer that puts back the permission last in force
355
+ (removing `book.jsonl` where it was last in force in the kept book) can
356
+ resume it, even if its passkey was retired afterwards, and so can one
357
+ that adds a permission's line at the end of either book. Retirement
358
+ stops new permissions; it does not stop a program that can change the
359
+ recorder's files. A file written by an earlier version holds
360
+ `passkeys` alone and is read as retiring none, so a recorder updated in
361
+ place starts on its folder as it is; a `retired` that is not a list of
362
+ thumbprints from `passkeys` stops the recorder, as a malformed
363
+ `passkeys` does. `status` names the retired passkeys (`retired`). Test:
364
+ `integrations/claude-code/test/retire.test.mjs`.
365
+
366
+ - **A workflow that publishes a release from the founder's signed tag**
367
+ (25 October 2026). `.github/workflows/publish.yml` runs only when a tag
368
+ such as `v0.7.0` is pushed. Before anything is published it checks that
369
+ the tag is signed by the release key in `.github/release-signers` and
370
+ by nothing else, that the tag, `package.json`, `python/pyproject.toml`
371
+ and the newest heading of this change log name the same version, and
372
+ that the JavaScript and Python libraries pass their tests on the tagged
373
+ commit, with the tests of every framework package against the library
374
+ from that commit. It then publishes the Python packages and stages the
375
+ npm package, through each registry's trusted publishing, with no stored
376
+ password or token, and makes the GitHub release with the checking page
377
+ as one file and its SHA-256. The npm version is staged
378
+ (`npm stage publish`) and becomes public only when the author approves
379
+ it on npmjs.com; the run shows the stage ID and the package's
380
+ integrity value, so that the author approves that one stage, and the
381
+ release notes say, for each registry, what the run sent there. The
382
+ Python files are checked
383
+ against the SHA-256 recorded when they were built before they are
384
+ uploaded. A version already on a registry is not published again, so a
385
+ framework package is published only when its own version is raised;
386
+ where its version is already published and its code differs, the run
387
+ stops before anything is published. The scripts it runs are in
388
+ `tools/release/`. Nothing in the npm package or the Python packages
389
+ changes. Version 0.6.0 and earlier were published by hand.
390
+ - `tools/make-single-page.mjs` refuses an option, or more than one
391
+ argument, and writes nothing: `--out x` used to write a file named
392
+ `--out`. It still takes one optional argument, the file to write.
393
+
394
+ - **A conformance kit**, in `conformance/` (not published): another
395
+ implementation of the format, in any language, is tested against the
396
+ shared test files through a small program of its own (an adapter),
397
+ which the kit hands one case at a time as a line of JSON and whose
398
+ answer it compares. By default it compares the codes and the shape of
399
+ each answer, not the words meant for people, as `test-vectors/README.md`
400
+ sets out; `--words` compares every value. It ends with code 0 only where
401
+ every case compared matched. An adapter for Provared's own JavaScript
402
+ library passes all 14,742 cases word for word. Passing it shows
403
+ agreement on these cases only; it is not a certificate. Its tests run in
404
+ the test workflow beside the sealer's.
405
+
406
+ - **The conformance kit, two changes.** By default it no longer counts the
407
+ sentences in the lists named `notes` and `limits`: another implementation
408
+ may say the same thing in more or fewer sentences, so only the codes,
409
+ the items that are not texts and the shape of each answer are compared;
410
+ `--words` still compares every word and every sentence. And a second,
411
+ example adapter, `conformance/adapters/provared-public.mjs`, is written
412
+ only on the functions the npm package offers (`provared` and
413
+ `provared/check`), and can be pointed at an installed copy of the
414
+ package with `--package`: it answers 3,099 of the 14,742 cases, all of
415
+ them matching word for word, and skips the other 11,643, which test
416
+ parts of the library that are not offered to other programs, saying why
417
+ for each.
418
+
419
+ - **The conformance kit, after an independent review.** By default, the
420
+ findings of an entry (`problems`, `breaches`, `disclosureProblems`) are
421
+ compared in any order, as the format description says their order is
422
+ not part of the format; and an entry's own `problems` may hold only
423
+ some of those stored, at least one, since a checker may stop at the
424
+ first way an entry fails its own check (where it stopped before the
425
+ countersignature, "unchecked" is taken in its place). Breaches are
426
+ still compared in full. `--words` keeps the stored order. The verdict
427
+ now says how many cases were skipped and not tested, the functions
428
+ skipped entirely are listed, and `--no-skip` counts any skip as a
429
+ failure. A reply holding both an answer and a skip is a fault of the
430
+ adapter. The example adapter reports an error it does not expect as a
431
+ crash, where it gave the code `check-failed`. The guide gains a table
432
+ of every function the shared test files name: its section of the
433
+ format description, whether another implementation may skip it, where
434
+ its answer is declared, and how its cases are written; and it lists
435
+ the stored values that are particular to Provared's own library.
436
+
437
+ - **An OpenTelemetry bridge**, `provared-opentelemetry` 0.1.0, in
438
+ `integrations/opentelemetry/` (not published). It puts each tool call's
439
+ receipt on the trace span that observability tools already show, in one
440
+ call: `traceTools` (as `recordTools`) or `traceAct` (as the stub
441
+ writer's `act`). The span
442
+ gets `provared.outcome`, `provared.action`, `provared.stub.fingerprint`,
443
+ `provared.stub.seq`, `provared.stub.countersigned`,
444
+ `provared.stub.trace_bound`, `provared.slip.fingerprint` and, for a call
445
+ not taken, `provared.check.codes` and `provared.denial.fingerprint`; a span
446
+ the bridge starts is an `execute_tool` span as OpenTelemetry's
447
+ conventions for generative AI describe it. The receipt names the
448
+ fingerprint of the span's trace id as `provared.trace-id` only for a tool
449
+ that the caller says sends the trace out with its call, in a
450
+ `traceparent` header (`sendsTrace: true`; off by default), as section
451
+ 27 of the format description defines the name; the bridge cannot see
452
+ whether the tool really does. The model's identifier for the call goes
453
+ on the span as `gen_ai.tool.call.id`, never into the receipt. Only
454
+ fingerprints, names and codes reach a span: never arguments, amounts,
455
+ documents, keys, the words of a finding or an error's message. A span is
456
+ the agent's side's own word; the receipt and the checker are the
457
+ evidence. `@opentelemetry/api` and `provared` are peer dependencies; the
458
+ library itself still has none. An error that the tool, or one of its own
459
+ functions, throws is the tool's error, even a `NotTaken` from another
460
+ stub writer. `traceAct` adds nothing to a request that comes with an
461
+ approval made beforehand, or whose `details` is not a list. Its tests run
462
+ in a job of their own in the test workflow, at the locked versions and
463
+ again at the newest releases of OpenTelemetry.
464
+ - **An example for a JavaScript agent framework** in
465
+ `examples/frameworks/` (not in the npm package): an agent built with the
466
+ Vercel AI SDK (`ai` 7.0.153), with its tools put behind the stub writer
467
+ with `recordTools`. One order within the slip is run and receipted; one
468
+ over its limit is not run, leaves no stub, and the agent is told why; the
469
+ checker finds the book intact and within its slip, and the output says
470
+ that the slip was signed by a development stand-in, not a person. It
471
+ uses the SDK's own mock model and passes `telemetry: { isEnabled: false }`.
472
+ It runs with a guard (`_offline.mjs`) open from before any module is
473
+ loaded until the program ends, which replaces with refusing functions
474
+ Node.js's socket connections, `dgram` sends, `dns` look-ups (both
475
+ `Resolver` classes included), the functions that start a program (`child_process`, `ChildProcess.prototype.spawn` and, where it exists, `process.execve`),
476
+ `worker_threads.Worker`, `fetch`, `WebSocket`, and `process.binding`
477
+ for the bindings beneath them; the tests check that nothing was refused.
478
+ Its own `package.json` and lock file pin every version, so the library
479
+ keeps no dependencies. A new job in the test workflow runs it. In
480
+ `docs/guide.md`, the links to the examples and to the Python version's
481
+ examples now give their full addresses on GitHub, since `examples/` and
482
+ `python/` are not in the npm package.
483
+
484
+ - **A "try it" page** at prova.red/try/ (`site/try/`, live since 25 October 2026).
485
+ One button. Where the browser has Ed25519 and ML-DSA-87 built in (Chrome,
486
+ Edge and Brave 155, tried on 13 October 2026), the page makes a new
487
+ record with the development stand-in for a passkey, under a limit on
488
+ orders in GBP that the visitor may change, writes a receipt for each of
489
+ four orders (on purpose, even where the check before acting says no) and
490
+ checks the record. Elsewhere it reads the sample record and checks it one
491
+ entry at a time, and says why. The words about the record, what the page
492
+ does not do and what the check trusts are those of the path shown.
493
+ Nothing is sent, nothing is stored, and the record made has no seal; the
494
+ card of its top fingerprint is then headed "The top fingerprint". Linked
495
+ from the home page and the site map; its logic (`site/try/story.js`) is
496
+ tested in `test/try-page.test.mjs`. The site's style sheet gains the
497
+ checking page's rules for the checker's answer, scoped to the new page.
498
+
499
+ - **Signing moved to https://sign.prova.red** (25 October 2026). A browser
500
+ lets a passkey sign only for the website name it was made for and the
501
+ addresses under it, so a passkey made at prova.red/sign/ could be asked
502
+ to sign by any other page under prova.red (the list of threats, section
503
+ 3). The signing page is now served at sign.prova.red from its own
504
+ repository, https://github.com/provared/sign, which holds only that page
505
+ and the library's files, each a byte-for-byte copy of the files at tag
506
+ v0.6.0, and serves nothing else. prova.red/sign/ now says that
507
+ signing has moved and offers no signing (`site/sign/index.html`; the
508
+ publishing workflow no longer copies the signing page there). The
509
+ gateway's README points to the new address. (The recorder beside Claude
510
+ Code, the Hermes plugin and the signing page itself named the old
511
+ address until the change above; their READMEs said how to open such a
512
+ link at the new address.)
513
+ - The test workflow is no longer started by a push that changes only the
514
+ record page and the sitemap.
515
+
516
+ - On the "try it" page the checker's answer entry by entry is folded away behind "Show every entry", which opens in place; the story, the summary and what the check does not show stay open.
517
+ ## 0.6.0 (25 October 2026)
518
+
519
+ Version 0.6.0 adds three kinds of record and the services that use them: the denial (the agent's own side states that an action it was asked for was not taken), seals held elsewhere and handed over beside a book, and witness statements beside a seal, with consistency proofs; and, beside the library, a central sealer, a gateway that writes the receipts for agents that cannot be wrapped, and a recorder beside Claude Code that numbers its books and has them witnessed (none of these three programs is published). What a reader of a book made by 0.5.0 or earlier must know: every such book reads as it did, apart from the cases below; but a checker of 0.5.0 or earlier refuses a book that holds a denial, a seal that gives `book`, or a seal that holds `witnesses`, so a book made with these needs a checker of 0.6.0. The fingerprint of a tool call's arguments is now named `provared.arguments` by default, where it was `arguments`; the fingerprint itself is the same. And a checker of 0.6.0 refuses some time-stamps and keys that 0.5.0 accepted, each listed below: a time-stamp that is not strict DER, that names its signing certificate twice in the same form, or that is signed with Ed25519 under a method other than SHA-512 or with parameters; an ES256 key that is not a point on its curve; and an Ed25519 signature whose number S is not below the order L. A passkey and a time-stamp service that follow their standards produce none of these; the sample record gets the same findings and the same exit code from the command-line checker of both versions, and 0.6.0 adds only a line on witnesses and, with `--json`, the empty members for denials and witnesses.
520
+
521
+ - **The fingerprint of a tool call's arguments is named
522
+ `provared.arguments` by default**, in both languages (24 October 2026).
523
+ `recordTools`, `record_tools` and `record_function` name the one
524
+ document they write by default with the reserved name of section 27 of
525
+ the format description, where 0.5.0 and earlier named it `arguments`
526
+ (the default was kept in 0.5.0 so that the published framework packages
527
+ would not change what they write without a version of their own). The
528
+ fingerprint is the same; only the name changes, and every checker reads
529
+ both: one of 0.5.0 or later knows the reserved name, an earlier one
530
+ reads it as a label. A tool that names its own documents through
531
+ `details` is unchanged, and so are the recorder beside Claude Code, the
532
+ gateway and the Hermes plugin, which name their documents themselves.
533
+ No rule of the format changes. Shared test files: `tool-scenarios.json`
534
+ and `denial-scenarios.json` are made again; their stored answers differ
535
+ only in that name, and every record in them is signed afresh. The name
536
+ comes with the library: a framework package of an earlier version writes
537
+ the new name too once `provared` is upgraded to 0.6.0. The two published
538
+ framework packages gain versions that require it, `provared-langchain`
539
+ 0.2.0 and `pydantic-ai-provared` 0.2.0, whose READMEs say what changes,
540
+ and which can be installed only once `provared` 0.6.0 is published; `provared-agno` (not published)
541
+ requires 0.6.0 too. The Hermes plugin's bound is raised to below 0.7;
542
+ it does not use the default. The version in `package.json` and
543
+ `python/pyproject.toml` is now 0.6.0, so that the packages' bounds hold
544
+ against the library in this repository. Tests:
545
+ `test/documents.test.mjs` and `python/tests/test_record_function.py`.
546
+ - **One correction and three smaller changes from a narrow review of the
547
+ five fixes below (24 October 2026)**, in both languages. None changes
548
+ the bytes of any record; one widens what a checker accepts back to what
549
+ services in use write.
550
+ - *The order of a set is held only where a standard requires DER.* The
551
+ five fixes held every set the checker reads as a set to the order of
552
+ X.690, section 11.6, the list of certificates among them. The review
553
+ found three genuine time-stamps of 2017 from a public service, kept
554
+ as test data by the open-source Go library digitorus/timestamp
555
+ (nothing of which is copied here), that list their two certificates
556
+ in chain order, the signing certificate before its issuer, which is
557
+ the reverse of DER order: before the five fixes they checked as valid,
558
+ and the five fixes refused them in both languages. RFC 5652 requires
559
+ DER of the signed attributes alone, "even if the rest of the structure
560
+ is BER encoded" (section 5.3). So now the signed attributes (RFC 5652,
561
+ section 5.3) and every set inside them, the statement (RFC 3161,
562
+ section 2.4.2) and every set inside it, and each certificate (RFC
563
+ 5280, section 4.1) and every set inside it are held to that order;
564
+ the signed data's own sets (the list of fingerprint methods, the list
565
+ of certificates, the list of withdrawn certificates and the list of
566
+ signers) and the parts of a signer outside its signed attributes may
567
+ be in any order. Every other rule of strict DER still holds everywhere
568
+ (format description, section 21.2, item 1, which now names the sets
569
+ held to order). A test in each language builds a time-stamp with its
570
+ certificates out of DER order and expects it to pass; the made-up
571
+ time-stamps are enough for that.
572
+ - *What is walked under a tag that hides its kind*, said exactly. The
573
+ five fixes said such a part "is read as bytes only". In both
574
+ languages a part written in parts under such a tag has the parts
575
+ inside it read like any other (the review's probe, an `otherName`
576
+ holding a whole number not in its shortest form, is refused as such);
577
+ the tagged part itself is only neither refused as a string written in
578
+ parts nor held to the order of a set; and one written in one piece is
579
+ read as bytes only. The format description, the code's own notes and
580
+ the two entries below now say so.
581
+ - *RFC 5652, section 11*, forbids a second instance, and a second value,
582
+ of the signing-time attribute too (section 11.3), not of the
583
+ content-type and message-digest attributes only; corrected below.
584
+ - *The count of sets in the real time-stamps*, below, was wrong: the
585
+ sample record holds one time-stamp (`samples/office-supplies.jsonl`,
586
+ line 10), repeated in its Show, not two; and the four real time-stamps
587
+ hold 43 sets between them, not 53. Each of the 43 is in DER order, so
588
+ the five fixes refused none of them.
589
+ - *Shared test files remade:* `stamps`, `stamps-openssl`,
590
+ `stamps-flips-ecdsa`, `stamps-flips-ed25519` and `stamps-flips-rsa`.
591
+ Three stored answers of `stamps` change in substance: "certificates:
592
+ two certificates out of DER order" and "wrapper: two fingerprint
593
+ methods listed out of DER order" are now valid, and "wrapper: the
594
+ signed data that is a set" is refused for its kind, not its order.
595
+ Fifteen changed-byte cases that tripped the order of a set before
596
+ anything else (four of `stamps-openssl`, six of `stamps-flips-ecdsa`,
597
+ three of `stamps-flips-ed25519`, two of `stamps-flips-rsa`) are now
598
+ refused for what the changed byte does next. Every other difference
599
+ is a made-up service's fresh key (`stamps-flips-ecdsa` has one case
600
+ more, its signature a byte longer). A re-check then found the three
601
+ re-walks held by no deliberate case (weakened, every test still
602
+ passed), so `stamps` gains three refused cases, each caught in both
603
+ languages: a message-digest attribute whose second, larger value
604
+ stands before its first; a relative distinguished name out of DER
605
+ order inside a certificate; and one inside the statement's name of
606
+ the service. The JavaScript unit test builds the first of these too,
607
+ and checks that the list of fingerprint methods may be in any order,
608
+ as the Python test did. The tests are `test/audit-fixes.test.mjs` and
609
+ `python/tests/test_audit_fixes.py`.
610
+ - **Five fixes from the independent review of the audit fixes (24 October
611
+ 2026)**, in both languages. None changes the bytes of any record; four
612
+ narrow what a checker accepts, and one adds to the shared test files.
613
+ - *Strict DER is now what the checker enforces.* A string of bits, bytes
614
+ or characters, or a time, written in parts (the constructed form,
615
+ which X.690, section 10.2, forbids in DER) is refused
616
+ (`stamp-bad-data`); before, a certificate whose signature was a bit
617
+ string in two parts passed in both languages. The items of a set must
618
+ be in the order X.690, section 11.6, requires, where a standard
619
+ requires DER of the set (narrowed to those places by the review
620
+ above; as first made, every set the checker read as a set was held to
621
+ it). Every real time-stamp the repository holds was checked first:
622
+ the three made with the OpenSSL program (`test/fixtures/stamps.json`)
623
+ and the one in the sample record (repeated in its Show) hold 43 sets
624
+ between them, every one in that order. The made-up services of the
625
+ tests now write their sets in that order, as a real service does;
626
+ before, the one that makes the shared test files wrote the signed
627
+ attributes of its SHA-512 cases out of order (format description,
628
+ section 21.2, item 1).
629
+ - *A time-stamp signed with Ed25519 gives its fingerprint method and
630
+ its signing method no parameters*, as RFC 8419, section 3.1, requires
631
+ (`stamp-invalid`); before, only the method's name was checked, and
632
+ the made-up services wrote NULL parameters, which that section
633
+ forbids. The refusal has words of its own that name the cause (a
634
+ method other than SHA-512, or parameters), where before it said the
635
+ signature did not fit the certificate. The list of fingerprint methods
636
+ in the signed data is read for its form only (section 21.2, item 4).
637
+ - *A second written form of a P-256 key* is in the shared test files:
638
+ the point with x = 5 and the point with y = 5, each as itself
639
+ (accepted) and with that half written as 5 plus the prime (refused as
640
+ `bad-key`), so that a checker that reduced a half instead of refusing
641
+ it would fail `keys.json`. Before, every case at or above the prime
642
+ also failed the curve's equation.
643
+ - *Citations.* The one-instance rule for the signing-certificate
644
+ attribute is RFC 5035's (section 3 for the newer form; section 5,
645
+ which writes section 5.4.2 of RFC 2634, for the older), not RFC 5652
646
+ section 5.3's, which requires DER; RFC 5652 forbids a second instance,
647
+ and a second value, of the content-type, message-digest and
648
+ signing-time attributes (sections 11.1 to 11.3) and says nothing of
649
+ this one. Corrected in
650
+ the entry below, the format description, the list of threats and the
651
+ code. The format description now says that using the newer form where
652
+ both are present, without comparing the two, is this format's choice
653
+ (section 15, item 22): RFC 5035, section 7, lets a reader refuse such
654
+ a time-stamp.
655
+ - *Reach.* "Every bit string in a time-stamp" and "every part is read"
656
+ now say what the checker reads as such: a value inside an octet string
657
+ (an extension's value) is read as bytes only, as is a part written in
658
+ one piece under a tag that hides its kind; one written in parts has
659
+ the parts inside it read like any other (made exact by the review
660
+ above).
661
+ - *Shared test files remade:* `keys` (four cases added: the point of
662
+ P-256 with x = 5 and the point with y = 5, each as itself and with
663
+ that half written as 5 plus the prime); `der-strict` (thirteen added,
664
+ for strings written in parts and sets in and out of DER order; twelve
665
+ stored answers change: "a constructed octet string", "an unknown
666
+ constructed tag", which is a character string in parts, and ten random
667
+ cases are now refused, or refused in other words, for a string in
668
+ parts or a set out of order); `stamps` (eleven added, among them an
669
+ Ed25519 service with parameters in each place and sets out of DER
670
+ order; "attributes: in another order" is now refused as not DER,
671
+ "signing method: Ed25519 named with parameters" is now refused, and
672
+ the two Ed25519 cases that name another method have the new words;
673
+ the "unused bits" cases of a certificate's key are now two each, one
674
+ with the unused bit zero and one with it set, where before the answer
675
+ depended on the last bit of a key made at random);
676
+ `stamps-strict-keys` (the same split of its three "unused bit"
677
+ cases); `stamps-openssl`, `stamps-flips-ecdsa` and `stamps-flips-rsa`
678
+ (thirteen, fourteen and fifteen changed bytes of the real and made-up
679
+ time-stamps now trip the order of a set before anything else, and
680
+ fifty cases of the RSA set hit other bytes because its signed
681
+ attributes are now written in DER order); `stamps-flips-ed25519` (the
682
+ time-stamp is four bytes shorter, its two NULLs gone, so its cases do
683
+ not compare by name with the earlier set). The reviewer's probes were
684
+ run again on the fixed code: its 16 ways round the checks and 2,534
685
+ random cases give the same answer in both languages, words included.
686
+ The tests are `test/audit-fixes.test.mjs` and
687
+ `python/tests/test_audit_fixes.py`; each new refusal fails without its
688
+ fix.
689
+ - **Eight fixes from the audit of the Internet-Draft (24 October 2026)**,
690
+ in both languages, for version 0.6.0. None changes the bytes of any
691
+ record. Five narrow what a checker accepts, so that two checkers built
692
+ from the format description cannot disagree; three change the documents
693
+ only.
694
+ - *A time-stamp that names the certificate that signed it more than once
695
+ in the same form* (two ESSCertIDv2 attributes, or two ESSCertID
696
+ attributes) is refused (`stamp-invalid`), as RFC 5035 requires
697
+ (section 3 for the newer form; section 5, which writes section 5.4.2
698
+ of RFC 2634, for the older). Before, the last ESSCertIDv2 was used or,
699
+ where there was none, the first ESSCertID. Where both forms appear
700
+ once each, the newer still names the certificate (format description,
701
+ section 21.2, item 3).
702
+ - *A DER bit string whose unused bits are not zero* is not strict DER
703
+ and is refused (`stamp-bad-data`), as X.690, section 11.2.1, requires;
704
+ before, only the count of unused bits was checked. A string of no bits
705
+ must count none. This reaches every bit string the checker reads as
706
+ such (not one inside an octet string's value, such as an extension's,
707
+ nor one written in one piece under a tag that hides its kind), not
708
+ only the key's (section 21.2, item 1).
709
+ - *An ES256 key that is not a point on the curve P-256*, or either half
710
+ of which is not below the curve's prime, is refused as `bad-key`
711
+ wherever a key is checked, before any signature is tried (section
712
+ 3.8). Before, it passed the key check and was reported as
713
+ `signature-invalid` when a signature was checked under it. A slip, an
714
+ approval, a cancellation or a vouching record that names such a key
715
+ now fails on the key.
716
+ - *An Ed25519 signature whose number S is not below the order L* is
717
+ refused by the library itself, in every record and in a time-stamp, as
718
+ RFC 8032, section 5.1.7, requires (section 3.8). Before, both
719
+ languages relied on the library beneath them (OpenSSL), which refuses
720
+ it; a third implementation on another library might not. The twelve
721
+ cases of the paper "Taming the many EdDSAs" (Chalkias, Garillot, Kondi
722
+ and Nikolaenko, 2020) are a new shared test file,
723
+ `test-vectors/ed25519-signatures.json`, with ten cases made here, each
724
+ with the answer the format's rules give, so that the rules are pinned
725
+ and not the library: a small-order or non-canonical key is `bad-key`;
726
+ a small-order or non-canonical R, or an S not below L, is invalid; a
727
+ mixed-order key is checked without the cofactor and may pass.
728
+ - *A time-stamp signed with Ed25519 must name SHA-512* as the signer's
729
+ fingerprint method, as RFC 8419, section 3.1, requires; one that names
730
+ SHA-256 or SHA-384 is refused (`stamp-invalid`), on every device, as a
731
+ mismatched method is for RSA and ECDSA (section 21.2, item 4). This
732
+ settles open issue 4 of the Internet-Draft. A service that follows RFC
733
+ 8419 produces nothing else; the made-up service of the tests now does
734
+ the same, and writes the method with no parameters, as that section
735
+ also requires (see the entry above).
736
+ - *The format description* now says that a time-stamp's time is read to
737
+ the millisecond and compared in milliseconds between seals (sections
738
+ 21.2 and 21.3), which is what the code always did ("taken to the
739
+ second, rounded down" described only how it is shown); that a checker
740
+ SHOULD bound the total size of a book it accepts and MAY stop at the
741
+ first problem, since 100,000 entries of 131,072 bytes would be about
742
+ 13 gigabytes (section 9); and the rules above, in sections 3.8, 14, 15
743
+ and 21.2.
744
+ - *The list of threats, section 3,* states a limit the audit found: a
745
+ passkey used for anything else at the same website is a signing
746
+ oracle. Any page at an origin under the slip's `rpId` can hand the
747
+ device a record's fingerprint as an ordinary sign-in challenge and have
748
+ it signed; the person sees a sign-in prompt. So records should be
749
+ signed only with a passkey registered to a relying party used for
750
+ nothing else, and a signing page should show the record first. The
751
+ checker has no option yet to name the origin a reader expects.
752
+ - *Shared test files remade:* `keys` (six cases added for P-256);
753
+ `der-strict` (six added; "a bit string with 7 bits unused" and
754
+ thirteen random cases now refused); `stamps` (nine added and two
755
+ removed, the sound Ed25519 cases being remade with SHA-512; the four
756
+ cases with the signing-certificate attribute twice now refused);
757
+ `stamps-strict-keys`, `stamps-openssl`, `stamps-flips-ecdsa`,
758
+ `stamps-flips-rsa` and `stamps-flips-ed25519` (changed bytes that now
759
+ trip the bit-string rule first; the Ed25519 token names SHA-512);
760
+ `vouching-contents` and `vouchings-written` (the made-up passkey is
761
+ now a point on the curve). New: `ed25519-signatures`. The tests are
762
+ `test/audit-fixes.test.mjs` and `python/tests/test_audit_fixes.py`;
763
+ each refusal fails without its fix.
764
+
765
+ - **Witnesses, and consistency proofs** (format description, section 29),
766
+ in both languages. Whoever keeps a book could show one history to one
767
+ reader and another to another, each sealed. A witness is a party, with
768
+ a key set of its own, that keeps for each book the latest seal it has
769
+ signed for, and signs a statement for a new seal only where the new
770
+ seal extends that one.
771
+ - *Consistency proofs*, exactly as RFC 9162, section 2.1.4, sets out:
772
+ `consistencyProof(leaves, m)` and `verifyConsistency(m, n, rootM,
773
+ rootN, proof)` (Python: `consistency_proof`, `verify_consistency`).
774
+ For m equal to n the proof is empty and the roots must be the same;
775
+ for m of 0 the proof is empty and the earlier root must be that of the
776
+ empty tree. A second implementation, written in the tests from the RFC
777
+ alone, is compared with the library's for every pair of sizes up to 64
778
+ leaves, with every value of every proof changed and left out.
779
+ - *A new kind of record, the witness statement*: label
780
+ `vnd.provared.witness.v0+json`, two signatures (Ed25519 and
781
+ ML-DSA-87), content `type`, `id`, `seal` (the seal's fingerprint),
782
+ `size` and `root` (repeated from the seal), `by` (`{keys, name}`)
783
+ and `when`. `writeWitness(fields, signers)` (Python:
784
+ `write_witness`); signers may be keys or signer objects. There are now
785
+ twenty-six labels.
786
+ - *Beside a seal*: a seal's line may hold `witnesses`, 1 to 4
787
+ statements, none from the same key set twice; and a seal handed over
788
+ beside the book (`seals`, `--seal`) may hold statements that came
789
+ later. A checker checks each: its signatures, that it speaks of that
790
+ very seal (its `seal`, `size` and `root`), and that it is not dated
791
+ more than 300 seconds before the seal. One new code,
792
+ `witness-invalid`; it joins the format's own list of finding codes.
793
+ A statement that does not pass is a problem with its entry, or, handed
794
+ over, with the whole check (the seal it came with is used all the
795
+ same).
796
+ - *Which witnesses count*: the option `witnesses` (the fingerprints of
797
+ the key sets of the witnesses the person trusts), `--witness
798
+ <fingerprint>` on the command line (may be repeated), and a box on the
799
+ checking page. A statement counts only where its witness is named,
800
+ both its signatures were checked on the device, **the book's first
801
+ line is a permission that passed, signed by a passkey the person
802
+ named (`issuerKeys`), the person named the recorders they expect
803
+ (`sealKeys`)**, and its seal passed with its Ed25519 and ML-DSA-87
804
+ signatures checked there (its third, SLH-DSA, may not be: the Python
805
+ version and browsers count on the first two). A witness knows a book
806
+ only by its permission and its recorder's keys, which the recorder
807
+ chooses; without both fixed by the reader, a recorder could start a
808
+ second history with another first line and have the same witness
809
+ sign for it (found by the independent review). Where a statement
810
+ from a trusted witness is not counted for that reason, the answer
811
+ says why. One from a witness not named is shown and not counted.
812
+ - *What the answer says*: each seal entry, and each seal handed over,
813
+ gains `witnesses` (each statement with its fingerprint, its witness's
814
+ key set fingerprint, its name as a label, its `when`, its signatures,
815
+ `state`, `trusted` and `counted`); the summary gains `witnessed`: the
816
+ latest seal for which a statement counts, the entries it covers, the
817
+ witnesses, and every seal witnessed, or null. Where a seal is
818
+ witnessed, the answer's list of what the check does not show says what
819
+ a witness shows and what it does not. Being witnessed excuses nothing,
820
+ is never said of a book with a problem, and is not needed for exit
821
+ code 0. The command-line checker prints "Witnessed by a witness you
822
+ trust?".
823
+ - *The witness itself*, in the library (it needs Node.js, because it
824
+ checks SLH-DSA): `openWitness({ by, signers, state, clock })`, with
825
+ `witness(entry, proof, firstLine)`, `update`, `ready`, `latest(book)`
826
+ and `state()`. It knows a sequence of books by the recorder's key set
827
+ with the passkey and the agent of the permission on the first line
828
+ (`bookIdentity`); ties the first
829
+ line to the seal with an audit path when it first sees a sequence and
830
+ at each new book, and keeps it
831
+ tied with each consistency proof (`witnessProof` makes the proof to
832
+ send); signs only for a seal whose three signatures it checked, which
833
+ names the seal it holds, is not dated before it, extends it, and is
834
+ dated no more than 300 seconds after its clock; never signs two
835
+ different seals of one book at one size (the same seal again gets the
836
+ same statement); checks every signature it makes before it keeps or
837
+ gives out a statement; and hands back its state with each statement,
838
+ to be saved first. Opened from a saved state, it signs nothing until
839
+ it has been brought up to date from a copy of what it gave out
840
+ (`update`) and told so (`ready`). A witness running as a service is
841
+ not part of this change. The Python version has no witness.
842
+ - The check before acting, the stub writer and a checker that is
843
+ carried on read statements as a whole check does; a statement's
844
+ signatures that the device cannot check leave the record not fully
845
+ checked, as for any record.
846
+ - *The books of one permission* (format description, section 21.6): a
847
+ seal may give `book`, its book's number among the books its recorder
848
+ keeps under one permission (2 or more; absent for the first book, so
849
+ every seal written before reads as it did; `1` is refused, and a seal
850
+ with `book` must give `previous`). The first seal of a later book
851
+ names, as `previous`, the last seal of the book before: a checker of
852
+ that book alone does not compare it, says so, and, handed that seal,
853
+ says whether the book follows on from it (`seal-chain-broken` where it
854
+ is by other keys, `time-went-backwards` where it is dated after the
855
+ first seal); a seal of another number is not compared by its place.
856
+ Every seal of a book gives the same number (`seal-chain-broken`).
857
+ `writeSeal` takes `book` (Python: `write_seal`). The recorder beside
858
+ Claude Code does not yet number its books.
859
+ - *The witness keeps every book of one recorder, person and agent as
860
+ one sequence*: it knows a sequence by the recorder's keys with the
861
+ passkey and the agent's key set of the permission on the book's first
862
+ line, so a renewal (another permission from the same passkey to the
863
+ same agent) is the same sequence, and a book that starts with it must
864
+ follow on from the seal the witness holds (found by the second round
865
+ of the review: known by the permission alone, a book that began with
866
+ a renewal forked the history). It keeps no book whose first line is
867
+ not a permission (`slip-missing`; a first line given with its line
868
+ feed is `bad-field`), signs for the first seal of the next book only
869
+ where that seal names the latest seal it holds and its first line is
870
+ tied to it, and never again signs for the book before; a fork across
871
+ books is refused. Its state names each sequence by its passkey and
872
+ agent. One recorder key set keeps one sequence for each person and
873
+ agent: books kept side by side for one agent need recorder keys of
874
+ their own.
875
+ - *Every permission before a witnessed seal must name the first line's
876
+ passkey and agent* (found by the third round of the review: a book
877
+ that started with agent Y's permission, then held agent X's
878
+ permission and stubs with the one over the limit left out, was signed
879
+ as the first book of the person and agent Y and read as witnessed).
880
+ A statement now counts only where every permission among the entries
881
+ its seal covers names the same passkey key and agent key set as the
882
+ first line's; otherwise a note says why. A renewal by the same person
883
+ for the same agent, and each agent's own book under one recorder (as
884
+ the gateway keeps them), are witnessed as before. A Show counts a
885
+ statement only where its pages hold every entry the seal covers, so it
886
+ never counts one the whole book would not. The witness cannot apply
887
+ this itself: it sees a book's first line, not its entries.
888
+ - *A seal held elsewhere that gives another book number than the book
889
+ is not compared with it, and gives no pass* (found by the second
890
+ round of the review: a book cut short and sealed again as "book 2"
891
+ otherwise hid the cut). It is not used; its note says what it shows
892
+ and that this book does not show whether that book was cut short;
893
+ the command-line checker says "Seals held elsewhere compared? NO" and
894
+ does not end with code 0, and the checking page gives no green
895
+ answer.
896
+ - New shared test files: `tree-consistency`, `tree-consistency-checks`,
897
+ `witness-contents`, `witnesses-written`, `witness-books`,
898
+ `witness-shows`, `witness-before`, `seal-books`, `chained-books`,
899
+ `chained-shows`, `renewal-books`, `renewal-shows`, `two-agent-books`
900
+ and `two-agent-shows`. **Every stored answer of a whole
901
+ book, a Show or the stub writer changes**, and only by the new
902
+ members: `summary.witnessed: null`, and `witnesses: []` on every seal
903
+ entry and every seal handed over; `denial-constants` gains
904
+ `witness-invalid`. Eleven files change, each checked case by case to
905
+ differ in those members alone (`books`, `denial-books`,
906
+ `denial-constants`, `denial-scenarios`, `denial-shows`,
907
+ `held-seal-shows`, `held-seals`, `recorder-details-after`,
908
+ `recorder-scenarios`, `shows`, `tool-scenarios`); no record was
909
+ signed again.
910
+ - Section 28's list of finding codes now also names `book-cut-short`
911
+ and `seal-not-in-book`, which the library already gave.
912
+ - **A checker of version 0.5.0 or earlier refuses a seal's line that
913
+ holds `witnesses`** (`unknown-entry`), **and a seal that gives `book`**
914
+ (`bad-field`).
915
+ - **A witness that runs as a service, and the recorder beside Claude Code
916
+ numbering its books and asking witnesses** (`integrations/claude-code`:
917
+ `witness.mjs`, the new command `provared-witness`, `witnessed.mjs` and
918
+ `proofs.mjs`). The package now needs `provared` 0.6.0 or later.
919
+ - *The recorder numbers and chains its books* (format description,
920
+ section 21.6). The first seal of a new book gives the book's number
921
+ and names the last seal of the book before; its later seals give the
922
+ same number. "The book before" is the latest kept book whose first
923
+ line is a permission from the same passkey, for the same agent, sealed
924
+ with the recorder's keys, as a witness knows a sequence; it is read
925
+ from the kept files, so the numbers hold across a restart, and a kept
926
+ book that cannot be read stops the seal rather than give it a number
927
+ that may be wrong. A book that begins with another passkey's
928
+ permission begins a sequence of its own, with no number. The first
929
+ book gives no number, so the record so far reads as it did, and a book
930
+ whose seals give no number goes on giving none. Before the book in
931
+ force is first sealed, every kept book a late call could still come
932
+ back to is closed, and sealed again where something came after its
933
+ seal; such a call is then recorded in the book in force. Every seal's
934
+ line is made sure to be on the disk (fsync), and a book is kept as
935
+ `book-<n>.jsonl` only once it is.
936
+ - *`provared-witness`*, a small service on 127.0.0.1 (port 8796 by
937
+ default) around the library's witness (`openWitness`), in a folder of
938
+ its own: its keys (Ed25519 and ML-DSA-87), a token for the recorders
939
+ it serves and another for its operator, its settings (`witness.json`:
940
+ its name, the fingerprints of the recorders it serves, its port and
941
+ limits), a journal, a state and a checkpoint. For each new statement
942
+ the journal line and then the state are made sure to be on the disk
943
+ (fsync) before the statement is given out; what it says it holds
944
+ (`/latest`, `status`) is what it last saved, never what the library's
945
+ witness holds in memory, and once anything cannot be saved it gives
946
+ out nothing more, by any route, until it is started again. Every 256
947
+ journal lines it writes a checkpoint (the latest seal of each sequence,
948
+ with its statement) and begins the journal again; a start reads the
949
+ journal a line at a time, and in full only the lines its state does
950
+ not yet reflect. At every start, the first included, it signs nothing
951
+ (`witness-not-ready`) until its operator says that its folder was not
952
+ put back to an older copy (`ready`, or `start --ready`); `update <book>
953
+ [--seal <file>]` hands it, from a copy, the statements it signed for a
954
+ book's seals, and `forget <sequence>` makes it hold a sequence no more
955
+ (it then signs whatever it is next shown for that sequence). It
956
+ refuses to start where its journal has gone while its keys are there,
957
+ where a journal line is not one it writes or lines are numbered out of
958
+ order, where its state reflects more lines than the journal holds, or
959
+ where the journal follows a checkpoint it does not have; a last journal
960
+ line cut off by a stop is taken off; a lost state is rebuilt from the
961
+ checkpoint and the journal; a stop between the steps of a checkpoint
962
+ is mended at the next start. One witness at a time on a folder
963
+ (`witness.lock`, taken over from a process that has ended under a
964
+ second file only one start can make). Who may ask: a program proves
965
+ that it holds the token, which never travels, as for the recorder's
966
+ feed (a hello, then HMAC-SHA-256 over the question, its body included,
967
+ and over the answer), one question to a conversation; the operator's
968
+ commands (`status`, `ready`, `update`, `forget`, `stop`) take the
969
+ operator's token; it signs only for seals of the recorders named, and
970
+ starts a new sequence only from a permission whose passkey signature
971
+ the library's checker passes. Limits: 120 new statements in any hour
972
+ (shared by every recorder it serves), 64 sequences, 8 questions that
973
+ sign at once, bodies of 1 MiB. Library refusals keep the library's
974
+ codes.
975
+ - *The recorder asks the witnesses named in `witnesses.json`* (at most
976
+ four, each on 127.0.0.1, with the fingerprint of its key set and its
977
+ token; written by the new commands `add-witness`, which checks that the
978
+ witness answers, holds the token and signs with that key set, and
979
+ `remove-witness`). After each seal it asks each witness what it holds
980
+ and whether it signs now, and shows one that does, in order, the seals
981
+ it has not signed for whose lines are on the disk, with the proof RFC
982
+ 9162 sets out, made from the book's line fingerprints kept as the book
983
+ grows (`proofs.mjs`, the same proofs as the library's `witnessProof`
984
+ without reading the book again), and the book's first line; a witness
985
+ that holds none of this sequence is shown the latest only. For the
986
+ seal that `seal` makes, the seal's entry is first kept apart on the
987
+ disk (`pending-seal.json`, written at its place at the next start if a
988
+ stop comes first, or let go where its line is in the book or in a book
989
+ kept since; one at a time), the witnesses are asked, and the
990
+ statements that come within five seconds stand beside the seal on its
991
+ line; the wait ends at once when any other request arrives, so no tool
992
+ call waits for a witness. A statement is taken only where the
993
+ library's checker passes it for that seal, as signed by the witness
994
+ named; one that comes later is kept beside the book in
995
+ `witnessed/<seal>.json`, in the form the checker takes with `--seal`,
996
+ and `check` hands those of the book checked over, names the witnesses,
997
+ and says plainly that those copies are the book's own seals from the
998
+ recorder's folder, not seals held elsewhere, and cannot show the book
999
+ cut short. A witness that is down, refuses, could not save, or hands
1000
+ back what does not pass never stops recording: `status` shows, for
1001
+ each, the seal it holds, whether that is the latest, and why not; one
1002
+ that holds a seal of this sequence in none of the recorder's books is
1003
+ reported (`witness-holds-another`) and is sent no seal. The record page
1004
+ tool, tried on a numbered book with statements, reads the books as
1005
+ before (it names no witness, and hands over no statement kept beside
1006
+ the book); the application's uploader reads the books through the
1007
+ feed, which is unchanged. Measured on a book of 93.5 MB with four
1008
+ witnesses: a tool call's two questions took at most 67 and 99
1009
+ thousandths of a second after a seal; during any seal a call waits for
1010
+ the seal itself, about 3.3 seconds on such a book, with or without
1011
+ witnesses.
1012
+ - *After its independent review*: a statement that could not be
1013
+ journaled was still given out through `/latest`, and after a restart
1014
+ the witness signed a different seal at the same size (fixed: only what
1015
+ is saved is given out, and nothing once a save has failed); a folder
1016
+ put back to a copy taken before the first statement started ready and
1017
+ signed a fork (fixed: never ready at a start until told); the journal
1018
+ was read whole at each start and could no longer be read at about
1019
+ 8,000 lines (fixed: checkpoints, and a line at a time); proofs read the
1020
+ whole book each time (fixed: line fingerprints kept); and the low
1021
+ findings on the seal kept apart, the lock, the agent in the book
1022
+ before, `check`'s words and the passkey signature of a new sequence.
1023
+ - Stated: on one computer under one user account the agent can reach
1024
+ the witness's folder, so a witness means more under another account,
1025
+ on another computer through a tunnel, or run by someone else; a
1026
+ witness on another computer reached directly is not built; what
1027
+ travels is proved, not encrypted; one token serves every recorder the
1028
+ witness serves, and each can read what it holds for any of them, fill
1029
+ its sequences with permissions signed by passkeys of its own, and use
1030
+ up the shared statements; a witness put back to an older copy and told
1031
+ it is ready can be made to sign a fork (a test shows it); forgetting a
1032
+ sequence a recorder still uses lets that recorder have a second
1033
+ history witnessed. 51 new tests in the package (181 in all). Of 61 checks broken in turn, before and after the review, 59 are caught by a test; the two that are not (a stale lock taken without the second file, which only a race between two starts reaches, and the in-time ask kept to a book that passes its check, which no book the recorder holds fails) are stated.
1034
+ - **The recorder beside Claude Code offers its books to a reader on this
1035
+ computer, through a feed on its own port** (`integrations/claude-code`,
1036
+ `feed.mjs`), so that no other program need open a book file. On
1037
+ 11 October 2026 an uploader reading `book.jsonl` made the recorder's
1038
+ rename of it fail on Windows, and seven receipts were lost; the
1039
+ application's uploader now asks the recorder instead. `GET /books`
1040
+ lists the books oldest first (the kept ones by number, the one in force
1041
+ last), each named by its file's identity (the volume, the file's number
1042
+ and its creation time, which a rename keeps, reckoned as the uploader
1043
+ reckons it), with its size; `GET /lines?id=<identity>&from=<n>` (or
1044
+ `book=<name>`, or neither for the book in force) answers with up to 500
1045
+ lines (and at most 4 MiB) from place n, where each is in the file, the
1046
+ line before place n with its fingerprint, the number of lines and the
1047
+ fingerprint of the last; `max` asks for fewer, and 0 for none. Lines are
1048
+ counted as the uploader and the checker count them (an empty line and a
1049
+ carriage return set aside, a line with no line break not yet a line).
1050
+ Where the book holds nothing at place n yet, the question waits up to
1051
+ 25 seconds (`wait`) and is answered as soon as any book is written, a
1052
+ late receipt or a seal again in a kept book included; at most 8 wait at
1053
+ once, and one more is told to ask again (429). The recorder writes a
1054
+ token into its folder at its first start (`feed-token`, 32 random
1055
+ bytes, readable by its owner only where the file system keeps such
1056
+ rights), and **the token never travels**: a reader opens a conversation
1057
+ (`GET /hello`) with a random number; the recorder answers with a random
1058
+ number of its own and HMAC-SHA-256 over both, keyed by the token, and
1059
+ the reader trusts nothing until that proof checks; each question then
1060
+ carries an HMAC under a key both sides work out from the token and the
1061
+ two numbers, with a number used once, and each answer an HMAC under the
1062
+ same key over the question's number, its code and every byte; every
1063
+ proof is compared in constant time, and a question asked twice is
1064
+ refused. Each of these 32-byte values is read only in the one way it can
1065
+ be written in base64url, so none can be given again under another
1066
+ spelling of the same bytes. A hello keeps nothing: the recorder's number for a conversation
1067
+ is worked out from the time, the reader's number and a secret only the
1068
+ running recorder holds, and a conversation is kept only from its first
1069
+ proved question; only proved conversations (at most 16, each ended
1070
+ after 10 minutes unused) give way to one another, so a program without
1071
+ the token cannot push out one in use. Each question, refused or not,
1072
+ still costs the recorder about a thousandth of a second, so very many at
1073
+ once slow the hook in proportion. A token file that holds no token is not replaced, and the feed
1074
+ then refuses every question and `status` says why. One answer is made
1075
+ at a time, with a pause between two, and at most 16 are taken on at
1076
+ once (429 beyond). The books the recorder holds open are read from its
1077
+ own copy in memory, only as far as it has written them to their files,
1078
+ so the feed answers while another program holds `book.jsonl` open with
1079
+ no sharing and never gives out a receipt whose save failed; a kept book
1080
+ it no longer writes is read from its file, 4 MiB at a time, and closed
1081
+ again. Measured on the author's computer with a book of 98 MB: about
1082
+ 110 thousandths of a second for the first question, 1.8 seconds to read
1083
+ the whole book; a hook's two questions took a median of 61 thousandths
1084
+ of a second with no reader and of 160 (at most 266) with two readers
1085
+ reading the whole book again and again, and a hook's question 971
1086
+ thousandths with 1,500 feed questions at once. The service still listens
1087
+ on 127.0.0.1 only. Nothing of the record or the format changes.
1088
+ - **The recorder never replaces its book because it could not read it**
1089
+ (`integrations/claude-code`). Where another program held `book.jsonl`
1090
+ for a few seconds while a receipt was saved, the recorder took the
1091
+ failed read as "no book yet" and replaced the whole file, which then had
1092
+ a new identity; an uploader following it stopped for good. Now the
1093
+ reading and the adding are tried again for up to ten seconds while the
1094
+ file is busy, a read that fails otherwise fails the save and leaves the
1095
+ file as it is, and the file is replaced only where it was read and no
1096
+ longer holds the book's start. Found by the independent review of the
1097
+ feed, with four faults of the feed itself, each fixed and tested: the
1098
+ token sent to whatever listened (now never sent); questions that do not
1099
+ wait had no limit (on a 98 MB book, 1,500 at once held a hook up for 22
1100
+ seconds); a receipt whose save failed could be given out; and a kept
1101
+ book of exactly 4 MiB that ended part way through a line lost that
1102
+ line's start. A second review found that a hello kept a conversation,
1103
+ so that a program without the token could push out the uploader's (now
1104
+ a hello keeps nothing). Where a save fails after the ten seconds, the
1105
+ hook is now told the truth: the stub is signed and held by the
1106
+ recorder, not yet in the book file, and written with the next save, and
1107
+ `status` counts it as recorded. A third review found that an ended
1108
+ conversation could be begun again under another spelling of its number
1109
+ (now refused), and that no test held the refusal of a hello dated ahead
1110
+ of the clock (now tested). 37 tests of the feed; the package's 130 pass.
1111
+ - **Seals held elsewhere, handed over beside the book** (format
1112
+ description, section 21.5), in both languages. Whoever keeps a book
1113
+ could cut off its end, the last seal with it, and what is left still
1114
+ checks. Now anyone who keeps a copy of a seal (the person, an auditor, a
1115
+ second copy out of the recorder's reach) can hand up to 64 of them to
1116
+ the checker, each as the entry it is in the book: the option `seals`,
1117
+ `--seal <file>` on the command line (may be repeated), and a file
1118
+ chooser in the checking page's panel "What you were handed with the
1119
+ record". A seal at entry n must stand at entry n of the book. Two new
1120
+ codes, both problems with the book: `book-cut-short` (the book ends
1121
+ before that place) and `seal-not-in-book` (the entry there is not a
1122
+ seal with the same content; the same content with other signatures is
1123
+ the same seal, and is said). Nothing in a seal names its book, so a
1124
+ seal of another book by the same recorder is reported against the book
1125
+ being checked. Each seal handed over is checked as any seal is, once
1126
+ for a checker that is carried on and for the stub writer; it counts
1127
+ only where its recorder is named in `sealKeys` (`--sealer`), and on a
1128
+ device without SLH-DSA (the Python version, and browsers that have
1129
+ ML-DSA) on its Ed25519 and ML-DSA-87 signatures, which the answer says.
1130
+ Seals handed over without `--sealer` give no pass (code 1), and a line
1131
+ of their own says why, on the command line and on the checking page. One that does not pass its own check is a problem with
1132
+ the whole check. Its own time-stamps are shown, and are not used for the
1133
+ entries of the book; where they differ from those the book holds beside
1134
+ the same seal, the answer says so. A Show does not use seals held
1135
+ elsewhere, and says so. Each one is given in the answer's `held` list,
1136
+ after any cancellations, with `kind: "seal"`, `place`, `inBook`,
1137
+ `sealer`, `used`, `verified`, its signatures and time-stamps; the
1138
+ type declarations give `held` as `(HeldCancellation | HeldSeal)[]`.
1139
+ The check before acting and the stub writer take the option too, and a
1140
+ seal held elsewhere whose third signature this device cannot check is
1141
+ excused there as a seal in the book is. No new kind of record. The two
1142
+ codes join the format's own list of finding codes (`FINDING_CODES`).
1143
+ New shared test files: `held-seals`, `held-seal-shows` and
1144
+ `held-seals-before`; `denial-constants` changes for the two new codes,
1145
+ and no other shared file changes.
1146
+ - **A new kind of record: the denial** (format description, section 28),
1147
+ in both languages. It is the agent's own side's signed statement that it
1148
+ did not take an action that was asked for, and who said no: the check
1149
+ before acting (with the finding codes it gave, from the format's own
1150
+ list, now written out in section 28), the person, or a rule of the
1151
+ software around the agent. It is signed with the agent's two keys, or a
1152
+ helper's under its pass, with the new labels
1153
+ `vnd.provared.denial.v0+json` (twenty-four labels in all). In a book it
1154
+ comes after its slip, is in no chain and is compared with nothing; a
1155
+ checker shows it as the agent's side's own word, counts it
1156
+ (`summary.counts.denials`, new in every answer), and never counts it
1157
+ against the agent. `writeDenial` (`write_denial`), `denialCodes`,
1158
+ `FINDING_CODES` and `DENIAL_SOURCES` are new. The stub writer writes a
1159
+ denial whenever `act` does not take an action if it is opened with
1160
+ `denials: true` (`more.denial: false` writes none for one call);
1161
+ `deny` writes one for a refusal from elsewhere; at most
1162
+ `denialsPerHour` (600 by default) are written in any hour of the
1163
+ writer's clock, each counted at the clock's time when it joins the
1164
+ book (whatever date it states), the answers count those left out, and
1165
+ a stub is never kept out. `deny` records what its caller states. `recordTools` and
1166
+ `record_function` pass it on (`NotTaken.denial`), and where the person
1167
+ may yet approve a call they write none until it is known the call will
1168
+ not run, so one call never leaves both a stub and a denial. The
1169
+ command-line checker and the checking page show denials. **A checker of
1170
+ an earlier version refuses a book that holds a denial** (`unknown-entry`).
1171
+ New shared test files: `denial-contents`, `denials-written`,
1172
+ `denial-codes`, `denial-constants`, `denial-books`, `denial-shows`,
1173
+ `denial-before` and `denial-scenarios`; `books`, `shows`,
1174
+ `recorder-scenarios`, `recorder-details-after` and `tool-scenarios`
1175
+ gain `denials: 0` in their stored counts, and nothing else changes in
1176
+ them.
1177
+ - **`provared-hermes` 0.1.0, a plugin for Hermes Agent**
1178
+ (`python/integrations/provared-hermes/`): every tool call of a Hermes
1179
+ agent is checked against the slip before it runs, and a stub is
1180
+ written once it has run; a call the slip does not allow is not run,
1181
+ and the model is told why. It runs no recorded call on a Hermes it was
1182
+ not tested with (0.21.7 and later 0.21 versions) unless told to, and
1183
+ it loads on any Hermes so that its own check can refuse. Its commands
1184
+ are named by the same reading of the command text as the recorder
1185
+ beside Claude Code (below), with shared test rows that both languages
1186
+ answer alike. Checked by independent reviewers over eight rounds, the
1187
+ last six with the shared reading of commands, the last three clean;
1188
+ 71 tests. Not on the Python Package Index yet: the README gives the
1189
+ command to install it from this repository.
1190
+ - **The recorder beside Claude Code reads commands more closely**
1191
+ (`integrations/claude-code/`, not published), with the same reading
1192
+ as the Hermes plugin. A deletion or a publication is now named as one
1193
+ where it sits inside `"$( … )"` or back-quotes in double quotes, with
1194
+ quotation marks, `case` choices and comments inside; after a shell,
1195
+ `eval`, `ssh`, `watch` or another program that runs a command line;
1196
+ in a quoted command after `-c`, `-Command` or `-ArgumentList`; where a
1197
+ program is written as a path; or in text piped or handed into a
1198
+ shell. More publishing and deleting commands are named (among them
1199
+ `gem push`, `helm push`, `dotnet nuget push`, `docker buildx --push`,
1200
+ `rsync --delete`, `DELETE FROM` given to a database client and
1201
+ `redis-cli FLUSHALL`). Quoted words that run nothing, such as a commit
1202
+ message holding `rm`, are no longer named deletions; a back-quoted
1203
+ command in double quotes is, because the shell runs it. On a varied
1204
+ set of 11,340 deleting and publishing lines, none is named less
1205
+ strictly than before; on 109 everyday commands, 2 are named deletions,
1206
+ as before. The longest lines tried (about 900,000 characters) are read
1207
+ in under a second. The README lists what the reading still misses: a
1208
+ command line kept in a file or a variable, `-EncodedCommand`, and
1209
+ others. Writing the book file, the slip and the list of trusted
1210
+ passkeys is retried for up to ten seconds where Windows holds the file
1211
+ busy, and leaves no temporary file behind; a renewal held by a busy
1212
+ file can keep the service waiting about 40 seconds, during which calls
1213
+ are refused, never let through. Checked by an independent reviewer
1214
+ over six rounds, the last three clean; 60 tests.
1215
+ - **The recorder beside Claude Code starts a new book at 90,000 entries
1216
+ or 100 MB** (`integrations/claude-code/`, not published). A book holds
1217
+ at most 100,000 entries, and once full every call would be refused. At
1218
+ 90,000 entries or 100,000,000 bytes of text, whichever comes first, or
1219
+ on the command `new-book`, the recorder seals the book, keeps it beside
1220
+ the new one as `book-1.jsonl`, then `book-2.jsonl` and so on, and
1221
+ starts a new `book.jsonl` that begins with the permission in force,
1222
+ with no gap in recording; a call that ends during the switch is written
1223
+ into the kept book, which is then sealed again. Every limit is counted
1224
+ across the books, from the kept books' receipts and the calls still
1225
+ running, so a new book, by command, by size or after a restart, starts
1226
+ no limit afresh. A checker given one book still counts limits within
1227
+ that book only, so around a switch the record shown to a checker can
1228
+ hold up to twice a limit within one period across two books, though the
1229
+ agent was held to it. The README states what this does not close: a
1230
+ kept book moved, renamed or removed while the recorder is stopped is
1231
+ no longer counted, and nothing in the record shows it. The record page
1232
+ (`tools/make-record-page.mjs`) lists each kept book with its own
1233
+ checker's answer, and points out a gap in their numbers; the site's
1234
+ publishing run fetches them from the release "record". Checked by an
1235
+ independent reviewer over four rounds; 93 tests.
1236
+ - **The recorder beside Claude Code numbers its books, chains each new book
1237
+ to the last seal of the book before, and asks witnesses; and a local
1238
+ witness service, `provared-witness`** (`integrations/claude-code`:
1239
+ `recorder.mjs`, `witness.mjs`, `witnessed.mjs`, `proofs.mjs`; live for the
1240
+ published record since 24 October 2026, 15:38 UTC). A book's number
1241
+ follows the passkey of its first line: a book that holds a seal goes on as
1242
+ its first seal says; one that holds none takes the latest kept book's
1243
+ number and one, where that book began with a permission from the same
1244
+ passkey and its last seal is by this recorder's keys, and its first seal
1245
+ names that book's last seal; where there is no such book it gives no
1246
+ number, so the record so far reads exactly as it did and its next book is
1247
+ book 2. The numbers are read from the kept files, never kept in a file of
1248
+ their own. Before the book in force is first sealed, every kept book that
1249
+ a late call could still come back to is closed and, where something came
1250
+ after its seal, sealed again, so that the first seal of book n names the
1251
+ last seal of book n - 1; a kept book that cannot be read when that seal
1252
+ needs it stops the seal, and the error says why. Every seal's line is
1253
+ made sure to be on the disk, and a witness is shown only such seals. A
1254
+ renewal with a new passkey starts no new book; the next book then begins a
1255
+ sequence of its own.
1256
+ - *Witnesses*: `witnesses.json` names at most four, each
1257
+ `{address, key, token}` at `http://127.0.0.1:<port>`, written by
1258
+ `add-witness` (which reads the token from the witness's token file and
1259
+ checks that the witness answers, holds the token and signs with that key
1260
+ set) and `remove-witness`. After the seal that `seal` makes (the
1261
+ command, `POST /seal` or the service's own) the recorder keeps the seal
1262
+ apart on the disk (`pending-seal.json`) before any witness is asked,
1263
+ shows each witness every seal after the one it holds (at first contact,
1264
+ the latest only), and waits at most five seconds, and no longer than
1265
+ until any tool call arrives; statements that come in time stand on the
1266
+ seal's own line, and every other statement is kept beside the book in
1267
+ `witnessed/<seal fingerprint>.json`. Each statement is checked with the
1268
+ library's own checker before it is kept. It asks again after each seal
1269
+ and every minute while a witness is behind; a witness that holds a seal
1270
+ of this sequence that is in none of the recorder's books is reported
1271
+ (`witness-holds-another`) and asked no more. A seal kept apart that the
1272
+ book changed under while the recorder was stopped is moved aside
1273
+ (`pending-seal-not-placed-<time>.json`) and `status` says so. `check`
1274
+ hands over the kept statements of the seals in the book checked (the
1275
+ latest 64) and names the witnesses of `witnesses.json`, unless `--seal`
1276
+ or `--witness` is given. `status` gains `witnesses`, `witnessesProblem`
1277
+ and `pendingSealProblem`; `seal` answers with `book` and `witnessed`.
1278
+ - *The witness service* (`provared-witness`: `init`, `start`, `status`,
1279
+ `ready`, `update`, `forget`, `stop`) listens on 127.0.0.1:8796 with a
1280
+ folder of its own (`witness-keys.jwk`, two tokens, `witness.json`,
1281
+ `journal.jsonl`, `state.json`, `checkpoint.json`, `journal-mark.json`,
1282
+ `witness.lock`). One token is for the recorders it serves and one for
1283
+ its operator; questions and answers are proved as the feed's are
1284
+ (HMAC-SHA-256 under a key worked out from the token and two random
1285
+ numbers; one question to a conversation). It signs only for the
1286
+ recorders its operator named, by the fingerprint of their key sets, and
1287
+ only after the journal line, then the state, then the mark, are on the
1288
+ disk; after a save that fails it answers 503 until it is started again;
1289
+ at every start it signs nothing until its operator says it is up to date
1290
+ (`ready`), after taking its own journal again, and it refuses to start
1291
+ where the journal has gone while the keys are there, where a line is not
1292
+ one it writes, or where its state reflects more lines than the journal
1293
+ holds. Limits: 120 new statements in any hour, 64 sequences, 8 signing
1294
+ or updating questions at once, bodies of 1 MiB. There is no background
1295
+ start: whoever runs a witness decides how it is kept running. Stated in
1296
+ the README: a witness is only as sound as its folder, and whoever can
1297
+ change it, or put back an older copy whole, can make it forget a
1298
+ sequence.
1299
+ - The package depends on `provared` 0.6.0 or later. Three independent
1300
+ reviews: the first found one serious fault (a statement given out that
1301
+ was never saved, so after a restart the witness could sign a fork) and
1302
+ three medium; the second and third found nothing serious or medium.
1303
+ 189 tests, and the three test fixes of 21 October brought across.
1304
+ - **`provared-gateway` 0.1.0, a reverse proxy that writes the receipts**
1305
+ (`gateway/`, not published). It sits in front of the web interfaces an
1306
+ agent acts through, holds the agent's keys (in memory or through
1307
+ signers) and each interface's credential, and for every call: checks
1308
+ the agent's own secret, names the call from a rules file the company
1309
+ writes (paths by the WHATWG URL Pattern standard, amounts by a fixed
1310
+ value, a JSON Pointer of RFC 6901 or the length of a list), removes
1311
+ the agent's own credentials from the headers and the address, asks the
1312
+ stub writer's check before acting, sends the call, and writes a stub
1313
+ that names `provared.http-request`, `provared.http-response` or
1314
+ `provared.error`, and the upstream's own identifier where it gives one.
1315
+ A call that is not allowed is not sent, is answered `403` with the
1316
+ checker's words, and is written as a denial; a call that matches no
1317
+ rule is refused by default. A call once sent always gets its stub,
1318
+ whatever the upstream did. An upstream the company runs may
1319
+ countersign. Books are sealed on demand with the recorder's keys
1320
+ (`provared-gateway seal`); `provared-gateway check` says what each rule
1321
+ maps to and what is wrong. Each book is a file appended line by line;
1322
+ the feed to the application is not built. It is the gateway's record
1323
+ only, and proves nothing about calls that do not go through it, as its
1324
+ README says first. An independent review found one serious point: a
1325
+ body naming `hosts` and `HOSTS` was recorded with the first's amount,
1326
+ while an upstream that reads names whatever their case acts on the last.
1327
+ Now a body whose names some readers take as one (differing only in
1328
+ capitals, or in the long s or the Kelvin sign) gives no amount, and the
1329
+ call is not sent. Also from the review: method and address override
1330
+ headers and `_method` are removed, with every `X-Forwarded-*` and the
1331
+ other client-address headers; a `;` in a path is refused unless the
1332
+ upstream allows it, and then the rules match each segment without what
1333
+ follows its `;`; overlong UTF-8 and full-width `/` or `.` in a path are
1334
+ refused; each agent may have only so many calls and bytes waiting; a
1335
+ journal beside each book lets a start find a call that was sent when the
1336
+ gateway stopped, and stops that agent until the administrator
1337
+ acknowledges it (`provared-gateway acknowledge`), when a stub saying the
1338
+ outcome is not known is written; header names with `_` and a
1339
+ `Connection` header that names other headers are refused; more
1340
+ credential headers and query forms are removed; a character set other
1341
+ than UTF-8 gives no amount; a credential that cannot be sent as a header
1342
+ is refused at start; a stub for a call that never reached the upstream is
1343
+ labelled `gateway.not-delivered`; two agents may not share a secret or a
1344
+ book file; and the status answers while a call is out. A second review
1345
+ found two medium points in the journal: one call could get two receipts
1346
+ (a stop between its stub and the journal's "done" line, or an
1347
+ acknowledgement tried again after a failure), and any program that could
1348
+ write the journal could have the gateway sign stubs of its choosing. Now
1349
+ every stub names its call (a label, `gateway.call`), and a start takes a
1350
+ call whose stub the book holds as done; each journal line carries an
1351
+ HMAC-SHA-256 tag under a journal key of the agent's own, and a line
1352
+ whose tag does not check is never acted on; each line is checked against
1353
+ the rules before a stub is written from it; the status shows each
1354
+ waiting line whole; and the administrator can set aside a line
1355
+ (`acknowledge --set-aside`). Also from that review: JSON names are
1356
+ folded beyond ASCII (Greek final sigma, the dotless i and others), so an
1357
+ unrelated `ID` and `id` in one object now give no amount; an encoded
1358
+ character in a path whose NFKC form differs from itself is refused;
1359
+ `provared-gateway repair` takes off a book's last line that a stop left
1360
+ part-written; and two agents may not share a book, a journal, a journal
1361
+ key or a key file. A third review found one medium point: a stub
1362
+ written on acknowledgement went under the slip in force at the restart,
1363
+ not the one the call was sent under. Now each journal line names its
1364
+ slip, and the stub is written under that slip (by a stub writer for it,
1365
+ opened on the same book), or, where the book does not hold it, the line
1366
+ can only be set aside. Also from that review: two agents' files are also
1367
+ compared by each existing file's device and file numbers (a link to
1368
+ the file is followed), so, where the file itself exists, a hard link or
1369
+ a link to the file counts as the same file; a journal that none of its
1370
+ lines checks under the journal key in place stops the start; a
1371
+ journal that holds lines and has lost its key stops the start (each of
1372
+ these two stops the whole gateway, not that agent alone); a line
1373
+ whose rule was taken out can still be acknowledged, with a warning; a
1374
+ clock behind the journal's times is said plainly; calls set aside are
1375
+ kept in a file a clean start does not empty; the library's own checks of
1376
+ a request's fields decide whether a line is usable; the dotted capital
1377
+ `İ` folds with `i`; `repair` takes its byte count only in digits and can
1378
+ put back a lost line ending; and the README lists which honest path
1379
+ forms are refused. 68 tests, all on 127.0.0.1, with a file of 145 calls
1380
+ and the action, amount and service each must map to.
1381
+ - **A central sealer** (format description, section 21.7, new; no record
1382
+ changes). The recorder's three keys can now sit apart from the agent,
1383
+ with a party that never sees the book.
1384
+ - *In the library*: `leafFingerprint(line)` (Python:
1385
+ `leaf_fingerprint`), the SHA-256 of the byte 0x00 and one line of a
1386
+ book, refused where the line is empty, holds a line feed, is not
1387
+ well-formed Unicode or is over 131,072 bytes; and `openSealer({by,
1388
+ signers, stamp, state, clock, signWithin})`, which takes the leaf
1389
+ fingerprints of each book's lines (`extend(book, at, leaves)`, only
1390
+ at the place it holds, so a line is never taken twice), keeps the
1391
+ same running tree the checker keeps, and signs a seal over its own
1392
+ number of entries and top fingerprint (`seal(book, {previous})`),
1393
+ through the same keys and signers `writeSeal` takes, each signer
1394
+ given 30 seconds. The seal is the one `writeSeal` signs over the same
1395
+ book, with the same time and unique number. It signs only where
1396
+ `previous`, the seal the caller's book names, is the latest it
1397
+ holds of that book (`not-up-to-date`): a sealer whose state is
1398
+ behind the book signs nothing until it is brought up to date with
1399
+ `update(book, entry, leaves)`, from a later seal it signed and the
1400
+ leaf fingerprints since its own latest seal, which must give that
1401
+ seal's top fingerprint; it is never moved back. `latest(book)` hands
1402
+ back its latest seal, for a book that lost it. A book is known by
1403
+ its first line's leaf fingerprint and its number; a later book names
1404
+ the book it follows, its first seal names that book's last seal, and
1405
+ that book is then closed (also where that first seal is brought in
1406
+ with `update`, which waits, `not-up-to-date`, while the sealer holds
1407
+ that book at an earlier seal than the one named). Every book with one first line is one chain: a book
1408
+ whose first line the sealer holds starts only as the next book after
1409
+ the latest with that first line (`not-next-book`). One sealer may
1410
+ serve several recorders, through `forRecorder(name)`: a book, and
1411
+ every book after it, belongs to the recorder that started it
1412
+ (`other-recorder`). A recorder may be given rules,
1413
+ `forRecorder(name, {agents, books})`: the fingerprints of the key
1414
+ sets of the agents whose permissions it may start books with (an
1415
+ agent belongs to one recorder), and the most books it may hold
1416
+ (`too-large`). Where any recorder has a list of agents, every book is
1417
+ started with its first line itself (`line`, beside `extend`, or
1418
+ `update` for a book brought in), which must be a permission for one
1419
+ of the caller's own agents (`agent-not-allowed`, `other-recorder`);
1420
+ so no recorder can start a book whose first line is a permission for
1421
+ another recorder's agent, nor bring another's book in first after a
1422
+ lost state. The sealer then learns each first line; it does not check the
1423
+ passkey's signature. `releaseUnsealed({recorder})` takes off books
1424
+ that hold no seal, for which nothing was signed. A book started
1425
+ after a book that another book now follows takes no first seal
1426
+ (`book-closed`). Every seal is checked (all three signatures)
1427
+ before it is kept; the state is handed back with a seal, a book
1428
+ started and `update`, to be saved before the answer is given out. Of
1429
+ a seal entry brought in with `update` only the seal, whose three
1430
+ signatures it checks, and its time-stamps are kept, the time-stamps
1431
+ read for their form only (at most four, each at most 12,288 bytes,
1432
+ in base64url, none twice; no time-stamp's signature is checked); its
1433
+ witness statements are not, and an entry longer than a line of a
1434
+ book is refused (`too-large`), so that no recorder can make the
1435
+ state too large to save. The entry is read once, and what is
1436
+ measured, checked and kept is that one copy, so a member that gives
1437
+ another value at each reading cannot slip past the checks. A
1438
+ book is kept in the state at its latest seal, or with none at its
1439
+ first line, so a sealer started again asks for the lines since; at
1440
+ most 1,000 books, about 52 KB of state for each sealed book. With
1441
+ its state kept it never signs two histories of one first line; a
1442
+ book from another permission for the same agent that repeats sealed
1443
+ lines, and a lost or older state, still need a witness. It sees only
1444
+ fingerprints after the first line, so a recorder can start a book
1445
+ from a permission for its own agent, copy into it another
1446
+ recorder's agent's permissions and stubs with some left out, and
1447
+ have it sealed: where one person's passkey signed the permissions
1448
+ for both agents,
1449
+ the book reads as intact and within its slips, and a witness
1450
+ statement on its seal is not counted. A seal of the other
1451
+ recorder's own book held elsewhere shows it (`book-cut-short` or
1452
+ `seal-not-in-book`; where the two books' numbers differ, it cannot
1453
+ be compared, and gives no pass); readers of books from a shared sealer should
1454
+ hold each recorder's own seals elsewhere, or run a sealer for each
1455
+ recorder. It needs SLH-DSA built in, so
1456
+ Node.js; Python has no sealer. `writeSeal` and the sealer share one
1457
+ signing path (`sealOver`, not exported). The tree builder gains
1458
+ `addLeafHash` and `parts` (Python: `add_leaf_hash`).
1459
+ - *A new shared test file*, `sealer-trees.json`: lines or leaf
1460
+ fingerprints, and the number of entries and top fingerprint after
1461
+ each; both languages give every answer.
1462
+ - *`provared-sealer`* (`sealer/`, version 0.1.0, not published): the
1463
+ sealer as a program on 127.0.0.1 (port 8797 by default), with its
1464
+ keys in a file for its owner alone or reached through signer modules
1465
+ named in its settings, a token for each recorder, the agents whose
1466
+ permissions each recorder may start books with and the most books
1467
+ it may hold (100 unless it says; all recorders' books add up to at
1468
+ most 1,000), and one state for every recorder's books, each book
1469
+ bound to its recorder; `release-unsealed` takes off, while it is
1470
+ stopped, the books that hold no seal. A question proves its
1471
+ recorder, and an answer its sealer, with HMAC-SHA-256 under the
1472
+ token over a time, a number used once and the body; the token never
1473
+ travels; each recorder's numbers used once are kept apart, for as
1474
+ long as its question's time could be taken by a clock that never
1475
+ goes back; the recorder's name, the time, the number and a first
1476
+ proof over the body's declared length are checked before the body
1477
+ is read, so no body is read for a program without the token, and at
1478
+ most 16 MiB of bodies being read are held for one recorder. A
1479
+ question waits for its turn as the bytes of its body only, at most
1480
+ 16 questions and 32 MiB for one recorder, and its body is read as
1481
+ JSON only in its turn, so one read body exists at a time. A body
1482
+ must be a JSON object, and may hold only the members its path takes
1483
+ (`bad-field`); `connectSealer`, the recorder's side, refuses before
1484
+ sending any member of `more` the path does not take (`id` for a
1485
+ seal among them), so `more` cannot replace `book`, `at`, `leaves`
1486
+ or `entry`, and sends a `when` given as a `Date` as its
1487
+ milliseconds. A question
1488
+ turned away as `busy` keeps no number. Questions are answered one at a
1489
+ time; the state is made sure to be on the disk before an answer to a
1490
+ question that changed it; a seal is never given out before the state
1491
+ that holds it is saved, and after a failed save every question taken
1492
+ up is answered `sealer-stopped` until it is started again. A missing
1493
+ state stops the start, and adding a recorder (`--fresh` begins it
1494
+ again); `add-recorder` refuses a name that has books in the state
1495
+ (a recorder written into `recorders.json` by hand is not checked so); one
1496
+ sealer on a folder at a time. It names no time-stamp service and
1497
+ asks no witness yet.
1498
+
3
1499
  ## 0.5.0 (17 October 2026)
4
1500
 
5
1501
  - **The README's count of review rounds: 50.** The 40 recorded by