provared 0.5.0 → 0.6.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,987 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.0 (25 October 2026)
4
+
5
+ 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.
6
+
7
+ - **The fingerprint of a tool call's arguments is named
8
+ `provared.arguments` by default**, in both languages (24 October 2026).
9
+ `recordTools`, `record_tools` and `record_function` name the one
10
+ document they write by default with the reserved name of section 27 of
11
+ the format description, where 0.5.0 and earlier named it `arguments`
12
+ (the default was kept in 0.5.0 so that the published framework packages
13
+ would not change what they write without a version of their own). The
14
+ fingerprint is the same; only the name changes, and every checker reads
15
+ both: one of 0.5.0 or later knows the reserved name, an earlier one
16
+ reads it as a label. A tool that names its own documents through
17
+ `details` is unchanged, and so are the recorder beside Claude Code, the
18
+ gateway and the Hermes plugin, which name their documents themselves.
19
+ No rule of the format changes. Shared test files: `tool-scenarios.json`
20
+ and `denial-scenarios.json` are made again; their stored answers differ
21
+ only in that name, and every record in them is signed afresh. The name
22
+ comes with the library: a framework package of an earlier version writes
23
+ the new name too once `provared` is upgraded to 0.6.0. The two published
24
+ framework packages gain versions that require it, `provared-langchain`
25
+ 0.2.0 and `pydantic-ai-provared` 0.2.0, whose READMEs say what changes,
26
+ and which can be installed only once `provared` 0.6.0 is published; `provared-agno` (not published)
27
+ requires 0.6.0 too. The Hermes plugin's bound is raised to below 0.7;
28
+ it does not use the default. The version in `package.json` and
29
+ `python/pyproject.toml` is now 0.6.0, so that the packages' bounds hold
30
+ against the library in this repository. Tests:
31
+ `test/documents.test.mjs` and `python/tests/test_record_function.py`.
32
+ - **One correction and three smaller changes from a narrow review of the
33
+ five fixes below (24 October 2026)**, in both languages. None changes
34
+ the bytes of any record; one widens what a checker accepts back to what
35
+ services in use write.
36
+ - *The order of a set is held only where a standard requires DER.* The
37
+ five fixes held every set the checker reads as a set to the order of
38
+ X.690, section 11.6, the list of certificates among them. The review
39
+ found three genuine time-stamps of 2017 from a public service, kept
40
+ as test data by the open-source Go library digitorus/timestamp
41
+ (nothing of which is copied here), that list their two certificates
42
+ in chain order, the signing certificate before its issuer, which is
43
+ the reverse of DER order: before the five fixes they checked as valid,
44
+ and the five fixes refused them in both languages. RFC 5652 requires
45
+ DER of the signed attributes alone, "even if the rest of the structure
46
+ is BER encoded" (section 5.3). So now the signed attributes (RFC 5652,
47
+ section 5.3) and every set inside them, the statement (RFC 3161,
48
+ section 2.4.2) and every set inside it, and each certificate (RFC
49
+ 5280, section 4.1) and every set inside it are held to that order;
50
+ the signed data's own sets (the list of fingerprint methods, the list
51
+ of certificates, the list of withdrawn certificates and the list of
52
+ signers) and the parts of a signer outside its signed attributes may
53
+ be in any order. Every other rule of strict DER still holds everywhere
54
+ (format description, section 21.2, item 1, which now names the sets
55
+ held to order). A test in each language builds a time-stamp with its
56
+ certificates out of DER order and expects it to pass; the made-up
57
+ time-stamps are enough for that.
58
+ - *What is walked under a tag that hides its kind*, said exactly. The
59
+ five fixes said such a part "is read as bytes only". In both
60
+ languages a part written in parts under such a tag has the parts
61
+ inside it read like any other (the review's probe, an `otherName`
62
+ holding a whole number not in its shortest form, is refused as such);
63
+ the tagged part itself is only neither refused as a string written in
64
+ parts nor held to the order of a set; and one written in one piece is
65
+ read as bytes only. The format description, the code's own notes and
66
+ the two entries below now say so.
67
+ - *RFC 5652, section 11*, forbids a second instance, and a second value,
68
+ of the signing-time attribute too (section 11.3), not of the
69
+ content-type and message-digest attributes only; corrected below.
70
+ - *The count of sets in the real time-stamps*, below, was wrong: the
71
+ sample record holds one time-stamp (`samples/office-supplies.jsonl`,
72
+ line 10), repeated in its Show, not two; and the four real time-stamps
73
+ hold 43 sets between them, not 53. Each of the 43 is in DER order, so
74
+ the five fixes refused none of them.
75
+ - *Shared test files remade:* `stamps`, `stamps-openssl`,
76
+ `stamps-flips-ecdsa`, `stamps-flips-ed25519` and `stamps-flips-rsa`.
77
+ Three stored answers of `stamps` change in substance: "certificates:
78
+ two certificates out of DER order" and "wrapper: two fingerprint
79
+ methods listed out of DER order" are now valid, and "wrapper: the
80
+ signed data that is a set" is refused for its kind, not its order.
81
+ Fifteen changed-byte cases that tripped the order of a set before
82
+ anything else (four of `stamps-openssl`, six of `stamps-flips-ecdsa`,
83
+ three of `stamps-flips-ed25519`, two of `stamps-flips-rsa`) are now
84
+ refused for what the changed byte does next. Every other difference
85
+ is a made-up service's fresh key (`stamps-flips-ecdsa` has one case
86
+ more, its signature a byte longer). A re-check then found the three
87
+ re-walks held by no deliberate case (weakened, every test still
88
+ passed), so `stamps` gains three refused cases, each caught in both
89
+ languages: a message-digest attribute whose second, larger value
90
+ stands before its first; a relative distinguished name out of DER
91
+ order inside a certificate; and one inside the statement's name of
92
+ the service. The JavaScript unit test builds the first of these too,
93
+ and checks that the list of fingerprint methods may be in any order,
94
+ as the Python test did. The tests are `test/audit-fixes.test.mjs` and
95
+ `python/tests/test_audit_fixes.py`.
96
+ - **Five fixes from the independent review of the audit fixes (24 October
97
+ 2026)**, in both languages. None changes the bytes of any record; four
98
+ narrow what a checker accepts, and one adds to the shared test files.
99
+ - *Strict DER is now what the checker enforces.* A string of bits, bytes
100
+ or characters, or a time, written in parts (the constructed form,
101
+ which X.690, section 10.2, forbids in DER) is refused
102
+ (`stamp-bad-data`); before, a certificate whose signature was a bit
103
+ string in two parts passed in both languages. The items of a set must
104
+ be in the order X.690, section 11.6, requires, where a standard
105
+ requires DER of the set (narrowed to those places by the review
106
+ above; as first made, every set the checker read as a set was held to
107
+ it). Every real time-stamp the repository holds was checked first:
108
+ the three made with the OpenSSL program (`test/fixtures/stamps.json`)
109
+ and the one in the sample record (repeated in its Show) hold 43 sets
110
+ between them, every one in that order. The made-up services of the
111
+ tests now write their sets in that order, as a real service does;
112
+ before, the one that makes the shared test files wrote the signed
113
+ attributes of its SHA-512 cases out of order (format description,
114
+ section 21.2, item 1).
115
+ - *A time-stamp signed with Ed25519 gives its fingerprint method and
116
+ its signing method no parameters*, as RFC 8419, section 3.1, requires
117
+ (`stamp-invalid`); before, only the method's name was checked, and
118
+ the made-up services wrote NULL parameters, which that section
119
+ forbids. The refusal has words of its own that name the cause (a
120
+ method other than SHA-512, or parameters), where before it said the
121
+ signature did not fit the certificate. The list of fingerprint methods
122
+ in the signed data is read for its form only (section 21.2, item 4).
123
+ - *A second written form of a P-256 key* is in the shared test files:
124
+ the point with x = 5 and the point with y = 5, each as itself
125
+ (accepted) and with that half written as 5 plus the prime (refused as
126
+ `bad-key`), so that a checker that reduced a half instead of refusing
127
+ it would fail `keys.json`. Before, every case at or above the prime
128
+ also failed the curve's equation.
129
+ - *Citations.* The one-instance rule for the signing-certificate
130
+ attribute is RFC 5035's (section 3 for the newer form; section 5,
131
+ which writes section 5.4.2 of RFC 2634, for the older), not RFC 5652
132
+ section 5.3's, which requires DER; RFC 5652 forbids a second instance,
133
+ and a second value, of the content-type, message-digest and
134
+ signing-time attributes (sections 11.1 to 11.3) and says nothing of
135
+ this one. Corrected in
136
+ the entry below, the format description, the list of threats and the
137
+ code. The format description now says that using the newer form where
138
+ both are present, without comparing the two, is this format's choice
139
+ (section 15, item 22): RFC 5035, section 7, lets a reader refuse such
140
+ a time-stamp.
141
+ - *Reach.* "Every bit string in a time-stamp" and "every part is read"
142
+ now say what the checker reads as such: a value inside an octet string
143
+ (an extension's value) is read as bytes only, as is a part written in
144
+ one piece under a tag that hides its kind; one written in parts has
145
+ the parts inside it read like any other (made exact by the review
146
+ above).
147
+ - *Shared test files remade:* `keys` (four cases added: the point of
148
+ P-256 with x = 5 and the point with y = 5, each as itself and with
149
+ that half written as 5 plus the prime); `der-strict` (thirteen added,
150
+ for strings written in parts and sets in and out of DER order; twelve
151
+ stored answers change: "a constructed octet string", "an unknown
152
+ constructed tag", which is a character string in parts, and ten random
153
+ cases are now refused, or refused in other words, for a string in
154
+ parts or a set out of order); `stamps` (eleven added, among them an
155
+ Ed25519 service with parameters in each place and sets out of DER
156
+ order; "attributes: in another order" is now refused as not DER,
157
+ "signing method: Ed25519 named with parameters" is now refused, and
158
+ the two Ed25519 cases that name another method have the new words;
159
+ the "unused bits" cases of a certificate's key are now two each, one
160
+ with the unused bit zero and one with it set, where before the answer
161
+ depended on the last bit of a key made at random);
162
+ `stamps-strict-keys` (the same split of its three "unused bit"
163
+ cases); `stamps-openssl`, `stamps-flips-ecdsa` and `stamps-flips-rsa`
164
+ (thirteen, fourteen and fifteen changed bytes of the real and made-up
165
+ time-stamps now trip the order of a set before anything else, and
166
+ fifty cases of the RSA set hit other bytes because its signed
167
+ attributes are now written in DER order); `stamps-flips-ed25519` (the
168
+ time-stamp is four bytes shorter, its two NULLs gone, so its cases do
169
+ not compare by name with the earlier set). The reviewer's probes were
170
+ run again on the fixed code: its 16 ways round the checks and 2,534
171
+ random cases give the same answer in both languages, words included.
172
+ The tests are `test/audit-fixes.test.mjs` and
173
+ `python/tests/test_audit_fixes.py`; each new refusal fails without its
174
+ fix.
175
+ - **Eight fixes from the audit of the Internet-Draft (24 October 2026)**,
176
+ in both languages, for version 0.6.0. None changes the bytes of any
177
+ record. Five narrow what a checker accepts, so that two checkers built
178
+ from the format description cannot disagree; three change the documents
179
+ only.
180
+ - *A time-stamp that names the certificate that signed it more than once
181
+ in the same form* (two ESSCertIDv2 attributes, or two ESSCertID
182
+ attributes) is refused (`stamp-invalid`), as RFC 5035 requires
183
+ (section 3 for the newer form; section 5, which writes section 5.4.2
184
+ of RFC 2634, for the older). Before, the last ESSCertIDv2 was used or,
185
+ where there was none, the first ESSCertID. Where both forms appear
186
+ once each, the newer still names the certificate (format description,
187
+ section 21.2, item 3).
188
+ - *A DER bit string whose unused bits are not zero* is not strict DER
189
+ and is refused (`stamp-bad-data`), as X.690, section 11.2.1, requires;
190
+ before, only the count of unused bits was checked. A string of no bits
191
+ must count none. This reaches every bit string the checker reads as
192
+ such (not one inside an octet string's value, such as an extension's,
193
+ nor one written in one piece under a tag that hides its kind), not
194
+ only the key's (section 21.2, item 1).
195
+ - *An ES256 key that is not a point on the curve P-256*, or either half
196
+ of which is not below the curve's prime, is refused as `bad-key`
197
+ wherever a key is checked, before any signature is tried (section
198
+ 3.8). Before, it passed the key check and was reported as
199
+ `signature-invalid` when a signature was checked under it. A slip, an
200
+ approval, a cancellation or a vouching record that names such a key
201
+ now fails on the key.
202
+ - *An Ed25519 signature whose number S is not below the order L* is
203
+ refused by the library itself, in every record and in a time-stamp, as
204
+ RFC 8032, section 5.1.7, requires (section 3.8). Before, both
205
+ languages relied on the library beneath them (OpenSSL), which refuses
206
+ it; a third implementation on another library might not. The twelve
207
+ cases of the paper "Taming the many EdDSAs" (Chalkias, Garillot, Kondi
208
+ and Nikolaenko, 2020) are a new shared test file,
209
+ `test-vectors/ed25519-signatures.json`, with ten cases made here, each
210
+ with the answer the format's rules give, so that the rules are pinned
211
+ and not the library: a small-order or non-canonical key is `bad-key`;
212
+ a small-order or non-canonical R, or an S not below L, is invalid; a
213
+ mixed-order key is checked without the cofactor and may pass.
214
+ - *A time-stamp signed with Ed25519 must name SHA-512* as the signer's
215
+ fingerprint method, as RFC 8419, section 3.1, requires; one that names
216
+ SHA-256 or SHA-384 is refused (`stamp-invalid`), on every device, as a
217
+ mismatched method is for RSA and ECDSA (section 21.2, item 4). This
218
+ settles open issue 4 of the Internet-Draft. A service that follows RFC
219
+ 8419 produces nothing else; the made-up service of the tests now does
220
+ the same, and writes the method with no parameters, as that section
221
+ also requires (see the entry above).
222
+ - *The format description* now says that a time-stamp's time is read to
223
+ the millisecond and compared in milliseconds between seals (sections
224
+ 21.2 and 21.3), which is what the code always did ("taken to the
225
+ second, rounded down" described only how it is shown); that a checker
226
+ SHOULD bound the total size of a book it accepts and MAY stop at the
227
+ first problem, since 100,000 entries of 131,072 bytes would be about
228
+ 13 gigabytes (section 9); and the rules above, in sections 3.8, 14, 15
229
+ and 21.2.
230
+ - *The list of threats, section 3,* states a limit the audit found: a
231
+ passkey used for anything else at the same website is a signing
232
+ oracle. Any page at an origin under the slip's `rpId` can hand the
233
+ device a record's fingerprint as an ordinary sign-in challenge and have
234
+ it signed; the person sees a sign-in prompt. So records should be
235
+ signed only with a passkey registered to a relying party used for
236
+ nothing else, and a signing page should show the record first. The
237
+ checker has no option yet to name the origin a reader expects.
238
+ - *Shared test files remade:* `keys` (six cases added for P-256);
239
+ `der-strict` (six added; "a bit string with 7 bits unused" and
240
+ thirteen random cases now refused); `stamps` (nine added and two
241
+ removed, the sound Ed25519 cases being remade with SHA-512; the four
242
+ cases with the signing-certificate attribute twice now refused);
243
+ `stamps-strict-keys`, `stamps-openssl`, `stamps-flips-ecdsa`,
244
+ `stamps-flips-rsa` and `stamps-flips-ed25519` (changed bytes that now
245
+ trip the bit-string rule first; the Ed25519 token names SHA-512);
246
+ `vouching-contents` and `vouchings-written` (the made-up passkey is
247
+ now a point on the curve). New: `ed25519-signatures`. The tests are
248
+ `test/audit-fixes.test.mjs` and `python/tests/test_audit_fixes.py`;
249
+ each refusal fails without its fix.
250
+
251
+ - **Witnesses, and consistency proofs** (format description, section 29),
252
+ in both languages. Whoever keeps a book could show one history to one
253
+ reader and another to another, each sealed. A witness is a party, with
254
+ a key set of its own, that keeps for each book the latest seal it has
255
+ signed for, and signs a statement for a new seal only where the new
256
+ seal extends that one.
257
+ - *Consistency proofs*, exactly as RFC 9162, section 2.1.4, sets out:
258
+ `consistencyProof(leaves, m)` and `verifyConsistency(m, n, rootM,
259
+ rootN, proof)` (Python: `consistency_proof`, `verify_consistency`).
260
+ For m equal to n the proof is empty and the roots must be the same;
261
+ for m of 0 the proof is empty and the earlier root must be that of the
262
+ empty tree. A second implementation, written in the tests from the RFC
263
+ alone, is compared with the library's for every pair of sizes up to 64
264
+ leaves, with every value of every proof changed and left out.
265
+ - *A new kind of record, the witness statement*: label
266
+ `vnd.provared.witness.v0+json`, two signatures (Ed25519 and
267
+ ML-DSA-87), content `type`, `id`, `seal` (the seal's fingerprint),
268
+ `size` and `root` (repeated from the seal), `by` (`{keys, name}`)
269
+ and `when`. `writeWitness(fields, signers)` (Python:
270
+ `write_witness`); signers may be keys or signer objects. There are now
271
+ twenty-six labels.
272
+ - *Beside a seal*: a seal's line may hold `witnesses`, 1 to 4
273
+ statements, none from the same key set twice; and a seal handed over
274
+ beside the book (`seals`, `--seal`) may hold statements that came
275
+ later. A checker checks each: its signatures, that it speaks of that
276
+ very seal (its `seal`, `size` and `root`), and that it is not dated
277
+ more than 300 seconds before the seal. One new code,
278
+ `witness-invalid`; it joins the format's own list of finding codes.
279
+ A statement that does not pass is a problem with its entry, or, handed
280
+ over, with the whole check (the seal it came with is used all the
281
+ same).
282
+ - *Which witnesses count*: the option `witnesses` (the fingerprints of
283
+ the key sets of the witnesses the person trusts), `--witness
284
+ <fingerprint>` on the command line (may be repeated), and a box on the
285
+ checking page. A statement counts only where its witness is named,
286
+ both its signatures were checked on the device, **the book's first
287
+ line is a permission that passed, signed by a passkey the person
288
+ named (`issuerKeys`), the person named the recorders they expect
289
+ (`sealKeys`)**, and its seal passed with its Ed25519 and ML-DSA-87
290
+ signatures checked there (its third, SLH-DSA, may not be: the Python
291
+ version and browsers count on the first two). A witness knows a book
292
+ only by its permission and its recorder's keys, which the recorder
293
+ chooses; without both fixed by the reader, a recorder could start a
294
+ second history with another first line and have the same witness
295
+ sign for it (found by the independent review). Where a statement
296
+ from a trusted witness is not counted for that reason, the answer
297
+ says why. One from a witness not named is shown and not counted.
298
+ - *What the answer says*: each seal entry, and each seal handed over,
299
+ gains `witnesses` (each statement with its fingerprint, its witness's
300
+ key set fingerprint, its name as a label, its `when`, its signatures,
301
+ `state`, `trusted` and `counted`); the summary gains `witnessed`: the
302
+ latest seal for which a statement counts, the entries it covers, the
303
+ witnesses, and every seal witnessed, or null. Where a seal is
304
+ witnessed, the answer's list of what the check does not show says what
305
+ a witness shows and what it does not. Being witnessed excuses nothing,
306
+ is never said of a book with a problem, and is not needed for exit
307
+ code 0. The command-line checker prints "Witnessed by a witness you
308
+ trust?".
309
+ - *The witness itself*, in the library (it needs Node.js, because it
310
+ checks SLH-DSA): `openWitness({ by, signers, state, clock })`, with
311
+ `witness(entry, proof, firstLine)`, `update`, `ready`, `latest(book)`
312
+ and `state()`. It knows a sequence of books by the recorder's key set
313
+ with the passkey and the agent of the permission on the first line
314
+ (`bookIdentity`); ties the first
315
+ line to the seal with an audit path when it first sees a sequence and
316
+ at each new book, and keeps it
317
+ tied with each consistency proof (`witnessProof` makes the proof to
318
+ send); signs only for a seal whose three signatures it checked, which
319
+ names the seal it holds, is not dated before it, extends it, and is
320
+ dated no more than 300 seconds after its clock; never signs two
321
+ different seals of one book at one size (the same seal again gets the
322
+ same statement); checks every signature it makes before it keeps or
323
+ gives out a statement; and hands back its state with each statement,
324
+ to be saved first. Opened from a saved state, it signs nothing until
325
+ it has been brought up to date from a copy of what it gave out
326
+ (`update`) and told so (`ready`). A witness running as a service is
327
+ not part of this change. The Python version has no witness.
328
+ - The check before acting, the stub writer and a checker that is
329
+ carried on read statements as a whole check does; a statement's
330
+ signatures that the device cannot check leave the record not fully
331
+ checked, as for any record.
332
+ - *The books of one permission* (format description, section 21.6): a
333
+ seal may give `book`, its book's number among the books its recorder
334
+ keeps under one permission (2 or more; absent for the first book, so
335
+ every seal written before reads as it did; `1` is refused, and a seal
336
+ with `book` must give `previous`). The first seal of a later book
337
+ names, as `previous`, the last seal of the book before: a checker of
338
+ that book alone does not compare it, says so, and, handed that seal,
339
+ says whether the book follows on from it (`seal-chain-broken` where it
340
+ is by other keys, `time-went-backwards` where it is dated after the
341
+ first seal); a seal of another number is not compared by its place.
342
+ Every seal of a book gives the same number (`seal-chain-broken`).
343
+ `writeSeal` takes `book` (Python: `write_seal`). The recorder beside
344
+ Claude Code does not yet number its books.
345
+ - *The witness keeps every book of one recorder, person and agent as
346
+ one sequence*: it knows a sequence by the recorder's keys with the
347
+ passkey and the agent's key set of the permission on the book's first
348
+ line, so a renewal (another permission from the same passkey to the
349
+ same agent) is the same sequence, and a book that starts with it must
350
+ follow on from the seal the witness holds (found by the second round
351
+ of the review: known by the permission alone, a book that began with
352
+ a renewal forked the history). It keeps no book whose first line is
353
+ not a permission (`slip-missing`; a first line given with its line
354
+ feed is `bad-field`), signs for the first seal of the next book only
355
+ where that seal names the latest seal it holds and its first line is
356
+ tied to it, and never again signs for the book before; a fork across
357
+ books is refused. Its state names each sequence by its passkey and
358
+ agent. One recorder key set keeps one sequence for each person and
359
+ agent: books kept side by side for one agent need recorder keys of
360
+ their own.
361
+ - *Every permission before a witnessed seal must name the first line's
362
+ passkey and agent* (found by the third round of the review: a book
363
+ that started with agent Y's permission, then held agent X's
364
+ permission and stubs with the one over the limit left out, was signed
365
+ as the first book of the person and agent Y and read as witnessed).
366
+ A statement now counts only where every permission among the entries
367
+ its seal covers names the same passkey key and agent key set as the
368
+ first line's; otherwise a note says why. A renewal by the same person
369
+ for the same agent, and each agent's own book under one recorder (as
370
+ the gateway keeps them), are witnessed as before. A Show counts a
371
+ statement only where its pages hold every entry the seal covers, so it
372
+ never counts one the whole book would not. The witness cannot apply
373
+ this itself: it sees a book's first line, not its entries.
374
+ - *A seal held elsewhere that gives another book number than the book
375
+ is not compared with it, and gives no pass* (found by the second
376
+ round of the review: a book cut short and sealed again as "book 2"
377
+ otherwise hid the cut). It is not used; its note says what it shows
378
+ and that this book does not show whether that book was cut short;
379
+ the command-line checker says "Seals held elsewhere compared? NO" and
380
+ does not end with code 0, and the checking page gives no green
381
+ answer.
382
+ - New shared test files: `tree-consistency`, `tree-consistency-checks`,
383
+ `witness-contents`, `witnesses-written`, `witness-books`,
384
+ `witness-shows`, `witness-before`, `seal-books`, `chained-books`,
385
+ `chained-shows`, `renewal-books`, `renewal-shows`, `two-agent-books`
386
+ and `two-agent-shows`. **Every stored answer of a whole
387
+ book, a Show or the stub writer changes**, and only by the new
388
+ members: `summary.witnessed: null`, and `witnesses: []` on every seal
389
+ entry and every seal handed over; `denial-constants` gains
390
+ `witness-invalid`. Eleven files change, each checked case by case to
391
+ differ in those members alone (`books`, `denial-books`,
392
+ `denial-constants`, `denial-scenarios`, `denial-shows`,
393
+ `held-seal-shows`, `held-seals`, `recorder-details-after`,
394
+ `recorder-scenarios`, `shows`, `tool-scenarios`); no record was
395
+ signed again.
396
+ - Section 28's list of finding codes now also names `book-cut-short`
397
+ and `seal-not-in-book`, which the library already gave.
398
+ - **A checker of version 0.5.0 or earlier refuses a seal's line that
399
+ holds `witnesses`** (`unknown-entry`), **and a seal that gives `book`**
400
+ (`bad-field`).
401
+ - **A witness that runs as a service, and the recorder beside Claude Code
402
+ numbering its books and asking witnesses** (`integrations/claude-code`:
403
+ `witness.mjs`, the new command `provared-witness`, `witnessed.mjs` and
404
+ `proofs.mjs`). The package now needs `provared` 0.6.0 or later.
405
+ - *The recorder numbers and chains its books* (format description,
406
+ section 21.6). The first seal of a new book gives the book's number
407
+ and names the last seal of the book before; its later seals give the
408
+ same number. "The book before" is the latest kept book whose first
409
+ line is a permission from the same passkey, for the same agent, sealed
410
+ with the recorder's keys, as a witness knows a sequence; it is read
411
+ from the kept files, so the numbers hold across a restart, and a kept
412
+ book that cannot be read stops the seal rather than give it a number
413
+ that may be wrong. A book that begins with another passkey's
414
+ permission begins a sequence of its own, with no number. The first
415
+ book gives no number, so the record so far reads as it did, and a book
416
+ whose seals give no number goes on giving none. Before the book in
417
+ force is first sealed, every kept book a late call could still come
418
+ back to is closed, and sealed again where something came after its
419
+ seal; such a call is then recorded in the book in force. Every seal's
420
+ line is made sure to be on the disk (fsync), and a book is kept as
421
+ `book-<n>.jsonl` only once it is.
422
+ - *`provared-witness`*, a small service on 127.0.0.1 (port 8796 by
423
+ default) around the library's witness (`openWitness`), in a folder of
424
+ its own: its keys (Ed25519 and ML-DSA-87), a token for the recorders
425
+ it serves and another for its operator, its settings (`witness.json`:
426
+ its name, the fingerprints of the recorders it serves, its port and
427
+ limits), a journal, a state and a checkpoint. For each new statement
428
+ the journal line and then the state are made sure to be on the disk
429
+ (fsync) before the statement is given out; what it says it holds
430
+ (`/latest`, `status`) is what it last saved, never what the library's
431
+ witness holds in memory, and once anything cannot be saved it gives
432
+ out nothing more, by any route, until it is started again. Every 256
433
+ journal lines it writes a checkpoint (the latest seal of each sequence,
434
+ with its statement) and begins the journal again; a start reads the
435
+ journal a line at a time, and in full only the lines its state does
436
+ not yet reflect. At every start, the first included, it signs nothing
437
+ (`witness-not-ready`) until its operator says that its folder was not
438
+ put back to an older copy (`ready`, or `start --ready`); `update <book>
439
+ [--seal <file>]` hands it, from a copy, the statements it signed for a
440
+ book's seals, and `forget <sequence>` makes it hold a sequence no more
441
+ (it then signs whatever it is next shown for that sequence). It
442
+ refuses to start where its journal has gone while its keys are there,
443
+ where a journal line is not one it writes or lines are numbered out of
444
+ order, where its state reflects more lines than the journal holds, or
445
+ where the journal follows a checkpoint it does not have; a last journal
446
+ line cut off by a stop is taken off; a lost state is rebuilt from the
447
+ checkpoint and the journal; a stop between the steps of a checkpoint
448
+ is mended at the next start. One witness at a time on a folder
449
+ (`witness.lock`, taken over from a process that has ended under a
450
+ second file only one start can make). Who may ask: a program proves
451
+ that it holds the token, which never travels, as for the recorder's
452
+ feed (a hello, then HMAC-SHA-256 over the question, its body included,
453
+ and over the answer), one question to a conversation; the operator's
454
+ commands (`status`, `ready`, `update`, `forget`, `stop`) take the
455
+ operator's token; it signs only for seals of the recorders named, and
456
+ starts a new sequence only from a permission whose passkey signature
457
+ the library's checker passes. Limits: 120 new statements in any hour
458
+ (shared by every recorder it serves), 64 sequences, 8 questions that
459
+ sign at once, bodies of 1 MiB. Library refusals keep the library's
460
+ codes.
461
+ - *The recorder asks the witnesses named in `witnesses.json`* (at most
462
+ four, each on 127.0.0.1, with the fingerprint of its key set and its
463
+ token; written by the new commands `add-witness`, which checks that the
464
+ witness answers, holds the token and signs with that key set, and
465
+ `remove-witness`). After each seal it asks each witness what it holds
466
+ and whether it signs now, and shows one that does, in order, the seals
467
+ it has not signed for whose lines are on the disk, with the proof RFC
468
+ 9162 sets out, made from the book's line fingerprints kept as the book
469
+ grows (`proofs.mjs`, the same proofs as the library's `witnessProof`
470
+ without reading the book again), and the book's first line; a witness
471
+ that holds none of this sequence is shown the latest only. For the
472
+ seal that `seal` makes, the seal's entry is first kept apart on the
473
+ disk (`pending-seal.json`, written at its place at the next start if a
474
+ stop comes first, or let go where its line is in the book or in a book
475
+ kept since; one at a time), the witnesses are asked, and the
476
+ statements that come within five seconds stand beside the seal on its
477
+ line; the wait ends at once when any other request arrives, so no tool
478
+ call waits for a witness. A statement is taken only where the
479
+ library's checker passes it for that seal, as signed by the witness
480
+ named; one that comes later is kept beside the book in
481
+ `witnessed/<seal>.json`, in the form the checker takes with `--seal`,
482
+ and `check` hands those of the book checked over, names the witnesses,
483
+ and says plainly that those copies are the book's own seals from the
484
+ recorder's folder, not seals held elsewhere, and cannot show the book
485
+ cut short. A witness that is down, refuses, could not save, or hands
486
+ back what does not pass never stops recording: `status` shows, for
487
+ each, the seal it holds, whether that is the latest, and why not; one
488
+ that holds a seal of this sequence in none of the recorder's books is
489
+ reported (`witness-holds-another`) and is sent no seal. The record page
490
+ tool, tried on a numbered book with statements, reads the books as
491
+ before (it names no witness, and hands over no statement kept beside
492
+ the book); the application's uploader reads the books through the
493
+ feed, which is unchanged. Measured on a book of 93.5 MB with four
494
+ witnesses: a tool call's two questions took at most 67 and 99
495
+ thousandths of a second after a seal; during any seal a call waits for
496
+ the seal itself, about 3.3 seconds on such a book, with or without
497
+ witnesses.
498
+ - *After its independent review*: a statement that could not be
499
+ journaled was still given out through `/latest`, and after a restart
500
+ the witness signed a different seal at the same size (fixed: only what
501
+ is saved is given out, and nothing once a save has failed); a folder
502
+ put back to a copy taken before the first statement started ready and
503
+ signed a fork (fixed: never ready at a start until told); the journal
504
+ was read whole at each start and could no longer be read at about
505
+ 8,000 lines (fixed: checkpoints, and a line at a time); proofs read the
506
+ whole book each time (fixed: line fingerprints kept); and the low
507
+ findings on the seal kept apart, the lock, the agent in the book
508
+ before, `check`'s words and the passkey signature of a new sequence.
509
+ - Stated: on one computer under one user account the agent can reach
510
+ the witness's folder, so a witness means more under another account,
511
+ on another computer through a tunnel, or run by someone else; a
512
+ witness on another computer reached directly is not built; what
513
+ travels is proved, not encrypted; one token serves every recorder the
514
+ witness serves, and each can read what it holds for any of them, fill
515
+ its sequences with permissions signed by passkeys of its own, and use
516
+ up the shared statements; a witness put back to an older copy and told
517
+ it is ready can be made to sign a fork (a test shows it); forgetting a
518
+ sequence a recorder still uses lets that recorder have a second
519
+ 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.
520
+ - **The recorder beside Claude Code offers its books to a reader on this
521
+ computer, through a feed on its own port** (`integrations/claude-code`,
522
+ `feed.mjs`), so that no other program need open a book file. On
523
+ 11 October 2026 an uploader reading `book.jsonl` made the recorder's
524
+ rename of it fail on Windows, and seven receipts were lost; the
525
+ application's uploader now asks the recorder instead. `GET /books`
526
+ lists the books oldest first (the kept ones by number, the one in force
527
+ last), each named by its file's identity (the volume, the file's number
528
+ and its creation time, which a rename keeps, reckoned as the uploader
529
+ reckons it), with its size; `GET /lines?id=<identity>&from=<n>` (or
530
+ `book=<name>`, or neither for the book in force) answers with up to 500
531
+ lines (and at most 4 MiB) from place n, where each is in the file, the
532
+ line before place n with its fingerprint, the number of lines and the
533
+ fingerprint of the last; `max` asks for fewer, and 0 for none. Lines are
534
+ counted as the uploader and the checker count them (an empty line and a
535
+ carriage return set aside, a line with no line break not yet a line).
536
+ Where the book holds nothing at place n yet, the question waits up to
537
+ 25 seconds (`wait`) and is answered as soon as any book is written, a
538
+ late receipt or a seal again in a kept book included; at most 8 wait at
539
+ once, and one more is told to ask again (429). The recorder writes a
540
+ token into its folder at its first start (`feed-token`, 32 random
541
+ bytes, readable by its owner only where the file system keeps such
542
+ rights), and **the token never travels**: a reader opens a conversation
543
+ (`GET /hello`) with a random number; the recorder answers with a random
544
+ number of its own and HMAC-SHA-256 over both, keyed by the token, and
545
+ the reader trusts nothing until that proof checks; each question then
546
+ carries an HMAC under a key both sides work out from the token and the
547
+ two numbers, with a number used once, and each answer an HMAC under the
548
+ same key over the question's number, its code and every byte; every
549
+ proof is compared in constant time, and a question asked twice is
550
+ refused. Each of these 32-byte values is read only in the one way it can
551
+ be written in base64url, so none can be given again under another
552
+ spelling of the same bytes. A hello keeps nothing: the recorder's number for a conversation
553
+ is worked out from the time, the reader's number and a secret only the
554
+ running recorder holds, and a conversation is kept only from its first
555
+ proved question; only proved conversations (at most 16, each ended
556
+ after 10 minutes unused) give way to one another, so a program without
557
+ the token cannot push out one in use. Each question, refused or not,
558
+ still costs the recorder about a thousandth of a second, so very many at
559
+ once slow the hook in proportion. A token file that holds no token is not replaced, and the feed
560
+ then refuses every question and `status` says why. One answer is made
561
+ at a time, with a pause between two, and at most 16 are taken on at
562
+ once (429 beyond). The books the recorder holds open are read from its
563
+ own copy in memory, only as far as it has written them to their files,
564
+ so the feed answers while another program holds `book.jsonl` open with
565
+ no sharing and never gives out a receipt whose save failed; a kept book
566
+ it no longer writes is read from its file, 4 MiB at a time, and closed
567
+ again. Measured on the author's computer with a book of 98 MB: about
568
+ 110 thousandths of a second for the first question, 1.8 seconds to read
569
+ the whole book; a hook's two questions took a median of 61 thousandths
570
+ of a second with no reader and of 160 (at most 266) with two readers
571
+ reading the whole book again and again, and a hook's question 971
572
+ thousandths with 1,500 feed questions at once. The service still listens
573
+ on 127.0.0.1 only. Nothing of the record or the format changes.
574
+ - **The recorder never replaces its book because it could not read it**
575
+ (`integrations/claude-code`). Where another program held `book.jsonl`
576
+ for a few seconds while a receipt was saved, the recorder took the
577
+ failed read as "no book yet" and replaced the whole file, which then had
578
+ a new identity; an uploader following it stopped for good. Now the
579
+ reading and the adding are tried again for up to ten seconds while the
580
+ file is busy, a read that fails otherwise fails the save and leaves the
581
+ file as it is, and the file is replaced only where it was read and no
582
+ longer holds the book's start. Found by the independent review of the
583
+ feed, with four faults of the feed itself, each fixed and tested: the
584
+ token sent to whatever listened (now never sent); questions that do not
585
+ wait had no limit (on a 98 MB book, 1,500 at once held a hook up for 22
586
+ seconds); a receipt whose save failed could be given out; and a kept
587
+ book of exactly 4 MiB that ended part way through a line lost that
588
+ line's start. A second review found that a hello kept a conversation,
589
+ so that a program without the token could push out the uploader's (now
590
+ a hello keeps nothing). Where a save fails after the ten seconds, the
591
+ hook is now told the truth: the stub is signed and held by the
592
+ recorder, not yet in the book file, and written with the next save, and
593
+ `status` counts it as recorded. A third review found that an ended
594
+ conversation could be begun again under another spelling of its number
595
+ (now refused), and that no test held the refusal of a hello dated ahead
596
+ of the clock (now tested). 37 tests of the feed; the package's 130 pass.
597
+ - **Seals held elsewhere, handed over beside the book** (format
598
+ description, section 21.5), in both languages. Whoever keeps a book
599
+ could cut off its end, the last seal with it, and what is left still
600
+ checks. Now anyone who keeps a copy of a seal (the person, an auditor, a
601
+ second copy out of the recorder's reach) can hand up to 64 of them to
602
+ the checker, each as the entry it is in the book: the option `seals`,
603
+ `--seal <file>` on the command line (may be repeated), and a file
604
+ chooser in the checking page's panel "What you were handed with the
605
+ record". A seal at entry n must stand at entry n of the book. Two new
606
+ codes, both problems with the book: `book-cut-short` (the book ends
607
+ before that place) and `seal-not-in-book` (the entry there is not a
608
+ seal with the same content; the same content with other signatures is
609
+ the same seal, and is said). Nothing in a seal names its book, so a
610
+ seal of another book by the same recorder is reported against the book
611
+ being checked. Each seal handed over is checked as any seal is, once
612
+ for a checker that is carried on and for the stub writer; it counts
613
+ only where its recorder is named in `sealKeys` (`--sealer`), and on a
614
+ device without SLH-DSA (the Python version, and browsers that have
615
+ ML-DSA) on its Ed25519 and ML-DSA-87 signatures, which the answer says.
616
+ Seals handed over without `--sealer` give no pass (code 1), and a line
617
+ 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
618
+ the whole check. Its own time-stamps are shown, and are not used for the
619
+ entries of the book; where they differ from those the book holds beside
620
+ the same seal, the answer says so. A Show does not use seals held
621
+ elsewhere, and says so. Each one is given in the answer's `held` list,
622
+ after any cancellations, with `kind: "seal"`, `place`, `inBook`,
623
+ `sealer`, `used`, `verified`, its signatures and time-stamps; the
624
+ type declarations give `held` as `(HeldCancellation | HeldSeal)[]`.
625
+ The check before acting and the stub writer take the option too, and a
626
+ seal held elsewhere whose third signature this device cannot check is
627
+ excused there as a seal in the book is. No new kind of record. The two
628
+ codes join the format's own list of finding codes (`FINDING_CODES`).
629
+ New shared test files: `held-seals`, `held-seal-shows` and
630
+ `held-seals-before`; `denial-constants` changes for the two new codes,
631
+ and no other shared file changes.
632
+ - **A new kind of record: the denial** (format description, section 28),
633
+ in both languages. It is the agent's own side's signed statement that it
634
+ did not take an action that was asked for, and who said no: the check
635
+ before acting (with the finding codes it gave, from the format's own
636
+ list, now written out in section 28), the person, or a rule of the
637
+ software around the agent. It is signed with the agent's two keys, or a
638
+ helper's under its pass, with the new labels
639
+ `vnd.provared.denial.v0+json` (twenty-four labels in all). In a book it
640
+ comes after its slip, is in no chain and is compared with nothing; a
641
+ checker shows it as the agent's side's own word, counts it
642
+ (`summary.counts.denials`, new in every answer), and never counts it
643
+ against the agent. `writeDenial` (`write_denial`), `denialCodes`,
644
+ `FINDING_CODES` and `DENIAL_SOURCES` are new. The stub writer writes a
645
+ denial whenever `act` does not take an action if it is opened with
646
+ `denials: true` (`more.denial: false` writes none for one call);
647
+ `deny` writes one for a refusal from elsewhere; at most
648
+ `denialsPerHour` (600 by default) are written in any hour of the
649
+ writer's clock, each counted at the clock's time when it joins the
650
+ book (whatever date it states), the answers count those left out, and
651
+ a stub is never kept out. `deny` records what its caller states. `recordTools` and
652
+ `record_function` pass it on (`NotTaken.denial`), and where the person
653
+ may yet approve a call they write none until it is known the call will
654
+ not run, so one call never leaves both a stub and a denial. The
655
+ command-line checker and the checking page show denials. **A checker of
656
+ an earlier version refuses a book that holds a denial** (`unknown-entry`).
657
+ New shared test files: `denial-contents`, `denials-written`,
658
+ `denial-codes`, `denial-constants`, `denial-books`, `denial-shows`,
659
+ `denial-before` and `denial-scenarios`; `books`, `shows`,
660
+ `recorder-scenarios`, `recorder-details-after` and `tool-scenarios`
661
+ gain `denials: 0` in their stored counts, and nothing else changes in
662
+ them.
663
+ - **`provared-hermes` 0.1.0, a plugin for Hermes Agent**
664
+ (`python/integrations/provared-hermes/`): every tool call of a Hermes
665
+ agent is checked against the slip before it runs, and a stub is
666
+ written once it has run; a call the slip does not allow is not run,
667
+ and the model is told why. It runs no recorded call on a Hermes it was
668
+ not tested with (0.21.7 and later 0.21 versions) unless told to, and
669
+ it loads on any Hermes so that its own check can refuse. Its commands
670
+ are named by the same reading of the command text as the recorder
671
+ beside Claude Code (below), with shared test rows that both languages
672
+ answer alike. Checked by independent reviewers over eight rounds, the
673
+ last six with the shared reading of commands, the last three clean;
674
+ 71 tests. Not on the Python Package Index yet: the README gives the
675
+ command to install it from this repository.
676
+ - **The recorder beside Claude Code reads commands more closely**
677
+ (`integrations/claude-code/`, not published), with the same reading
678
+ as the Hermes plugin. A deletion or a publication is now named as one
679
+ where it sits inside `"$( … )"` or back-quotes in double quotes, with
680
+ quotation marks, `case` choices and comments inside; after a shell,
681
+ `eval`, `ssh`, `watch` or another program that runs a command line;
682
+ in a quoted command after `-c`, `-Command` or `-ArgumentList`; where a
683
+ program is written as a path; or in text piped or handed into a
684
+ shell. More publishing and deleting commands are named (among them
685
+ `gem push`, `helm push`, `dotnet nuget push`, `docker buildx --push`,
686
+ `rsync --delete`, `DELETE FROM` given to a database client and
687
+ `redis-cli FLUSHALL`). Quoted words that run nothing, such as a commit
688
+ message holding `rm`, are no longer named deletions; a back-quoted
689
+ command in double quotes is, because the shell runs it. On a varied
690
+ set of 11,340 deleting and publishing lines, none is named less
691
+ strictly than before; on 109 everyday commands, 2 are named deletions,
692
+ as before. The longest lines tried (about 900,000 characters) are read
693
+ in under a second. The README lists what the reading still misses: a
694
+ command line kept in a file or a variable, `-EncodedCommand`, and
695
+ others. Writing the book file, the slip and the list of trusted
696
+ passkeys is retried for up to ten seconds where Windows holds the file
697
+ busy, and leaves no temporary file behind; a renewal held by a busy
698
+ file can keep the service waiting about 40 seconds, during which calls
699
+ are refused, never let through. Checked by an independent reviewer
700
+ over six rounds, the last three clean; 60 tests.
701
+ - **The recorder beside Claude Code starts a new book at 90,000 entries
702
+ or 100 MB** (`integrations/claude-code/`, not published). A book holds
703
+ at most 100,000 entries, and once full every call would be refused. At
704
+ 90,000 entries or 100,000,000 bytes of text, whichever comes first, or
705
+ on the command `new-book`, the recorder seals the book, keeps it beside
706
+ the new one as `book-1.jsonl`, then `book-2.jsonl` and so on, and
707
+ starts a new `book.jsonl` that begins with the permission in force,
708
+ with no gap in recording; a call that ends during the switch is written
709
+ into the kept book, which is then sealed again. Every limit is counted
710
+ across the books, from the kept books' receipts and the calls still
711
+ running, so a new book, by command, by size or after a restart, starts
712
+ no limit afresh. A checker given one book still counts limits within
713
+ that book only, so around a switch the record shown to a checker can
714
+ hold up to twice a limit within one period across two books, though the
715
+ agent was held to it. The README states what this does not close: a
716
+ kept book moved, renamed or removed while the recorder is stopped is
717
+ no longer counted, and nothing in the record shows it. The record page
718
+ (`tools/make-record-page.mjs`) lists each kept book with its own
719
+ checker's answer, and points out a gap in their numbers; the site's
720
+ publishing run fetches them from the release "record". Checked by an
721
+ independent reviewer over four rounds; 93 tests.
722
+ - **The recorder beside Claude Code numbers its books, chains each new book
723
+ to the last seal of the book before, and asks witnesses; and a local
724
+ witness service, `provared-witness`** (`integrations/claude-code`:
725
+ `recorder.mjs`, `witness.mjs`, `witnessed.mjs`, `proofs.mjs`; live for the
726
+ published record since 24 October 2026, 15:38 UTC). A book's number
727
+ follows the passkey of its first line: a book that holds a seal goes on as
728
+ its first seal says; one that holds none takes the latest kept book's
729
+ number and one, where that book began with a permission from the same
730
+ passkey and its last seal is by this recorder's keys, and its first seal
731
+ names that book's last seal; where there is no such book it gives no
732
+ number, so the record so far reads exactly as it did and its next book is
733
+ book 2. The numbers are read from the kept files, never kept in a file of
734
+ their own. Before the book in force is first sealed, every kept book that
735
+ a late call could still come back to is closed and, where something came
736
+ after its seal, sealed again, so that the first seal of book n names the
737
+ last seal of book n - 1; a kept book that cannot be read when that seal
738
+ needs it stops the seal, and the error says why. Every seal's line is
739
+ made sure to be on the disk, and a witness is shown only such seals. A
740
+ renewal with a new passkey starts no new book; the next book then begins a
741
+ sequence of its own.
742
+ - *Witnesses*: `witnesses.json` names at most four, each
743
+ `{address, key, token}` at `http://127.0.0.1:<port>`, written by
744
+ `add-witness` (which reads the token from the witness's token file and
745
+ checks that the witness answers, holds the token and signs with that key
746
+ set) and `remove-witness`. After the seal that `seal` makes (the
747
+ command, `POST /seal` or the service's own) the recorder keeps the seal
748
+ apart on the disk (`pending-seal.json`) before any witness is asked,
749
+ shows each witness every seal after the one it holds (at first contact,
750
+ the latest only), and waits at most five seconds, and no longer than
751
+ until any tool call arrives; statements that come in time stand on the
752
+ seal's own line, and every other statement is kept beside the book in
753
+ `witnessed/<seal fingerprint>.json`. Each statement is checked with the
754
+ library's own checker before it is kept. It asks again after each seal
755
+ and every minute while a witness is behind; a witness that holds a seal
756
+ of this sequence that is in none of the recorder's books is reported
757
+ (`witness-holds-another`) and asked no more. A seal kept apart that the
758
+ book changed under while the recorder was stopped is moved aside
759
+ (`pending-seal-not-placed-<time>.json`) and `status` says so. `check`
760
+ hands over the kept statements of the seals in the book checked (the
761
+ latest 64) and names the witnesses of `witnesses.json`, unless `--seal`
762
+ or `--witness` is given. `status` gains `witnesses`, `witnessesProblem`
763
+ and `pendingSealProblem`; `seal` answers with `book` and `witnessed`.
764
+ - *The witness service* (`provared-witness`: `init`, `start`, `status`,
765
+ `ready`, `update`, `forget`, `stop`) listens on 127.0.0.1:8796 with a
766
+ folder of its own (`witness-keys.jwk`, two tokens, `witness.json`,
767
+ `journal.jsonl`, `state.json`, `checkpoint.json`, `journal-mark.json`,
768
+ `witness.lock`). One token is for the recorders it serves and one for
769
+ its operator; questions and answers are proved as the feed's are
770
+ (HMAC-SHA-256 under a key worked out from the token and two random
771
+ numbers; one question to a conversation). It signs only for the
772
+ recorders its operator named, by the fingerprint of their key sets, and
773
+ only after the journal line, then the state, then the mark, are on the
774
+ disk; after a save that fails it answers 503 until it is started again;
775
+ at every start it signs nothing until its operator says it is up to date
776
+ (`ready`), after taking its own journal again, and it refuses to start
777
+ where the journal has gone while the keys are there, where a line is not
778
+ one it writes, or where its state reflects more lines than the journal
779
+ holds. Limits: 120 new statements in any hour, 64 sequences, 8 signing
780
+ or updating questions at once, bodies of 1 MiB. There is no background
781
+ start: whoever runs a witness decides how it is kept running. Stated in
782
+ the README: a witness is only as sound as its folder, and whoever can
783
+ change it, or put back an older copy whole, can make it forget a
784
+ sequence.
785
+ - The package depends on `provared` 0.6.0 or later. Three independent
786
+ reviews: the first found one serious fault (a statement given out that
787
+ was never saved, so after a restart the witness could sign a fork) and
788
+ three medium; the second and third found nothing serious or medium.
789
+ 189 tests, and the three test fixes of 21 October brought across.
790
+ - **`provared-gateway` 0.1.0, a reverse proxy that writes the receipts**
791
+ (`gateway/`, not published). It sits in front of the web interfaces an
792
+ agent acts through, holds the agent's keys (in memory or through
793
+ signers) and each interface's credential, and for every call: checks
794
+ the agent's own secret, names the call from a rules file the company
795
+ writes (paths by the WHATWG URL Pattern standard, amounts by a fixed
796
+ value, a JSON Pointer of RFC 6901 or the length of a list), removes
797
+ the agent's own credentials from the headers and the address, asks the
798
+ stub writer's check before acting, sends the call, and writes a stub
799
+ that names `provared.http-request`, `provared.http-response` or
800
+ `provared.error`, and the upstream's own identifier where it gives one.
801
+ A call that is not allowed is not sent, is answered `403` with the
802
+ checker's words, and is written as a denial; a call that matches no
803
+ rule is refused by default. A call once sent always gets its stub,
804
+ whatever the upstream did. An upstream the company runs may
805
+ countersign. Books are sealed on demand with the recorder's keys
806
+ (`provared-gateway seal`); `provared-gateway check` says what each rule
807
+ maps to and what is wrong. Each book is a file appended line by line;
808
+ the feed to the application is not built. It is the gateway's record
809
+ only, and proves nothing about calls that do not go through it, as its
810
+ README says first. An independent review found one serious point: a
811
+ body naming `hosts` and `HOSTS` was recorded with the first's amount,
812
+ while an upstream that reads names whatever their case acts on the last.
813
+ Now a body whose names some readers take as one (differing only in
814
+ capitals, or in the long s or the Kelvin sign) gives no amount, and the
815
+ call is not sent. Also from the review: method and address override
816
+ headers and `_method` are removed, with every `X-Forwarded-*` and the
817
+ other client-address headers; a `;` in a path is refused unless the
818
+ upstream allows it, and then the rules match each segment without what
819
+ follows its `;`; overlong UTF-8 and full-width `/` or `.` in a path are
820
+ refused; each agent may have only so many calls and bytes waiting; a
821
+ journal beside each book lets a start find a call that was sent when the
822
+ gateway stopped, and stops that agent until the administrator
823
+ acknowledges it (`provared-gateway acknowledge`), when a stub saying the
824
+ outcome is not known is written; header names with `_` and a
825
+ `Connection` header that names other headers are refused; more
826
+ credential headers and query forms are removed; a character set other
827
+ than UTF-8 gives no amount; a credential that cannot be sent as a header
828
+ is refused at start; a stub for a call that never reached the upstream is
829
+ labelled `gateway.not-delivered`; two agents may not share a secret or a
830
+ book file; and the status answers while a call is out. A second review
831
+ found two medium points in the journal: one call could get two receipts
832
+ (a stop between its stub and the journal's "done" line, or an
833
+ acknowledgement tried again after a failure), and any program that could
834
+ write the journal could have the gateway sign stubs of its choosing. Now
835
+ every stub names its call (a label, `gateway.call`), and a start takes a
836
+ call whose stub the book holds as done; each journal line carries an
837
+ HMAC-SHA-256 tag under a journal key of the agent's own, and a line
838
+ whose tag does not check is never acted on; each line is checked against
839
+ the rules before a stub is written from it; the status shows each
840
+ waiting line whole; and the administrator can set aside a line
841
+ (`acknowledge --set-aside`). Also from that review: JSON names are
842
+ folded beyond ASCII (Greek final sigma, the dotless i and others), so an
843
+ unrelated `ID` and `id` in one object now give no amount; an encoded
844
+ character in a path whose NFKC form differs from itself is refused;
845
+ `provared-gateway repair` takes off a book's last line that a stop left
846
+ part-written; and two agents may not share a book, a journal, a journal
847
+ key or a key file. A third review found one medium point: a stub
848
+ written on acknowledgement went under the slip in force at the restart,
849
+ not the one the call was sent under. Now each journal line names its
850
+ slip, and the stub is written under that slip (by a stub writer for it,
851
+ opened on the same book), or, where the book does not hold it, the line
852
+ can only be set aside. Also from that review: two agents' files are also
853
+ compared by each existing file's device and file numbers (a link to
854
+ the file is followed), so, where the file itself exists, a hard link or
855
+ a link to the file counts as the same file; a journal that none of its
856
+ lines checks under the journal key in place stops the start; a
857
+ journal that holds lines and has lost its key stops the start (each of
858
+ these two stops the whole gateway, not that agent alone); a line
859
+ whose rule was taken out can still be acknowledged, with a warning; a
860
+ clock behind the journal's times is said plainly; calls set aside are
861
+ kept in a file a clean start does not empty; the library's own checks of
862
+ a request's fields decide whether a line is usable; the dotted capital
863
+ `İ` folds with `i`; `repair` takes its byte count only in digits and can
864
+ put back a lost line ending; and the README lists which honest path
865
+ forms are refused. 68 tests, all on 127.0.0.1, with a file of 145 calls
866
+ and the action, amount and service each must map to.
867
+ - **A central sealer** (format description, section 21.7, new; no record
868
+ changes). The recorder's three keys can now sit apart from the agent,
869
+ with a party that never sees the book.
870
+ - *In the library*: `leafFingerprint(line)` (Python:
871
+ `leaf_fingerprint`), the SHA-256 of the byte 0x00 and one line of a
872
+ book, refused where the line is empty, holds a line feed, is not
873
+ well-formed Unicode or is over 131,072 bytes; and `openSealer({by,
874
+ signers, stamp, state, clock, signWithin})`, which takes the leaf
875
+ fingerprints of each book's lines (`extend(book, at, leaves)`, only
876
+ at the place it holds, so a line is never taken twice), keeps the
877
+ same running tree the checker keeps, and signs a seal over its own
878
+ number of entries and top fingerprint (`seal(book, {previous})`),
879
+ through the same keys and signers `writeSeal` takes, each signer
880
+ given 30 seconds. The seal is the one `writeSeal` signs over the same
881
+ book, with the same time and unique number. It signs only where
882
+ `previous`, the seal the caller's book names, is the latest it
883
+ holds of that book (`not-up-to-date`): a sealer whose state is
884
+ behind the book signs nothing until it is brought up to date with
885
+ `update(book, entry, leaves)`, from a later seal it signed and the
886
+ leaf fingerprints since its own latest seal, which must give that
887
+ seal's top fingerprint; it is never moved back. `latest(book)` hands
888
+ back its latest seal, for a book that lost it. A book is known by
889
+ its first line's leaf fingerprint and its number; a later book names
890
+ the book it follows, its first seal names that book's last seal, and
891
+ that book is then closed (also where that first seal is brought in
892
+ with `update`, which waits, `not-up-to-date`, while the sealer holds
893
+ that book at an earlier seal than the one named). Every book with one first line is one chain: a book
894
+ whose first line the sealer holds starts only as the next book after
895
+ the latest with that first line (`not-next-book`). One sealer may
896
+ serve several recorders, through `forRecorder(name)`: a book, and
897
+ every book after it, belongs to the recorder that started it
898
+ (`other-recorder`). A recorder may be given rules,
899
+ `forRecorder(name, {agents, books})`: the fingerprints of the key
900
+ sets of the agents whose permissions it may start books with (an
901
+ agent belongs to one recorder), and the most books it may hold
902
+ (`too-large`). Where any recorder has a list of agents, every book is
903
+ started with its first line itself (`line`, beside `extend`, or
904
+ `update` for a book brought in), which must be a permission for one
905
+ of the caller's own agents (`agent-not-allowed`, `other-recorder`);
906
+ so no recorder can start a book whose first line is a permission for
907
+ another recorder's agent, nor bring another's book in first after a
908
+ lost state. The sealer then learns each first line; it does not check the
909
+ passkey's signature. `releaseUnsealed({recorder})` takes off books
910
+ that hold no seal, for which nothing was signed. A book started
911
+ after a book that another book now follows takes no first seal
912
+ (`book-closed`). Every seal is checked (all three signatures)
913
+ before it is kept; the state is handed back with a seal, a book
914
+ started and `update`, to be saved before the answer is given out. Of
915
+ a seal entry brought in with `update` only the seal, whose three
916
+ signatures it checks, and its time-stamps are kept, the time-stamps
917
+ read for their form only (at most four, each at most 12,288 bytes,
918
+ in base64url, none twice; no time-stamp's signature is checked); its
919
+ witness statements are not, and an entry longer than a line of a
920
+ book is refused (`too-large`), so that no recorder can make the
921
+ state too large to save. The entry is read once, and what is
922
+ measured, checked and kept is that one copy, so a member that gives
923
+ another value at each reading cannot slip past the checks. A
924
+ book is kept in the state at its latest seal, or with none at its
925
+ first line, so a sealer started again asks for the lines since; at
926
+ most 1,000 books, about 52 KB of state for each sealed book. With
927
+ its state kept it never signs two histories of one first line; a
928
+ book from another permission for the same agent that repeats sealed
929
+ lines, and a lost or older state, still need a witness. It sees only
930
+ fingerprints after the first line, so a recorder can start a book
931
+ from a permission for its own agent, copy into it another
932
+ recorder's agent's permissions and stubs with some left out, and
933
+ have it sealed: where one person's passkey signed the permissions
934
+ for both agents,
935
+ the book reads as intact and within its slips, and a witness
936
+ statement on its seal is not counted. A seal of the other
937
+ recorder's own book held elsewhere shows it (`book-cut-short` or
938
+ `seal-not-in-book`; where the two books' numbers differ, it cannot
939
+ be compared, and gives no pass); readers of books from a shared sealer should
940
+ hold each recorder's own seals elsewhere, or run a sealer for each
941
+ recorder. It needs SLH-DSA built in, so
942
+ Node.js; Python has no sealer. `writeSeal` and the sealer share one
943
+ signing path (`sealOver`, not exported). The tree builder gains
944
+ `addLeafHash` and `parts` (Python: `add_leaf_hash`).
945
+ - *A new shared test file*, `sealer-trees.json`: lines or leaf
946
+ fingerprints, and the number of entries and top fingerprint after
947
+ each; both languages give every answer.
948
+ - *`provared-sealer`* (`sealer/`, version 0.1.0, not published): the
949
+ sealer as a program on 127.0.0.1 (port 8797 by default), with its
950
+ keys in a file for its owner alone or reached through signer modules
951
+ named in its settings, a token for each recorder, the agents whose
952
+ permissions each recorder may start books with and the most books
953
+ it may hold (100 unless it says; all recorders' books add up to at
954
+ most 1,000), and one state for every recorder's books, each book
955
+ bound to its recorder; `release-unsealed` takes off, while it is
956
+ stopped, the books that hold no seal. A question proves its
957
+ recorder, and an answer its sealer, with HMAC-SHA-256 under the
958
+ token over a time, a number used once and the body; the token never
959
+ travels; each recorder's numbers used once are kept apart, for as
960
+ long as its question's time could be taken by a clock that never
961
+ goes back; the recorder's name, the time, the number and a first
962
+ proof over the body's declared length are checked before the body
963
+ is read, so no body is read for a program without the token, and at
964
+ most 16 MiB of bodies being read are held for one recorder. A
965
+ question waits for its turn as the bytes of its body only, at most
966
+ 16 questions and 32 MiB for one recorder, and its body is read as
967
+ JSON only in its turn, so one read body exists at a time. A body
968
+ must be a JSON object, and may hold only the members its path takes
969
+ (`bad-field`); `connectSealer`, the recorder's side, refuses before
970
+ sending any member of `more` the path does not take (`id` for a
971
+ seal among them), so `more` cannot replace `book`, `at`, `leaves`
972
+ or `entry`, and sends a `when` given as a `Date` as its
973
+ milliseconds. A question
974
+ turned away as `busy` keeps no number. Questions are answered one at a
975
+ time; the state is made sure to be on the disk before an answer to a
976
+ question that changed it; a seal is never given out before the state
977
+ that holds it is saved, and after a failed save every question taken
978
+ up is answered `sealer-stopped` until it is started again. A missing
979
+ state stops the start, and adding a recorder (`--fresh` begins it
980
+ again); `add-recorder` refuses a name that has books in the state
981
+ (a recorder written into `recorders.json` by hand is not checked so); one
982
+ sealer on a folder at a time. It names no time-stamp service and
983
+ asks no witness yet.
984
+
3
985
  ## 0.5.0 (17 October 2026)
4
986
 
5
987
  - **The README's count of review rounds: 50.** The 40 recorded by