provared 0.6.0 → 0.8.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,728 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.0 (1 November 2026)
4
+
5
+ Version 0.8.0 changes the library's browser part, `src/passkey-browser.js`, which a web page uses to have a person sign with a passkey. A new function, `signChallengeWithChosenPasskey`, lets a page whose browser has forgotten which passkey signed before ask the device to offer the passkeys it holds for the website; the page must then check what comes back against the key it expects. Every signing function now also says which passkey signed (`credentialId`). What changes for a user: every other signing function now refuses, before the device is asked, a passkey that does not name its identifier as text of at least one character, with a `PasskeyError` of code `cancelled` and the words "No passkey was named, so nothing was signed." A missing or non-text identifier already failed with that code, with other words; a passkey whose identifier was empty text was handed to the device, and now throws instead. The signing page uses all this to open as a small window for another page (the Provared application), sign a new permission, a renewal or a cancellation there, and hand the record back; the signing page is served at sign.prova.red and is not in the npm package. Nothing else in the library changes: no record's bytes, no rule of the checker, nothing in the Python version but its version number, so a book checks with 0.8.0 exactly as with 0.7.0. Beside the library, in this repository, with entries in the list below: `provared-mcp-executor` 0.2.0, with rules on the content of a tool call, already published on npm on 1 November 2026; and a reader of Agent Middleware's receipts, `provared-agent-middleware`, not published. The framework packages `provared-langchain` and `pydantic-ai-provared` stay at 0.2.0, unchanged.
6
+
7
+ - **Versions.** The library is 0.8.0, in `package.json` and `python/pyproject.toml`. The Hermes plugin (not published) asks for `provared>=0.4.0,<0.9`, where it asked for `<0.8`, which left 0.8.0 out; the test workflow checks that bound. `examples/frameworks/package-lock.json` gives the library's version as 0.8.0. No other package's range changes: the others' tests run against the repository's own library whatever range they state, and the ranges of the published `provared-mcp-executor` 0.2.0 (`^0.7.0`) and `provared-claude-code` 0.1.0 (`>=0.6.0 <0.8.0`) are left as published, since neither needs anything of 0.8.0. The README, the guide, the Python README and three pages say 0.8.0, and the guide gives the checking page's new fingerprint.
8
+
9
+ - **The signing page opened as a small window by another page** (the
10
+ Provared application, which signs nothing itself). `page/sign.html` and
11
+ `page/sign-page.js` take a request after `#request=` in the address: a
12
+ new permission, a renewal or a cancellation, with the fields to fill,
13
+ the earlier signed permission, the passkey's public key, the address to
14
+ hand the result back to, and a short text handed back unchanged. The
15
+ page trusts the earlier permission only once the library's checker has
16
+ confirmed its passkey signature, then shows what the new permission
17
+ changes against it, part by part. It shows the return address before
18
+ signing, and warns, asking for a second press, before a new passkey is
19
+ made, before an agent's key set is signed for the first time on that
20
+ device, before a passkey other than the earlier one signs, and where the
21
+ return address is not one of the application's own two addresses,
22
+ exactly (`https://app.prova.red:8443` and `https://app.prova.red`;
23
+ `http://localhost` is taken and always warned of). The warnings are
24
+ worked out again at every press, and a press signs only where every
25
+ warning that stands then was shown and confirmed; a passkey prompt that
26
+ fails, or a passkey this browser forgets, takes the confirmation back. A
27
+ passkey is made only where the warning that one will be made was among
28
+ those confirmed. Where the browser has forgotten which passkey signed
29
+ before, the device is asked to offer the passkeys it holds for the
30
+ website, and what it signs is kept only if the checker passes it with
31
+ the expected public key. A cancellation is signed only by the passkey
32
+ that signed the permission, at this page's own website name and
33
+ address: a request to cancel one signed elsewhere, or with a passkey for
34
+ another website name (a parent name included), is refused, and a
35
+ cancellation never makes a passkey. A cancellation for an address that
36
+ is not the application's asks for a second press too. The signed record
37
+ is sent by `postMessage` to the exact return address, and the page says
38
+ it was handed over only once that page answers; it closes once that page
39
+ answers, from the window that opened it and from the return address
40
+ exactly, that it kept the record (`{ provared: 'received', kind, state,
41
+ kept }`), and stays open with the file offered where it says it did not,
42
+ or says nothing within ten seconds. With no window that opened it, the
43
+ page offers the file first, then a button that goes back with the record
44
+ after `#signed=`. A signature that arrives after the permission was
45
+ changed is not kept. A page shown inside another page still offers
46
+ nothing.
47
+ - `src/passkey-browser.js`: `signChallengeWithChosenPasskey(challenge,
48
+ rpId)` asks the device to offer the passkeys it holds for the website,
49
+ for a caller that checks the answer against the key it expects; the
50
+ answer of every signing function now names the passkey's identifier
51
+ (`credentialId`). Every other function still refuses a passkey with no
52
+ identifier, so that no caller is offered a choice it did not ask for.
53
+ - Tests: `test/sign-request.test.mjs` (the request, the return address,
54
+ what changed, the messages each way) and `test/sign-window.test.mjs`
55
+ (the window driven in a browser with a virtual passkey, at phone and
56
+ desktop width: a new permission, a renewal and a cancellation handed
57
+ back; the wrong passkey refused; the answer that the record was not
58
+ kept, and no answer; the way back by a link for all three kinds; a
59
+ request that cannot be read; a framed page),
60
+ `test/sign-window-review.test.mjs` (the cases an independent review
61
+ found, at made-up https addresses: a failed passkey prompt, a passkey
62
+ forgotten between two presses, a cancellation for a parent website name
63
+ or another address, a cancellation for an unknown address, a record
64
+ sent to a page at another address, an answer from another window, a
65
+ change while the passkey is asked, a cancellation by another passkey)
66
+ and `test/passkey-browser-guard.test.mjs` (a passkey with no identifier
67
+ is refused before the device is asked).
68
+ - **After an independent re-check of the signing window, 1 November
69
+ 2026:** once a cancellation asked for by a request has been signed and
70
+ handed back (or offered to take back by a link), the "sign the
71
+ cancellation" button stays off: while the page that asked waits, after
72
+ it says it did not keep it, and after no answer. Before, it was switched
73
+ on again, and a second press signed a second cancellation with no new
74
+ warning. The file stays offered; a new request starts the page afresh.
75
+ Held by `test/sign-window.test.mjs`.
76
+
77
+ - **`provared-mcp-executor` 0.2.0** (published on npm on 1 November 2026
78
+ and listed in the official MCP Registry as
79
+ `io.github.provared/mcp-executor`; the library stays at 0.7.0, and the
80
+ executor still asks for `^0.7.0`). It carries the rules
81
+ on the content of a change, below (choices X56 to X77), and names itself
82
+ for the official MCP Registry: `package.json` gains
83
+ `"mcpName": "io.github.provared/mcp-executor"`, and
84
+ `mcp-executor/server.json` describes the package for the registry, with
85
+ the command line `stdio --config <settings file>` (choices X78 to X81 in
86
+ `mcp-executor/DESIGN.md`). `server.json` is not in the npm package; the
87
+ registry's publishing tool reads it from the folder. Beside it,
88
+ `mcp-executor/demo/` (not in the package): the executor in front of a
89
+ made-up tool server with two harmless tools, set up on its first start
90
+ with a permission signed by the library's development stand-in for a
91
+ passkey, so that a directory of MCP servers can start it with no
92
+ settings and no secret; a `Dockerfile` on the official `node` image,
93
+ pinned by digest; its records prove nothing about anyone, as its README
94
+ says (choices X82 to X89). One new test starts it as a real process.
95
+ After an independent review (choices X90 to X99): the README and the
96
+ design say that only an Owner of the organisation may publish to the
97
+ registry, with a token that can read its members, and only after the
98
+ npm version is out; the demonstration uses only an empty folder or one
99
+ it marked as its own, keeps each set-up in a folder of its own and never
100
+ renames or empties a folder, writes its paths afresh on every start (so
101
+ it survives a move or a new Node.js), lets two starts at once on an
102
+ empty folder each set up, says plainly when a folder cannot be written,
103
+ and its server compares the made-up credential's value; the container
104
+ is built from four named files, with a packed executor handed over as
105
+ a context of its own and never copied in. Four more tests. After a
106
+ second, narrow review (choice X100): an empty mark left by a start that
107
+ was stopped no longer refuses the folder for good, a file error inside
108
+ the folder gets the same plain words, and a name that is a file is said
109
+ to be one. Two more tests.
110
+ - **The MCP executor tests rules on the content of a change before a
111
+ call is let through** (F-157, F-158; `mcp-executor/rules.mjs`; choices
112
+ X56 to X70 in `mcp-executor/DESIGN.md`). A permission allows kinds of
113
+ action; being allowed to change a file is not being allowed to change
114
+ everything the file controls. The new setting `contentRules` names a
115
+ JSON file of rules: each names a tool, says which calls it applies to
116
+ (`when`: conditions on the arguments), where the proposed content is
117
+ and how to read it (`content`: a JSON Pointer, as JSON text or as plain
118
+ data) and what must hold in it (`test`: `equals`, `oneOf`, `absent`,
119
+ `matches` a plain glob, each at a JSON Pointer, with `orAbsent`). A rule
120
+ with no `test` refuses every call it applies to, which is how a tool
121
+ that carries only a partial edit is closed: the executor reads no file
122
+ and keeps no content. A call a rule refuses (`content-rule`), or whose
123
+ content the rule cannot read exactly (`content-unreadable`: not JSON, a
124
+ member named twice or twice once folded, a number beyond 2^53 - 1,
125
+ nothing at the pointer, not text, a text too long to match), is not
126
+ sent; the model is told which rule and what it asked for, never a value
127
+ from the arguments, and a denial is written. Every receipt and denial
128
+ names `executor.rules`, the SHA-256 of the rules file's parsed content
129
+ in the one form of RFC 8785; a denial by a rule also names
130
+ `executor.rule`, the fingerprint of the rule's name; both are labels.
131
+ The person puts the same fingerprint into the permission's
132
+ `agent.software` (`keys` prints the entry); where the permission states
133
+ software and the rules in force are not among it, the executor does not
134
+ start; where it states nothing, it warns. `check` reports the rules and
135
+ whether the permission states them; `compare` reports which rules
136
+ fingerprints the records name and whether a slip states each. The
137
+ README says plainly what the rules can never promise: an edit by
138
+ another route, a tool whose arguments do not show the resulting file, a
139
+ file type the executor cannot read, a pattern that matches characters
140
+ rather than files. Content in YAML, TOML or line-based forms is later
141
+ work. No record and no rule of the format changes. 12 new tests, among
142
+ them a label change let through and the same edit with a hidden setting
143
+ switched on refused.
144
+ After an independent review (choices X71 to X77): with `unlisted`
145
+ `forward`, a content rule for a tool that `tools` does not name stops
146
+ the start, since such a call was sent on before any content rule was
147
+ tested and the warning said the opposite; a permission whose
148
+ `agent.software` states rules on content (an entry named `Content rules
149
+ of …`) stops the start where the settings name no rules file, and
150
+ `compare` lists the executor's receipts and denials under such a slip
151
+ that name no rules file (`withoutRules`); a `/` or a `\` in a pattern
152
+ matches either separator, as promised, so `**/settings.json` matches
153
+ `C:\proj\settings.json`; a rules file holds at most 20,000 pattern parts
154
+ in all, which keeps one call to about a quarter of a second where a
155
+ chosen file took 9.6 seconds; the file is decoded as strict UTF-8; a
156
+ JSON Pointer with a `~` not followed by `0` or `1` is refused. Four more
157
+ limits are stated in the README: a `when` value that is not text does
158
+ not match; a member in other capitals in the content passes an
159
+ `absent` or `orAbsent` test; a path with a trailing full stop is another
160
+ text; JSON text inside a JSON string is not read. 7 new tests.
161
+ - **A reader of another project's receipts: Agent Middleware (AMW)**,
162
+ `integrations/agent-middleware/` (`provared-agent-middleware` 0.1.0, not
163
+ published): the first of the plan's readers of the closest other
164
+ projects' receipts. It checks an AMW permit or receipt by the gateway's
165
+ published Ed25519 key (the key found by `kid`, its status read; an
166
+ envelope compared with the signed content; AMW's own six answers,
167
+ reproduced for every one of its 96 published test cases, which are
168
+ copied into `test/vectors/` under their Apache License 2.0 with a notice
169
+ of where they came from), maps each verified receipt onto the fields
170
+ the checker compares (the tool as the action, `credits_charged` as the
171
+ amount in a unit and scale the reader states, `created_at` as the time,
172
+ `permit_id` as the permit it names), and judges a set of receipts
173
+ against a Provared slip that names the AMW gateway in `with`: the
174
+ checker's own codes where they apply (`action-not-allowed`,
175
+ `prohibited`, `outside-valid-time`, `over-limit`, `over-each-limit`,
176
+ `over-count-limit`, `over-period-limit`, `amount-missing`,
177
+ `approval-missing`), with words that say "AMW receipt"; codes of its
178
+ own (`amw-permit-…`) where only the AMW permit can be compared; and a
179
+ stated list of what cannot be judged (no chain, so omission and cut
180
+ ends do not show; no countersignature; no signature of the person on
181
+ the AMW permit; approvals by identifier only; which agent; with whom).
182
+ A denied AMW receipt is shown as the gateway's refusal and never
183
+ counted against the agent; a receipt that does not verify is not shown.
184
+ `receiptDocument` carries an AMW receipt by its fingerprint in a
185
+ Provared receipt's `details` under the name `agent-middleware.receipt`.
186
+ A command, `provared-agent-middleware verify | judge`, with the
187
+ checker's exit codes. Written from AMW's published data and the
188
+ standards alone; no AMW program was read or run. Its tests run in the
189
+ test workflow beside the other packages.
190
+ - **The AMW reader after an independent review**, each fix held by a
191
+ test (`test/review.test.mjs`): a slip's countersignature condition, or
192
+ an approval named by identifier only, is listed on the receipt it
193
+ applies to (`notJudged`), and such a receipt is never said to be within
194
+ the slip (`withinSlip` is `null`, not known) nor gives exit code 0
195
+ (before, both gave "within" and 0); a P-256 key over content that says
196
+ "Ed25519" reads `unsupported` (before, `verified`); signed text that is
197
+ not well-formed Unicode, names a member twice in one object, or gives a
198
+ `kid` that is not text is `malformed`; times are compared to the
199
+ nanosecond (before, cut to milliseconds, so two receipts 3,599.9996
200
+ seconds apart fell in different hours); a receipt handed over twice is
201
+ counted once and listed as a duplicate; a receipt that fails its check
202
+ shows no identifier (the envelope's is not signed), only its place; the
203
+ command shows control and invisible characters as a mark, and escapes
204
+ them in `--json`; `verify` loads no library, and the README's `judge`
205
+ command works from a copy of the repository; `cannotShow` also states
206
+ that cancellations cannot be handed over, that outcomes other than
207
+ success are left out of totals, and that retired keys check receipts
208
+ of any date. The package no longer pins a development copy of
209
+ `provared` 0.6.0, which its peer range excludes.
210
+
211
+ ## 0.7.0 (1 November 2026)
212
+
213
+
214
+ 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.
215
+
216
+ - **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.
217
+
218
+ - The gateway's test "M3: an agent may have only so many calls waiting"
219
+ no longer depends on how busy the computer is: each wait has 120
220
+ seconds, not 20, and the status is shown to answer without waiting for a
221
+ call by the upstream still holding that call, not by a time under one
222
+ second. It checks the same limit.
223
+ - **Three more reserved document names** (format description, section
224
+ 27), in both languages: `provared.tool-name` (the fingerprint of the
225
+ tool's name, exactly as the agent's software called it, which is not the
226
+ slip's action name), `provared.tool-call-id` (the fingerprint of the
227
+ identifier the model gave the tool call, exactly as received) and
228
+ `provared.tool-result` (the fingerprint of the tool's result in the one
229
+ form of RFC 8785, as `provared.arguments` is of the arguments; never the
230
+ result itself). Each may be named once in a record; the three
231
+ identifiers of 0.5.0 are still the only names that may be named more
232
+ than once. The first two are fingerprinted with `identifierFingerprint`
233
+ (`identifier_fingerprint`), the third with `argumentsFingerprint`
234
+ (`arguments_fingerprint`); no new function. `recordTools`,
235
+ `record_tools` and `record_function` still name the arguments only, so
236
+ what the published framework packages write is unchanged. The MCP
237
+ executor writes `provared.tool-name` and `provared.tool-result` in place
238
+ of its labels `mcp.tool` and `mcp.result`; the OpenTelemetry bridge
239
+ writes `provared.tool-call-id` where a tool gives `callId`, which before
240
+ went on the span only, and still never writes `provared.event-id`, the
241
+ platform's identifier, which only the caller can name. Both packages now
242
+ need 0.7.0 or later (`^0.7.0`). Shared test files:
243
+ `stub-contents.json`, `http-fingerprints.json` and `denial-contents.json`
244
+ remade, with the three names accepted once, refused twice, and names
245
+ close to them (`provared.Tool-result`, `provared.tool_result`,
246
+ `provared.tool-call`) refused as unknown. **Compatibility:** no book
247
+ written by version 0.6.0 fails its check because of this change, since
248
+ 0.6.0 refused these names and so never wrote them; a checker of version
249
+ 0.5.0 or 0.6.0 refuses a book that names any of the three
250
+ (`bad-field`, as for every `provared.` name it does not know), and a
251
+ checker of 0.4.0 or earlier, from before the names were reserved, reads
252
+ them as labels; so a book written with them checks in full only with a
253
+ later version. The guide, the Python README, the list of threats and the
254
+ type declarations say so.
255
+ - **An executor for MCP tool servers**, `mcp-executor/`
256
+ (`provared-mcp-executor` 0.1.0), stage 1: the stdio
257
+ transport. An executor, in the words of the Internet-Draft
258
+ `draft-izmaylov-agent-permission-receipts-02`, is software other than
259
+ the agent that holds what its actions need and refuses an action the
260
+ permission does not allow. The agent's MCP client starts the executor as
261
+ if it were the tool server; the executor starts the real server with its
262
+ credential (from a file or the executor's own environment, never the
263
+ agent's), and passes every message of either revision of the protocol
264
+ through unchanged except two. `tools/call` is matched to an action by
265
+ the tool's name in a rules file, checked by the check before acting
266
+ through the agent's stub writer, which the executor holds; a call the
267
+ permission allows is sent on and gets a receipt that names the executor
268
+ in `with`, binds the arguments and the server's result by fingerprint,
269
+ and is countersigned with the executor's own key set once the outcome is
270
+ known; a call it does not allow is answered as a tool error that says
271
+ why, and written as a denial. `tools/list` comes back without the tools
272
+ that have no rule. Beside the book the executor keeps its own copy, one
273
+ tagged line before each call is sent (made sure to be on the disk
274
+ first) and one once its receipt is in the book, and `compare` holds that
275
+ copy against a book to find a receipt that was left out, which the draft
276
+ leaves unspecified. A call the executor cannot record is not sent. A
277
+ call cut off by a stop gets its receipt at the next start, from the copy,
278
+ naming `provared.error`. Lines from the agent are read strictly (no
279
+ batch, no member name given twice, an id that is text or a whole number),
280
+ and refused otherwise. The design, the HTTP transport and the content
281
+ rules of the plan's F-157 (design only), and 36 numbered choices for
282
+ review are in `mcp-executor/DESIGN.md`. Nothing in the library changes.
283
+ The test workflow runs the package's tests, and ESLint covers the folder.
284
+ An independent review of the first draft found one serious fault and
285
+ six of medium weight, each now fixed and a test (`test/review1.test.mjs`):
286
+ a receipt lost for good where the book file could not be written after a
287
+ call (the copy now keeps the outcome and the next start writes the
288
+ receipt from it); a late answer from a server that ignores cancellation
289
+ taken for a later call's under the same client id (each call now goes to
290
+ the server under an id of the executor's own); a `tools/call` sent as a
291
+ notification forwarded unchecked (dropped); the process staying alive
292
+ after its server ended (it ends with the server's code); no cap on calls
293
+ or list requests waiting (`maxWaiting`, default 16; 256 lists); a bare
294
+ program name found through the client's `PATH` (named by path, or
295
+ `server.path`, with a warning otherwise); a result that cannot be
296
+ fingerprinted leaving no outcome in the receipt (`provared.error` with a
297
+ fixed text). Smaller points: each copy line carries its number, so a
298
+ line taken out, put twice or moved stops the start; `compare` tells a
299
+ receipt the executor could not write apart from one the book lacks, does
300
+ not agree where the book has a problem, and makes no key; a whole number
301
+ beyond 2^53 - 1 anywhere in a line is refused; `check` writes nothing;
302
+ a permission naming other services beside the executor is warned about.
303
+ The founder's decision F-158 (content rules in the executor's settings,
304
+ fingerprinted into receipts and into the permission's `agent.software`;
305
+ the automatic late receipt kept; reserved names for a tool's name and
306
+ result to come in the library and draft -03) is recorded in `DESIGN.md`.
307
+ A narrow second review of those fixes found one serious and one medium
308
+ fault in the id mapping, both fixed and tests (`test/review2.test.mjs`):
309
+ a server answer holding a whole number beyond 2^53 - 1 was refused as
310
+ unreadable, so the call timed out and the receipt named a false timeout
311
+ (the server's lines are now read without that rule, reach the client in
312
+ the server's own bytes with only the id replaced, and such a result is
313
+ named as one that cannot be fingerprinted; an answer that cannot be read
314
+ at all still reaches the client, and the receipt says so); and only tool
315
+ calls were renamed, with guessable ids, so a `ping` under a call's id
316
+ could settle the call with the ping's answer (every request now goes
317
+ under an id with a random part). Requests the server never answers are
318
+ forgotten past 1,000. A receipt written at a restart names
319
+ `executor.written-late`, so that the book itself says so. A final check
320
+ found two small points, fixed: a server line naming `id` twice at its
321
+ top level, or a batch, is matched with no call and handed on as it is,
322
+ and the call ends by the timeout with a "no answer" receipt.
323
+
324
+ - **The recorder beside Claude Code as a Claude Code plugin.** It can now
325
+ be installed, with its hooks, in two commands in Claude Code:
326
+ `/plugin marketplace add provared/provared`, then
327
+ `/plugin install provared-recorder@provared`. A marketplace file at the
328
+ top of the repository (`.claude-plugin/marketplace.json`, the place
329
+ `/plugin marketplace add` looks) lists one plugin, the folder
330
+ `integrations/claude-code`, which gains `.claude-plugin/plugin.json`
331
+ (the name `provared-recorder`, the package's version) and
332
+ `hooks/hooks.json`: the hooks `init` writes, each run through the
333
+ plugin's entry, `plugin.mjs`, by the plugin's own paths
334
+ (`${CLAUDE_PLUGIN_ROOT}`), and at the start of a session a new command,
335
+ `session-start`. It starts the recorder as `ensure` does and, where
336
+ something stands in the way (no library, no permission yet, a recorder
337
+ that does not start or is still starting, the recorder's hooks in force
338
+ twice), tells the person and Claude in Claude Code's own fields for
339
+ that, within 27 seconds; where all is well it says nothing. Nothing
340
+ signs for the person: they run `init` (which, run through the plugin,
341
+ writes no hooks), sign at https://sign.prova.red/ with their own
342
+ passkey, and run `use`, as the first session tells them. Before each
343
+ call the hook, where no permission was ever put in place, now says that,
344
+ with the same steps, rather than only that the recorder is not running,
345
+ and it waits for the recorder for at most 25 seconds from its own start.
346
+
347
+ - **The plugin's library, by an exact path.** Claude Code 2.1.167 copied a
348
+ plugin fetched from a git repository into its cache without the library
349
+ `provared`, so every call was refused and the start of a session failed;
350
+ and Node.js would have looked for the name in the folders above the
351
+ plugin, such as the person's home folder. The plugin's entry
352
+ (`plugin.mjs`, with `plugin-library.mjs`) now takes the library from
353
+ the plugin's own `node_modules` where Claude Code put it there, or else
354
+ installs it itself, at a session's start or at a command the person runs,
355
+ with `npm ci --ignore-scripts` from the folder's new `package-lock.json`
356
+ (exactly 0.6.0, the package checked by npm against the fingerprint
357
+ the lockfile holds, no install script run, npm's own program run by Node.js
358
+ with no shell and stopped for certain at its time limit), into the
359
+ plugin's data folder, and loads it from there alone, in this program and
360
+ in every program it starts. After that, the copy is trusted by where it
361
+ sits, and is not checked again before each use. Whatever goes wrong in
362
+ the entry itself (a damaged copy, a missing lockfile, a Node.js without
363
+ `module.registerHooks`), a call is refused before it is taken and Claude
364
+ Code is told after it, as the hook itself does. A settings file with
365
+ comments is looked through for the recorder's hooks as JSON with
366
+ comments. Where it cannot, the session says so,
367
+ every call is refused, and each refusal gives the one command that mends
368
+ it (`plugin.mjs install-library`). Checked with Claude Code 2.1.167's own
369
+ `claude plugin validate`, with and without `--strict`, and by installing
370
+ the plugin, from a marketplace whose entry names a git copy of this
371
+ branch, into a Claude Code settings folder of its own, then running its
372
+ hooks and `session-start` from the copy in the plugin cache.
373
+
374
+ - **Not recorded twice.** Claude Code runs a plugin's hook and the same
375
+ hook in a settings file side by side. Before each call, the plugin's
376
+ hook (and only it: Claude Code names the plugin's folder to it) looks in
377
+ the person's and the project's settings files for a hook that runs a
378
+ recorder's `hook.mjs`, in the forms the README lists (a word joined from
379
+ quoted and unquoted parts as a shell joins it; the project's and the
380
+ home folder written as variables or `~`; a copy since deleted, on a path
381
+ that names Provared); where it finds one, it refuses the call and says
382
+ how to take one of the two out. It reads no settings file larger than
383
+ 1 MB, looks at no more than 20,000 words, for no more than 2 seconds,
384
+ and refuses the call past any of these. After a call it records it
385
+ whatever it finds. A new command, `remove-hooks`, takes the hooks `init`
386
+ wrote, from any copy of the recorder, out of a project's
387
+ `.claude/settings.local.json`, and nothing else, by writing a new file
388
+ that then takes the old one's place, and only where the file did not
389
+ change while it was read. `init` gains `--no-hooks`, refuses any other
390
+ word after it, and warns where it writes hooks while the plugin is
391
+ installed. The new file `settings-hooks.mjs` is in the npm package.
392
+
393
+ - **The heading of the top fingerprint.** The checking page heads the card
394
+ of a record's top fingerprint "The top fingerprint (the Seal)" only where
395
+ the record holds a seal, and "The top fingerprint" where it holds none
396
+ (`topFingerprintHeading` in `page/render.js`). The "try it" page no
397
+ longer relabels the heading itself. Nothing else the page shows changes,
398
+ and the command-line checker, the Python version and the shared test
399
+ files never held these words.
400
+
401
+ - **OpenSSF Scorecard.** A workflow, `.github/workflows/scorecard.yml`,
402
+ runs the Open Source Security Foundation's Scorecard on each push to
403
+ main, every Sunday and when a branch protection rule changes, and
404
+ publishes the result to api.scorecard.dev and to this repository's code
405
+ scanning page. Laid out as the Scorecard project's own example; no
406
+ permission at the top, and only `security-events: write` and
407
+ `id-token: write` in its one job. It is the first workflow here to use
408
+ an action that is not GitHub's own (`ossf/scorecard-action` v2.5.0,
409
+ pinned to its commit). Pinning fixes the action's own files, not all
410
+ that runs: at that commit the action runs the container
411
+ `ghcr.io/ossf/scorecard-action:v2.5.0`, chosen by its tag (on
412
+ 31 October 2026 the tag named the image
413
+ sha256:1e1481c65fc2407d3fd6ab18a23c741eb4d1c0d9359ebafc4b3ab13aa61d8627).
414
+ It cannot be pinned by that fingerprint, because the Scorecard service
415
+ takes a result only from the named action. The job's token can only
416
+ send results to the code scanning page and ask for an identity token,
417
+ so at worst a changed container could publish a false result. The
418
+ README shows the Scorecard badge.
419
+
420
+ - **Static analysis.** `.github/workflows/tests.yml` gains a job that runs
421
+ ESLint over the JavaScript and Ruff over the Python on every push and
422
+ pull request, with rules that look for faults (eval, unsafe regular
423
+ expressions, child processes, weak fingerprint methods, subprocess with
424
+ shell=True and the like), not style. Both are development tools only,
425
+ kept in `tools/lint/` at exact versions (its `package-lock.json`, and
426
+ `requirements.txt` with each file's fingerprint), and are in neither
427
+ published package. A finding that is not a fault is set aside on its
428
+ own line, with a comment giving the reason. Faults found and fixed, none
429
+ changing what the library does: the Hermes plugin's `cli.py` used a
430
+ backslash inside an f-string's braces, which Python 3.11, the oldest
431
+ the plugin supports, cannot read; three Python tests expected any
432
+ exception where the library raises `Refusal`, and now expect
433
+ `Refusal`; three imports were never used. ESLint also covers
434
+ `conformance/`, and Ruff also covers `tools/release/`; there
435
+ `compare_wheel.py` now reads only `https://` addresses, since the
436
+ second address it reads comes from the index's own answer.
437
+
438
+ - **Warnings are failures.** The Python tests run with `python -W error`
439
+ (with two filters: a warning raised inside a LangChain dependency on
440
+ Python 3.14, and, in the Hermes plugin's job, the plugin's own notices to
441
+ the person, which it raises inside Hermes on purpose). The JavaScript tests no longer hide Node.js's warnings,
442
+ and a step after each run fails on any warning other than Node.js's
443
+ notice that ML-DSA or SLH-DSA is experimental
444
+ (`tools/lint/node-warnings.sh`); each run keeps its own exit code
445
+ (`set -o pipefail`), so a failing run is not hidden by the step that
446
+ saves its output. One was found and fixed: the seal
447
+ writer handed a Web Crypto key of another method to Node.js's
448
+ `createPrivateKey`, which Node.js has deprecated (DEP0203); such a key
449
+ is now refused before it is handed over, with the same answer as before.
450
+
451
+ - **Smaller changes.** The site's publishing workflow gives each job only
452
+ the permissions it needs (`pages: write` and `id-token: write` in the
453
+ deploy job alone) and checks out without keeping a token.
454
+ `CONTRIBUTING.md` names GitHub's private reporting form beside email
455
+ for security faults, and says that a change in behaviour comes with
456
+ tests.
457
+
458
+ - **A reader may name the address a permission must have been signed
459
+ at** (25 to 31 October 2026), in both languages. A passkey can be asked
460
+ to sign by any page under the website name it was made for (its relying
461
+ party), and what it signs is a fingerprint the person cannot read on the
462
+ device's prompt; so a page served under that name, or a script on one,
463
+ could have a passkey made for `prova.red` sign a permission it wrote, by
464
+ presenting the permission's fingerprint as a sign-in challenge, and a
465
+ checker, which compared the address only with what the permission itself
466
+ said, would pass it (the list of threats, section 3, said so since 0.6.0,
467
+ with no option to close it). Now the checker's option `issuerOrigins`
468
+ (Python `issuer_origins`; the command line `--origin`, which may be
469
+ repeated; a box on the checking page) names the addresses at which
470
+ permissions must have been signed, each in the one form the format
471
+ allows for `issuer.origin` (`isPageAddress`, Python `is_page_address`,
472
+ gives the form). A permission signed anywhere else is refused with the
473
+ new code `issuer-origin-not-expected`, after the passkey has been
474
+ compared as before, and the stubs under it are not compared; a
475
+ permission signed at an address named gets a note that says so; a list
476
+ that is not in that form, or longer than 64, is a problem with the whole
477
+ check (`bad-field`), never set aside. An approval and a cancellation are
478
+ held to the permission's address by the passkey check already. The
479
+ command-line checker says "Signed at an address you named?" where
480
+ `--origin` was given, and refuses a value not in its plain form. Nothing
481
+ changes for a reader who names no address, and no record's bytes change.
482
+ Format description, section 4.4; the list of threats, sections 3, 7 and
483
+ 8; the guide, "How signing works". Shared test files: `slips.json` is
484
+ made again with eleven new cases (every record in it is signed afresh),
485
+ `denial-constants.json` with the new code, and `issuer-origins.json`
486
+ and `issuer-origin-shows.json` are new. Tests:
487
+ `test/issuer-origins.test.mjs`, `python/tests/test_issuer_origins.py`,
488
+ and cases in `test/cli.test.mjs`, `test/page.test.mjs` and
489
+ `test/refusals.test.mjs`.
490
+
491
+ - **The signing page says why it is served at a name of its own**, and
492
+ the one tool that writes that site. The page (`page/sign.html`) now
493
+ says that the passkey it makes belongs to the page's website name and
494
+ to nothing else, why the page is served at a name that serves nothing
495
+ else, and that whoever checks a record can name that address; shown
496
+ inside another page, it now says to open sign.prova.red in a tab of its
497
+ own (before: prova.red/sign/; the pages name no address with its
498
+ scheme, which a test holds). `tools/make-sign-site.mjs`
499
+ writes the whole of the repository `provared/sign`, from which
500
+ sign.prova.red is served, from this repository: under `site/` the page
501
+ as `index.html`, its script, the renderer, the style sheet and every
502
+ module of the library, each byte for byte, with `site/files.sha256` in
503
+ the form `sha256sum --check` reads; and the repository's own README,
504
+ publishing workflow (with the actions pinned to the commits this
505
+ repository's workflow pins) and licence. It takes `--from <tag or
506
+ commit>`, which the README and the workflow name as where the copies
507
+ come from, and reads every file with git at that tag or commit, never
508
+ from the working tree: a file not committed, or changed and not
509
+ committed, cannot reach the site, and the modules copied are those git
510
+ holds directly under `src/` there. A name that git cannot find is
511
+ refused (code 2), and nothing is written. A tag is named as the tag;
512
+ any other name as the commit's full hash. With `--check` it compares a
513
+ folder written before with the same files, read the same way, and names
514
+ what differs. Run against the copy made by hand on 25 October 2026 from
515
+ tag v0.6.0, with `--from v0.6.0` it finds every file equal; with
516
+ `--from` this branch's commit it finds the seven files that this change
517
+ and the one above change (the page, its script, the renderer, and
518
+ `src/book.js`, `check.js`, `denial.js` and `slip.js`) and their list of
519
+ fingerprints different, and the README and the workflow, which name
520
+ where the copies come from. Test: `test/sign-site.test.mjs`, which
521
+ builds a small repository of its own with a tag, so that it needs
522
+ neither this repository's tags nor its history.
523
+ The guide's table of folders names the tool.
524
+
525
+ - **The recorder beside Claude Code and the Hermes plugin print links to
526
+ https://sign.prova.red/** (their `SIGNING_PAGE`), where they printed
527
+ links to prova.red/sign/; their READMEs say what a link printed by an
528
+ earlier version begins with and how to open it at the new address.
529
+
530
+ - **The recorder beside Claude Code: a passkey can be retired.** The
531
+ recorder's list of passkeys, `trusted-passkeys.json`, served two
532
+ purposes with one list: which passkeys may sign a new permission, and
533
+ which passkeys' permissions it opens its books with (the book in force
534
+ and the kept ones, for late receipts and for counting limits across
535
+ books), and it refuses to start where the book in force holds a
536
+ permission from a passkey not on it. So a passkey that had signed
537
+ permissions in the record could not be taken off the list without
538
+ stopping the recorder. Now the file holds two lists: `passkeys`, every
539
+ passkey whose permissions are in the record, written and added to as
540
+ before and never shortened; and among them `retired`, written by the
541
+ new commands `retire-passkey <thumbprint>` and
542
+ `admit-passkey <thumbprint>`. A retired passkey's permissions stand,
543
+ the recorder still starts on the books that hold them and still counts
544
+ limits across those books, and a new permission from it is refused,
545
+ with or without `--new-passkey`, by `renew`, by `use`, by the running
546
+ service and at start-up where a new permission was put in place by
547
+ hand, until the person admits it again; only a passkey on the list can
548
+ be retired. The retired list is read from the file at each renewal, so
549
+ the command counts at once, while the recorder runs or not. The list is
550
+ read again just before it is written, and the two are merged: every
551
+ passkey on the file and every one added, never fewer, and the retired
552
+ list with the one change asked for, written to a temporary file of its
553
+ own and renamed into place; so `retire-passkey` run at the same moment
554
+ as a renewal that adds a new passkey keeps both changes (a narrow
555
+ window remains between that reading and the renaming, since no lock is
556
+ held); a list that would hold no passkey is never written. At start-up,
557
+ only the permission last in force is taken without the checks: the
558
+ last permission line of `book.jsonl`, or, where a switch to a new book
559
+ was interrupted and there is no `book.jsonl`, the last permission line
560
+ of the last kept book. Any other permission put in `slip.json`, even one
561
+ that either book holds further up, goes through the checks, a retired
562
+ passkey's among them, and no line is added for it. What is left: a
563
+ program on this computer that puts back the permission last in force
564
+ (removing `book.jsonl` where it was last in force in the kept book) can
565
+ resume it, even if its passkey was retired afterwards, and so can one
566
+ that adds a permission's line at the end of either book. Retirement
567
+ stops new permissions; it does not stop a program that can change the
568
+ recorder's files. A file written by an earlier version holds
569
+ `passkeys` alone and is read as retiring none, so a recorder updated in
570
+ place starts on its folder as it is; a `retired` that is not a list of
571
+ thumbprints from `passkeys` stops the recorder, as a malformed
572
+ `passkeys` does. `status` names the retired passkeys (`retired`). Test:
573
+ `integrations/claude-code/test/retire.test.mjs`.
574
+
575
+ - **A workflow that publishes a release from the founder's signed tag**
576
+ (25 October 2026). `.github/workflows/publish.yml` runs only when a tag
577
+ such as `v0.7.0` is pushed. Before anything is published it checks that
578
+ the tag is signed by the release key in `.github/release-signers` and
579
+ by nothing else, that the tag, `package.json`, `python/pyproject.toml`
580
+ and the newest heading of this change log name the same version, and
581
+ that the JavaScript and Python libraries pass their tests on the tagged
582
+ commit, with the tests of every framework package against the library
583
+ from that commit. It then publishes the Python packages and stages the
584
+ npm package, through each registry's trusted publishing, with no stored
585
+ password or token, and makes the GitHub release with the checking page
586
+ as one file and its SHA-256. The npm version is staged
587
+ (`npm stage publish`) and becomes public only when the author approves
588
+ it on npmjs.com; the run shows the stage ID and the package's
589
+ integrity value, so that the author approves that one stage, and the
590
+ release notes say, for each registry, what the run sent there. The
591
+ Python files are checked
592
+ against the SHA-256 recorded when they were built before they are
593
+ uploaded. A version already on a registry is not published again, so a
594
+ framework package is published only when its own version is raised;
595
+ where its version is already published and its code differs, the run
596
+ stops before anything is published. The scripts it runs are in
597
+ `tools/release/`. Nothing in the npm package or the Python packages
598
+ changes. Version 0.6.0 and earlier were published by hand.
599
+ - `tools/make-single-page.mjs` refuses an option, or more than one
600
+ argument, and writes nothing: `--out x` used to write a file named
601
+ `--out`. It still takes one optional argument, the file to write.
602
+
603
+ - **A conformance kit**, in `conformance/` (not published): another
604
+ implementation of the format, in any language, is tested against the
605
+ shared test files through a small program of its own (an adapter),
606
+ which the kit hands one case at a time as a line of JSON and whose
607
+ answer it compares. By default it compares the codes and the shape of
608
+ each answer, not the words meant for people, as `test-vectors/README.md`
609
+ sets out; `--words` compares every value. It ends with code 0 only where
610
+ every case compared matched. An adapter for Provared's own JavaScript
611
+ library passes all 14,742 cases word for word. Passing it shows
612
+ agreement on these cases only; it is not a certificate. Its tests run in
613
+ the test workflow beside the sealer's.
614
+
615
+ - **The conformance kit, two changes.** By default it no longer counts the
616
+ sentences in the lists named `notes` and `limits`: another implementation
617
+ may say the same thing in more or fewer sentences, so only the codes,
618
+ the items that are not texts and the shape of each answer are compared;
619
+ `--words` still compares every word and every sentence. And a second,
620
+ example adapter, `conformance/adapters/provared-public.mjs`, is written
621
+ only on the functions the npm package offers (`provared` and
622
+ `provared/check`), and can be pointed at an installed copy of the
623
+ package with `--package`: it answers 3,099 of the 14,742 cases, all of
624
+ them matching word for word, and skips the other 11,643, which test
625
+ parts of the library that are not offered to other programs, saying why
626
+ for each.
627
+
628
+ - **The conformance kit, after an independent review.** By default, the
629
+ findings of an entry (`problems`, `breaches`, `disclosureProblems`) are
630
+ compared in any order, as the format description says their order is
631
+ not part of the format; and an entry's own `problems` may hold only
632
+ some of those stored, at least one, since a checker may stop at the
633
+ first way an entry fails its own check (where it stopped before the
634
+ countersignature, "unchecked" is taken in its place). Breaches are
635
+ still compared in full. `--words` keeps the stored order. The verdict
636
+ now says how many cases were skipped and not tested, the functions
637
+ skipped entirely are listed, and `--no-skip` counts any skip as a
638
+ failure. A reply holding both an answer and a skip is a fault of the
639
+ adapter. The example adapter reports an error it does not expect as a
640
+ crash, where it gave the code `check-failed`. The guide gains a table
641
+ of every function the shared test files name: its section of the
642
+ format description, whether another implementation may skip it, where
643
+ its answer is declared, and how its cases are written; and it lists
644
+ the stored values that are particular to Provared's own library.
645
+
646
+ - **An OpenTelemetry bridge**, `provared-opentelemetry` 0.1.0, in
647
+ `integrations/opentelemetry/` (not published). It puts each tool call's
648
+ receipt on the trace span that observability tools already show, in one
649
+ call: `traceTools` (as `recordTools`) or `traceAct` (as the stub
650
+ writer's `act`). The span
651
+ gets `provared.outcome`, `provared.action`, `provared.stub.fingerprint`,
652
+ `provared.stub.seq`, `provared.stub.countersigned`,
653
+ `provared.stub.trace_bound`, `provared.slip.fingerprint` and, for a call
654
+ not taken, `provared.check.codes` and `provared.denial.fingerprint`; a span
655
+ the bridge starts is an `execute_tool` span as OpenTelemetry's
656
+ conventions for generative AI describe it. The receipt names the
657
+ fingerprint of the span's trace id as `provared.trace-id` only for a tool
658
+ that the caller says sends the trace out with its call, in a
659
+ `traceparent` header (`sendsTrace: true`; off by default), as section
660
+ 27 of the format description defines the name; the bridge cannot see
661
+ whether the tool really does. The model's identifier for the call goes
662
+ on the span as `gen_ai.tool.call.id`, never into the receipt. Only
663
+ fingerprints, names and codes reach a span: never arguments, amounts,
664
+ documents, keys, the words of a finding or an error's message. A span is
665
+ the agent's side's own word; the receipt and the checker are the
666
+ evidence. `@opentelemetry/api` and `provared` are peer dependencies; the
667
+ library itself still has none. An error that the tool, or one of its own
668
+ functions, throws is the tool's error, even a `NotTaken` from another
669
+ stub writer. `traceAct` adds nothing to a request that comes with an
670
+ approval made beforehand, or whose `details` is not a list. Its tests run
671
+ in a job of their own in the test workflow, at the locked versions and
672
+ again at the newest releases of OpenTelemetry.
673
+ - **An example for a JavaScript agent framework** in
674
+ `examples/frameworks/` (not in the npm package): an agent built with the
675
+ Vercel AI SDK (`ai` 7.0.153), with its tools put behind the stub writer
676
+ with `recordTools`. One order within the slip is run and receipted; one
677
+ over its limit is not run, leaves no stub, and the agent is told why; the
678
+ checker finds the book intact and within its slip, and the output says
679
+ that the slip was signed by a development stand-in, not a person. It
680
+ uses the SDK's own mock model and passes `telemetry: { isEnabled: false }`.
681
+ It runs with a guard (`_offline.mjs`) open from before any module is
682
+ loaded until the program ends, which replaces with refusing functions
683
+ Node.js's socket connections, `dgram` sends, `dns` look-ups (both
684
+ `Resolver` classes included), the functions that start a program (`child_process`, `ChildProcess.prototype.spawn` and, where it exists, `process.execve`),
685
+ `worker_threads.Worker`, `fetch`, `WebSocket`, and `process.binding`
686
+ for the bindings beneath them; the tests check that nothing was refused.
687
+ Its own `package.json` and lock file pin every version, so the library
688
+ keeps no dependencies. A new job in the test workflow runs it. In
689
+ `docs/guide.md`, the links to the examples and to the Python version's
690
+ examples now give their full addresses on GitHub, since `examples/` and
691
+ `python/` are not in the npm package.
692
+
693
+ - **A "try it" page** at prova.red/try/ (`site/try/`, live since 25 October 2026).
694
+ One button. Where the browser has Ed25519 and ML-DSA-87 built in (Chrome,
695
+ Edge and Brave 155, tried on 13 October 2026), the page makes a new
696
+ record with the development stand-in for a passkey, under a limit on
697
+ orders in GBP that the visitor may change, writes a receipt for each of
698
+ four orders (on purpose, even where the check before acting says no) and
699
+ checks the record. Elsewhere it reads the sample record and checks it one
700
+ entry at a time, and says why. The words about the record, what the page
701
+ does not do and what the check trusts are those of the path shown.
702
+ Nothing is sent, nothing is stored, and the record made has no seal; the
703
+ card of its top fingerprint is then headed "The top fingerprint". Linked
704
+ from the home page and the site map; its logic (`site/try/story.js`) is
705
+ tested in `test/try-page.test.mjs`. The site's style sheet gains the
706
+ checking page's rules for the checker's answer, scoped to the new page.
707
+
708
+ - **Signing moved to https://sign.prova.red** (25 October 2026). A browser
709
+ lets a passkey sign only for the website name it was made for and the
710
+ addresses under it, so a passkey made at prova.red/sign/ could be asked
711
+ to sign by any other page under prova.red (the list of threats, section
712
+ 3). The signing page is now served at sign.prova.red from its own
713
+ repository, https://github.com/provared/sign, which holds only that page
714
+ and the library's files, each a byte-for-byte copy of the files at tag
715
+ v0.6.0, and serves nothing else. prova.red/sign/ now says that
716
+ signing has moved and offers no signing (`site/sign/index.html`; the
717
+ publishing workflow no longer copies the signing page there). The
718
+ gateway's README points to the new address. (The recorder beside Claude
719
+ Code, the Hermes plugin and the signing page itself named the old
720
+ address until the change above; their READMEs said how to open such a
721
+ link at the new address.)
722
+ - The test workflow is no longer started by a push that changes only the
723
+ record page and the sitemap.
724
+
725
+ - 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.
3
726
  ## 0.6.0 (25 October 2026)
4
727
 
5
728
  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.