provared 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/guide.md CHANGED
@@ -6,7 +6,7 @@ Everything about the format, the library, the checker, the stub writer beside an
6
6
 
7
7
  ## Status
8
8
 
9
- **Version 0.5.0, a draft.** Published as it is, by one author in their
9
+ **Version 0.7.0, a draft.** Published as it is, by one author in their
10
10
  own time; fixes as time allows. The format may still change, so do not
11
11
  rely on a draft record staying checkable.
12
12
 
@@ -21,7 +21,7 @@ An Internet-Draft is a working document, not a standard.
21
21
  |---|---|---|
22
22
  | **Slip** | A short permission a person signs with a passkey: which agent, which actions, what limits and conditions, with whom, from when until when, and why | Built |
23
23
  | **Stub** | A signed receipt for each action, pointing back to its slip, countersigned by the other side where possible, and approved by the person where the slip asks for it | Built |
24
- | **Seal** | The stubs chained together and gathered under one top fingerprint, signed by whoever keeps the book and time-stamped by an outside service | Built. A public place where seals are kept is not |
24
+ | **Seal** | The stubs chained together and gathered under one top fingerprint, signed by whoever keeps the book, time-stamped by an outside service, and signed for by witnesses | Built, with the part of a witness that decides and signs. A public place where seals are kept is not |
25
25
  | **Show** | One page shown to a checker with proof that it is in the book, and when the book was sealed, with the names it does not need kept covered | Built. Only a slip's names and purpose can be covered so far |
26
26
 
27
27
  ## What is here
@@ -33,14 +33,16 @@ An Internet-Draft is a working document, not a standard.
33
33
  | `src/` | The library: the checker, the writers for every kind of record, the stub writer beside an agent and the connector for an agent's tools. No outside code |
34
34
  | `bin/provared-check.mjs` | The checker for the command line |
35
35
  | `bin/provared-headers.mjs`, `net/` | A command that fetches the chain of Bitcoin block headers from nodes you name, so that a block time-stamp can be checked with no node of your own. The only code here that makes a network request. Experimental |
36
- | `page/` | The checking page and the demonstration page |
36
+ | `page/` | The checking page, the signing page and the demonstration page |
37
37
  | `demo/` | The demonstration: an invented office agent works under a permission and goes outside it four times |
38
38
  | `samples/` | A sample record, written once, that the tests keep checking |
39
- | `tools/` | Three small tools: one wrote the sample record, one writes the checking page as a single file, one writes the shared test files |
39
+ | `tools/` | Small tools: one wrote the sample record, one writes the checking page as a single file, one writes the shared test files, one writes the record page at prova.red/records/, and one writes the files of the signing page's own site, sign.prova.red |
40
40
  | `test/` | The tests |
41
41
  | `test-vectors/` | Shared test files: records and values, each with the answer this library gives. Another implementation of the format can be tested against them |
42
- | `python/` | The Python version, `pip install provared`, which gives the same answers. See its own [README](../python/README.md), and its examples with three agent frameworks in `python/examples/`, and small packages for two of them in `python/integrations/` |
43
- | `site/` | The pages at [prova.red](https://prova.red/): what a record proves, with the checker's answer for the sample record, and a worked example. Published with GitHub Pages by `.github/workflows/pages.yml`, which also serves the checking page at `prova.red/check/` and as one file. The pages load nothing from anywhere and keep nothing about a reader |
42
+ | `integrations/claude-code/` | The recorder beside Claude Code, `npm install -g provared-claude-code`, also a Claude Code plugin. See its own [README](https://github.com/provared/provared/blob/main/integrations/claude-code/README.md) |
43
+ | `mcp-executor/` | An executor for MCP tool servers, `npm install -g provared-mcp-executor`: it holds the tool servers' credentials, checks each tool call against the permission, and countersigns each receipt with keys of its own. See its own [README](https://github.com/provared/provared/blob/main/mcp-executor/README.md) |
44
+ | `python/` | The Python version, `pip install provared`, which gives the same answers. See its own [README](https://github.com/provared/provared/blob/main/python/README.md), and its examples with three agent frameworks in `python/examples/`, and small packages for two of them in `python/integrations/` |
45
+ | `site/` | The pages at [prova.red](https://prova.red/): what a record proves, with the checker's answer for the sample record, and a worked example. Published with GitHub Pages by `.github/workflows/pages.yml`, which also serves the checking page at `prova.red/check/` and as one file. The signing page is served from a repository of its own at `sign.prova.red`, so that the passkeys made there belong to a name that serves nothing else. The pages load nothing from anywhere and keep nothing about a reader |
44
46
 
45
47
  The npm package holds `src/`, `bin/`, `net/`, `spec/`, `docs/`,
46
48
  `samples/` and the checking page. The tests, the shared test files, the
@@ -105,7 +107,7 @@ const passkey = await developmentPasskey();
105
107
  fills in the issuer (its key, page address and marked name) and the
106
108
  agent's keys; by default the slip runs from a minute ago for one day,
107
109
  holds no limits and names no other party, and its purpose says that it
108
- is for development. [examples/first-record.mjs](../examples/first-record.mjs)
110
+ is for development. [examples/first-record.mjs](https://github.com/provared/provared/blob/main/examples/first-record.mjs)
109
111
  is a whole first record in one file. In Python the same is
110
112
  `provared.dev` (`development_passkey`, `development_slip`,
111
113
  `development_recorder`), with the writer as `.writer`.
@@ -151,9 +153,9 @@ be loaded from anywhere. The tool prints the file's SHA-256 fingerprint,
151
153
  so that a copy can be compared with it. (Tried on 8 October 2026 in the
152
154
  three browser builds this repository is tested with, and on 13 October
153
155
  2026 in Chrome 155, Edge 155 and Brave 155.) The file for
154
- version 0.5.0 is attached to the release on GitHub. Its SHA-256
156
+ version 0.7.0 is attached to the release on GitHub. Its SHA-256
155
157
  fingerprint is
156
- `cec1b8d5e6e7a11c00478c6e83d9f9b5963340ec656657ea0ac84f32e8e52bda`.
158
+ `c7b3d5fe1b9168628fc91308c7f952b970c05b3abeb908a132b8b2b78aaab1b5`.
157
159
 
158
160
  **Run the demonstration in a browser.**
159
161
 
@@ -168,8 +170,9 @@ is invented: nothing is bought, sent or deleted. The checking
168
170
  page on its own is at `http://localhost:8787/check`. It takes a record
169
171
  file, the fingerprints you already trust (keys, time-stamp services,
170
172
  blocks), and what you were handed with the record: the person's own
171
- copies of cancellations, and disclosures for covered fields. All of it is
172
- read in the browser and sent nowhere.
173
+ copies of cancellations, seals of the book held elsewhere, and
174
+ disclosures for covered fields. All of it is read in the browser and
175
+ sent nowhere.
173
176
 
174
177
  ## What a slip can say
175
178
 
@@ -236,6 +239,214 @@ The fingerprints for the sample are in
236
239
  `samples/office-supplies.expected.json`. The sample's time-stamp service
237
240
  is made up, as its passkey is.
238
241
 
242
+ ### Seals held elsewhere
243
+
244
+ Whoever keeps the book can cut off its end, with the last seal, and what
245
+ is left still checks. Anyone who keeps a copy of a seal elsewhere (the
246
+ person, an auditor, a second copy out of the recorder's reach) can hand
247
+ it to the checker beside the book, as the entry it is in the book:
248
+
249
+ ```
250
+ provared-check record.jsonl --seal seal.json --sealer <fingerprint> --issuer <thumbprint>
251
+ ```
252
+
253
+ In the library: `seals`, up to 64. A seal at entry n covers the n
254
+ entries before it, so the book must hold that very seal at entry n. A
255
+ book that ends before it is reported as cut short (`book-cut-short`); a
256
+ book that holds another line there, as changed (`seal-not-in-book`).
257
+ Both are problems with the book. A seal handed over is checked as any
258
+ seal is, and counts only where you named its recorder (`--sealer`, or
259
+ `sealKeys`); one that does not pass its own check is a problem with the
260
+ whole check. On a device without SLH-DSA (the Python version, and
261
+ browsers that have ML-DSA, such as Edge and Brave 155 when tested) it
262
+ counts on its Ed25519 and ML-DSA-87 signatures, and says so; on a device
263
+ without ML-DSA it is not used. The seal is compared with the book by its
264
+ content: the same content with other signatures is the same seal, and
265
+ the answer says that the signatures differ. Nothing in a seal names its
266
+ book, so hand over only seals of the book being checked: a seal of
267
+ another book by the same recorder (for example the book it kept before
268
+ it started a new one) is reported as `seal-not-in-book` or
269
+ `book-cut-short`. Without `--sealer`, the seals handed over are not
270
+ compared with the book, and the command-line checker says so on a line
271
+ of its own and gives no pass.
272
+ Its own time-stamps are shown but are not used for the entries of the
273
+ book. The answer lists each one in `held`, after any cancellations. A
274
+ Show does not use one. What it does not catch: a book cut back after the
275
+ latest seal anyone holds, or a book never shown to anyone who holds one.
276
+
277
+ ### Witnesses
278
+
279
+ A witness is a party, with a key set of its own, that keeps for each
280
+ book the latest seal it has signed for, and signs a short statement for
281
+ a new seal only where the new seal extends that one (format
282
+ description, section 29). The recorder sends it the seal and a
283
+ consistency proof (RFC 9162, section 2.1.4); the statement goes beside
284
+ the seal on its line, in `witnesses`, up to four, or is handed to the
285
+ checker later with a copy of the seal (`--seal` with a file of
286
+ `{"seal": ..., "witnesses": [...]}`). Name the witnesses you trust by
287
+ the fingerprints of their key sets:
288
+
289
+ ```
290
+ provared-check record.jsonl --witness <fingerprint> --sealer <fingerprint> --issuer <thumbprint>
291
+ ```
292
+
293
+ In the library: `witnesses`. The answer's `summary.witnessed` gives the
294
+ latest seal for which a statement from a witness you trust counts, the
295
+ entries it covers, those witnesses, and every seal witnessed; the
296
+ command-line checker prints it as "Witnessed by a witness you trust?".
297
+ A statement that does not check, or that speaks of another seal, is a
298
+ problem (`witness-invalid`). A statement counts only where you also
299
+ named the passkey you trust (`--issuer`) and the recorder you expect
300
+ (`--sealer`), the book starts with a permission signed by that passkey,
301
+ and every permission before the seal is from that passkey for the same
302
+ agent (a Show must hold every page the seal covers): a witness knows a book only by the passkey and the agent of
303
+ the permission it starts from and its recorder's keys, and the recorder
304
+ chooses the first line and its keys. Being witnessed excuses nothing
305
+ and is not needed for a pass. What it adds: a second history of the
306
+ entries before a witnessed seal, sealed by the same recorder and
307
+ starting from any permission that passkey gave that agent (a renewal
308
+ included), shown to someone else, would need that witness to sign for it
309
+ too. What it does not: a history sealed with other keys, even keys you
310
+ also named, or one that starts from a permission for another agent;
311
+ anything never shown to the witness; or the time. To see whether the end
312
+ of the book is missing, ask the witness for the latest seal it holds for
313
+ this recorder, passkey and agent, and hand that seal over beside the book
314
+ it is of. One recorder key set keeps one sequence for each person and
315
+ agent: books kept side by side for one agent need recorder keys of
316
+ their own.
317
+
318
+ A recorder may keep the record of one permission in several books: a
319
+ seal says which in `book` (2, 3 and so on; the first book has no
320
+ number), and the first seal of a later book names the last seal of the
321
+ book before. `writeSeal` takes `book`. A witness treats every book of one
322
+ recorder, person and agent as one sequence: it signs for a new book only
323
+ where its first seal starts from the latest seal it holds, and never
324
+ again for the book before. A checker of one book does not see the book
325
+ before; handed that book's last seal, it says whether this book follows
326
+ on from it (format description, section 21.6). A seal handed over that
327
+ gives another book number than the book is not compared with it, and
328
+ gives no pass: the command-line checker says "Seals held elsewhere
329
+ compared? NO" and does not end with code 0, even for an honest book.
330
+ To check book n − 1 against the seal book n starts from, check book
331
+ n − 1 with that seal handed over beside it.
332
+
333
+ The library holds a witness: `openWitness({ by, signers, state })`, with
334
+ `witness(entry, proof, firstLine)`, which signs only for a seal whose
335
+ three signatures it checked and that extends the seal it holds, and
336
+ hands back its state with each statement, to be saved before the
337
+ statement is given out; `update`, `ready`, `latest` and `state`.
338
+ `witnessProof(book, size, from)` makes the proof to send it (`from`:
339
+ the size of the seal the witness holds of the same book, or none for a
340
+ first seal and for the first seal of the next book), and `bookIdentity`
341
+ names a sequence as a witness knows it: by the recorder's keys with the
342
+ passkey and the agent of the permission on the book's first line. Whoever can change a witness's
343
+ state can make it forget a sequence: keep it as safely as its keys. A
344
+ witness needs SLH-DSA, so it needs Node.js; the Python version has none.
345
+
346
+ ### A central sealer
347
+
348
+ The recorder's three keys need not sit on the agent's computer. A
349
+ central sealer, run by a team other than the one that runs the agent,
350
+ holds them and seals books it never sees: the recorder sends it the
351
+ leaf fingerprint of each new line (`leafFingerprint(line)`: the SHA-256
352
+ of the byte 0x00 and the line), and asks for a seal; the sealer builds
353
+ the same tree as the checker, signs a seal over its own number of
354
+ entries and top fingerprint, and hands back the seal's entry, which the
355
+ recorder adds to its book with the stub writer's `add` (format
356
+ description, section 21.7). A sealer sent a wrong fingerprint signs a
357
+ seal the book refuses (`seal-mismatch`).
358
+
359
+ ```js
360
+ import { leafFingerprint, openSealer, toBase64url } from 'provared';
361
+
362
+ const sealer = await openSealer({ by: { keys, name: 'Our central sealer' }, signers, state });
363
+ const first = toBase64url(await leafFingerprint(lines[0])); // a book is known by its first line
364
+ await sealer.extend(first, 0, await Promise.all(lines.map(leafFingerprint)));
365
+ const made = await sealer.seal(first, { previous: null }); // the seal the book's latest seal names, or null
366
+ // save made.state first, then hand made.entry to the book
367
+ ```
368
+
369
+ `extend(book, at, leaves)` takes lines only at the place it holds, so a
370
+ line is never taken twice. `seal(book, { previous })` signs only where
371
+ `previous` is the latest seal it holds of that book: a sealer whose
372
+ state is behind the book signs nothing until it is brought up to date
373
+ with `update(book, entry, leaves)` (the book's latest seal, and the leaf
374
+ fingerprints since the latest seal it holds); a book that lost a seal
375
+ the sealer gave takes it back with `latest(book)`. A later book is named
376
+ `{ first, number }`, and its first `extend` names the book it follows
377
+ (`{ follows }`); its first seal names the last seal of that book, which
378
+ is then closed, and so is a book brought up to date from such a first
379
+ seal (bring books up to date in their order: a later book's first seal
380
+ is refused, `not-up-to-date`, while the sealer holds the book before at
381
+ an earlier seal than the one it names). Every book with one first line
382
+ is one chain: a book whose first line the sealer holds starts only as
383
+ the next book after the latest with that first line (`not-next-book`). A sealer that serves several
384
+ recorders gives each its own calls, `sealer.forRecorder(name)`: a book,
385
+ and every book after it, belongs to the recorder that started it, and
386
+ another recorder's calls about it are refused (`other-recorder`) or
387
+ answered as if the sealer did not hold it. Recorders that do not trust
388
+ each other are given rules, `sealer.forRecorder(name, { agents, books })`:
389
+ the fingerprints of the key sets of the agents whose permissions each may
390
+ start books with (an agent belongs to one recorder), and the most books
391
+ each may hold. Then every book is started with its first line itself
392
+ (`extend(book, 0, leaves, { line })`, or `update(book, entry, leaves, {
393
+ line })` for a book brought in), which must be a permission for one of
394
+ the recorder's own agents (`agent-not-allowed`, or `other-recorder` for
395
+ another recorder's agent); so no recorder can start a book whose first
396
+ line is a permission for another recorder's agent. `releaseUnsealed()` takes off the
397
+ books that hold no seal, for which nothing was signed. Save the state
398
+ handed back with a seal, with a book started (`extend`) and with
399
+ `update` before the answer is given out, and the one `releaseUnsealed`
400
+ hands back. Of a seal entry brought in with `update`, the sealer keeps
401
+ the seal, whose three signatures it checks, and its time-stamps, read
402
+ for their form only: it checks no time-stamp's signature, so one that
403
+ `latest` gives back is not vouched for by the sealer. A book is
404
+ kept in the state at its latest seal, or with none at its first line:
405
+ started again, the sealer asks for the lines since. It keeps at most
406
+ 1,000 books, about 52 KB of state for each sealed book. It checks every
407
+ seal it signs, so it needs Node.js; the Python version has
408
+ `leaf_fingerprint` only.
409
+
410
+ What a central sealer adds, and what still needs a witness. With its
411
+ state kept, it never signs two different histories of one first line.
412
+ With rules, it starts a recorder's books only from permissions for that
413
+ recorder's own agents, and so learns each first line. It sees only
414
+ fingerprints after the first line, so it cannot tell that a book which
415
+ starts from another first line (another permission for the same agent,
416
+ or, with no rules, any record the agent's side can sign) repeats lines
417
+ of a book it has sealed. A sealer that loses its state, or is put back
418
+ to an older copy, can be made to sign a second history, and two states
419
+ kept with one key set are two sealers. A witness is the check for these
420
+ (format description, sections 21.7 and 29).
421
+
422
+ One case a witness does not settle. A recorder, through its own calls
423
+ alone, can start a book from a permission for one of its own agents and
424
+ copy into it the permissions and stubs of another recorder's agent,
425
+ leaving out any it chooses (a stub over a limit, say); the sealer seals
426
+ it. Where one person's passkey signed the permissions for both agents,
427
+ a reader who names that passkey and the sealer's key set finds the book
428
+ intact and within its slips. A witness statement on its seal is not
429
+ counted (the seal covers a permission for an agent other than the first
430
+ line's), so the seal reads as not witnessed, but nothing reports why.
431
+ What shows it: a seal of the other recorder's own book, held elsewhere
432
+ and handed to the checker with this book (`seals`, `--seal`), gives
433
+ `book-cut-short` or `seal-not-in-book`; or, where the two books'
434
+ numbers differ, it cannot be compared, and the checker says so and
435
+ gives no pass. So readers of books from a
436
+ sealer shared by recorders that do not trust each other should hold
437
+ each recorder's own seals elsewhere, or the organisation should run a
438
+ sealer, with its own key set, for each recorder (format description,
439
+ section 21.7).
440
+
441
+ The folder `sealer/` of the repository holds `provared-sealer`, a small
442
+ program around it: it listens on 127.0.0.1, takes questions only from the
443
+ recorders it was given (each proves that it holds its token, which never
444
+ travels), names each recorder with its agents and its number of books,
445
+ keeps one state for all of them, keeps its keys in a file for its owner
446
+ alone or reaches them through signer modules named in its settings, and
447
+ makes sure the state is on the disk before it answers a question that
448
+ changed it. See its README.
449
+
239
450
  ### Knowing that a block is part of the chain
240
451
 
241
452
  **Experimental.** `provared-headers` is new in version 0.1.0 and may
@@ -533,7 +744,8 @@ A call that the slip does not allow throws `NotTaken`, with the answer
533
744
  of the check before acting, and the tool is not run. Where the slip asks
534
745
  for the person's own approval, a tool's `approve` function is asked for
535
746
  it once. By default the stub of a call names one document,
536
- `arguments`: the fingerprint of the call's arguments, written in one
747
+ `provared.arguments` (the reserved name of section 27 of the format
748
+ description): the fingerprint of the call's arguments, written in one
537
749
  form (RFC 8785). So the record fixes exactly what was asked for without
538
750
  holding it; `argumentsFingerprint` works the same fingerprint out again.
539
751
  The arguments must be JSON: a list with a gap in it, or with a named
@@ -551,6 +763,70 @@ not a promise. The numbers `0` and `-0` share a fingerprint, as RFC
551
763
  though the tool has run and its stub is in the book. `recordTools`
552
764
  names no agent software, and works with any function.
553
765
 
766
+ [examples/frameworks/](https://github.com/provared/provared/tree/main/examples/frameworks) in the repository shows
767
+ `recordTools` beside a JavaScript agent framework, the Vercel AI SDK
768
+ (`ai`, version 7). The SDK's own tool calls the recorded tool from its
769
+ `execute` function, and the SDK hands the words of `NotTaken` back to the
770
+ model as a failed tool call, so the agent is told why a call was not run.
771
+ It runs with a scripted stand-in model and sends nothing anywhere; it has
772
+ its own `package.json`, with exact versions, and is not part of the npm
773
+ package. The Python examples are in
774
+ [python/examples/](https://github.com/provared/provared/tree/main/python/examples) in the repository.
775
+
776
+ ### Denials: the calls the agent's side refused
777
+
778
+ A book shows what the agent did, not how often it was stopped. Opened
779
+ with `denials: true`, the stub writer also writes a **denial** whenever
780
+ `act` does not take an action: the agent's side's own signed statement
781
+ that it did not take it, and why (format description, section 28).
782
+
783
+ ```js
784
+ const recorder = await openRecorder({ book, slip, privateKeys, issuerKeys, denials: true });
785
+
786
+ const outcome = await recorder.act({ action: 'provared.data.delete' }, async () => removeTheFiles());
787
+ outcome.done; // false: the slip does not allow it
788
+ outcome.denial.written; // true: the book now holds a denial, by "check", with the reason "action-not-allowed"
789
+
790
+ // A refusal that came from elsewhere: the person said no, or a rule of the software around the agent did.
791
+ await recorder.deny({ action: 'provared.order.place', amount: { unit: 'GBP', value: 900 } }, { source: 'person' });
792
+ ```
793
+
794
+ A denial names what was asked for (its action, amount and service) and
795
+ who said no: `check` (the check before acting, with the codes it gave),
796
+ `person` or `rule`. Its documents are fingerprints only; arguments are
797
+ never written into it. `more.denial: false` asks `act` to write none
798
+ for one call. A writer writes at most `denialsPerHour` (600 by default)
799
+ in any hour by its clock. Each denial it writes, or is handed through
800
+ `add`, counts at the clock's time when it joins the book, whatever date
801
+ it states; those of its own chain that the book already held when it
802
+ was opened count at the dates they state. Past that it writes none, and
803
+ each answer's `notWritten` says how many it left out since it was
804
+ opened. A writer opened again on a book counts the book's denials at
805
+ their stated dates, so a caller who dates denials back and then opens
806
+ the writer again can get a second burst (5 and then 5 more within 3,302
807
+ seconds, at a limit of 5). At 600 an hour, an agent that is refused again and again still
808
+ fills a book's 100,000 entries in about a week: the limit slows that,
809
+ it does not stop it. A stub is never kept out by that limit, and
810
+ nothing that goes wrong in writing a denial makes `act` or `deny` fail:
811
+ the answer says what was done. `deny` does throw where the call itself
812
+ is malformed: a request that is not an object, or that cannot be copied
813
+ as plain data, a `when` that is neither
814
+ a number nor a date, `codes` that cannot be copied, or a call from
815
+ inside an action, which is refused as every call from there is. It
816
+ writes nothing for a writer opened without `denials: true`, and takes a
817
+ `when` within 300 seconds of the clock. It records what its caller
818
+ states: it does not check that the action was not taken, so a caller
819
+ can write a denial for a request that also has a stub.
820
+
821
+ `recordTools` passes it on: with a writer that writes denials, a call
822
+ that is not run leaves one, and `NotTaken` holds what was done
823
+ (`denial`). Where a tool can ask the person for approval, no denial is
824
+ written until it is known that the call will not run: so one call
825
+ leaves a stub or a denial, never both. A checker shows each denial as
826
+ the agent's side's own word, counts it (`summary.counts.denials`), and
827
+ never counts it against the agent. An agent can simply not write one,
828
+ so a book without denials shows nothing about refusals.
829
+
554
830
  ### Binding a stub to the platform's own record
555
831
 
556
832
  Where the other side will not countersign, a stub can still be matched
@@ -568,6 +844,9 @@ section 27); any other such name is refused.
568
844
  | `provared.request-id` | An identifier the agent's side chose and sent with the call | `identifierFingerprint` |
569
845
  | `provared.event-id` | An identifier the platform gave for the call | `identifierFingerprint` |
570
846
  | `provared.error` | The error text the agent's software was given | `identifierFingerprint` |
847
+ | `provared.tool-name` | The name of the tool, exactly as the agent's software called it (not the slip's action name) | `identifierFingerprint` |
848
+ | `provared.tool-call-id` | The identifier the model gave the tool call, exactly as received | `identifierFingerprint` |
849
+ | `provared.tool-result` | The tool's result, in the one form of RFC 8785; never the result itself | `argumentsFingerprint` (the same one form) |
571
850
 
572
851
  `DOCUMENT_NAMES` lists them, each with the exact text it stands for. An
573
852
  HTTP request or answer is written as the signature base of RFC 9421
@@ -619,14 +898,44 @@ the decoded body of a compressed message will not match the body's
619
898
  fingerprint. An identifier that is short or easy to guess can be guessed
620
899
  from its fingerprint.
621
900
 
622
- `recordTools` still names the arguments `arguments` by default; a tool
623
- can name them `provared.arguments` through its own `details` function.
901
+ `recordTools` names the arguments `provared.arguments` by default (before
902
+ version 0.6.0, `arguments`; every checker reads both); a tool can name
903
+ them otherwise through its own `details` function. It does not name the
904
+ tool's name or its result: a tool's `details` function can add
905
+ `provared.tool-name`, and `provared.tool-result` is added after the
906
+ action, through `detailsAfter` of `act`. The MCP executor
907
+ (`mcp-executor/`) names all three where it has them. Each of the three
908
+ may be named once in a stub; a checker of version 0.5.0 or 0.6.0 refuses
909
+ a stub that names any of them, and one of 0.4.0 or earlier reads them as
910
+ labels.
911
+
912
+ To show each receipt on the trace span of its call in an observability
913
+ tool, use the OpenTelemetry bridge in `integrations/opentelemetry/` (not
914
+ yet published); a span is a pointer to the receipt, not evidence. It puts
915
+ `provared.trace-id` into a receipt only for a tool that you say sends the
916
+ trace out with its call, in a `traceparent` header, as this section
917
+ defines the name; it cannot see whether the tool really does. Where a
918
+ tool gives the model's identifier for the call (`callId`), the receipt
919
+ names it as `provared.tool-call-id`.
624
920
 
625
921
  ## How signing works
626
922
 
627
923
  - **The person** signs a slip with a passkey. A passkey signs a short
628
924
  value handed to it; that value is the SHA-256 fingerprint of the slip's
629
925
  exact signed bytes, so the signature covers the whole slip.
926
+ - **The signing page is served at an address of its own**,
927
+ [sign.prova.red](https://sign.prova.red/), which serves nothing else
928
+ (its files are copied from `page/` by `tools/make-sign-site.mjs` into
929
+ the public repository `provared/sign`). A passkey can be asked to sign
930
+ by any page under the website name it was made for, and what it signs
931
+ is a fingerprint the person cannot read on the device's prompt, so a
932
+ passkey made for a name that serves other pages could be made to sign
933
+ a permission by one of them. A passkey made at sign.prova.red can be
934
+ asked only there. Whoever checks a record can name the address the
935
+ person signs at (`issuerOrigins`, or `--origin`, or the box on the
936
+ checking page): a permission signed under the same passkey anywhere
937
+ else is then refused (`issuer-origin-not-expected`). The list of
938
+ threats, section 3, says what this closes and what it does not.
630
939
  - **The agent and the service** each sign with two keys at once: Ed25519,
631
940
  which is today's kind, and ML-DSA-87, which is quantum-safe. A record
632
941
  counts only if both signatures check. A forger must break both.
@@ -798,15 +1107,15 @@ answer with a named reason. It keeps two things apart:
798
1107
  |---|---|
799
1108
  | `size`, `root` | How many entries the book holds, and its top fingerprint |
800
1109
  | `problems` | Problems with the book as a whole |
801
- | `entries` | One for each line: `index`, `kind` (`slip`, `stub`, `refusal`, `terms`, `seal`, `cancellation`, `acknowledgement`, `vouching`, `withdrawal`, `pass` or `unreadable`), `fingerprint`, `verified` (`all`, `some` or `none` of its signatures confirmed here), `problems`, `breaches`, `notes`, the signature states, and `content`. Where a time-stamp you trust on a later seal covers the entry: `existedBy`, the time by which it is shown to have existed. On a seal or a cancellation that passed its check: `stampedAt`, the time its own time-stamps give it |
802
- | `held` | One for each cancellation handed over beside the book: whether it passed its check and was used, whether the book holds it (`inBook`), the time its time-stamp gives, and the acknowledgements handed over with it (`acknowledgements`: who acknowledged, and when by their own word) |
1110
+ | `entries` | One for each line: `index`, `kind` (`slip`, `stub`, `refusal`, `terms`, `seal`, `cancellation`, `acknowledgement`, `vouching`, `withdrawal`, `pass`, `denial` or `unreadable`), `fingerprint`, `verified` (`all`, `some` or `none` of its signatures confirmed here), `problems`, `breaches`, `notes`, the signature states, and `content`. Where a time-stamp you trust on a later seal covers the entry: `existedBy`, the time by which it is shown to have existed. On a seal or a cancellation that passed its check: `stampedAt`, the time its own time-stamps give it |
1111
+ | `held` | One for each cancellation handed over beside the book (`kind` `cancellation`): whether it passed its check and was used, whether the book holds it (`inBook`), the time its time-stamp gives, and the acknowledgements handed over with it (`acknowledgements`: who acknowledged, and when by their own word). Then one for each seal held elsewhere (`kind` `seal`): whether it passed its check and was used, the entry it stands at (`place`), whether the book holds it there (`inBook`), its recorder's key set (`sealer`), its signatures and time-stamps |
803
1112
  | `summary.problemFound` | A problem was found somewhere in the record |
804
1113
  | `summary.fullyChecked` | Every signature was checked on this device |
805
1114
  | `summary.intact` | No problem was found **and** every signature was checked. The only answer that is a pass |
806
1115
  | `summary.methodsMissing` | The signing methods this device lacks, if any, for example `ML-DSA-87` |
807
1116
  | `summary.withinSlips` | Every stub was compared with its slip, and none is outside it |
808
1117
  | `summary.firstBreach` | The first entry that shows the agent outside its slip, if any |
809
- | `summary.counts` | Slips, stubs, countersigned stubs (the countersignature confirmed on this device), one-sided stubs (no countersignature; a stub that was not checked, because its slip failed, is neither), stubs the person approved, refusals, sets of terms, and the other kinds of entry |
1118
+ | `summary.counts` | Slips, stubs, countersigned stubs (the countersignature confirmed on this device), one-sided stubs (no countersignature; a stub that was not checked, because its slip failed, is neither), stubs the person approved, refusals, sets of terms, denials (the agent's side's own word, never counted against it), and the other kinds of entry |
810
1119
  | `summary.sealed` | How far the last time-stamp you trust reaches: how many entries, the time they existed by, and whether a service or a block (`by`) gave it. Otherwise nothing |
811
1120
  | `summary.limits` | What no check can show, in plain sentences |
812
1121
 
@@ -822,13 +1131,19 @@ wrong. Never show it as fact: the checking page and the command-line
822
1131
  checker do not.
823
1132
 
824
1133
  Options: `issuerKeys` (the thumbprints of issuer keys you trust),
1134
+ `issuerOrigins` (the addresses of the pages at which the person signs
1135
+ permissions, such as `https://sign.example.org`, each in the one form
1136
+ the format allows; a permission signed anywhere else is refused with
1137
+ `issuer-origin-not-expected`; see "How signing works"),
825
1138
  `sealKeys` (the fingerprints of the key sets of the recorders you
826
1139
  expect), `stampServices` (the fingerprints of the certificates of the
827
1140
  time-stamp services you trust), `blocks` (the fingerprints of the
828
1141
  blocks of a public blockchain you trust), `vouchers` (the fingerprints of the key
829
- sets of the organisations whose vouching you trust), `disclosures` (the
1142
+ sets of the organisations whose vouching you trust), `witnesses` (the
1143
+ fingerprints of the key sets of the witnesses you trust), `disclosures` (the
830
1144
  disclosures of covered fields that you were handed), `cancellations`
831
- (the person's own copies of cancellations), `expectedRoot` and
1145
+ (the person's own copies of cancellations), `seals` (seals of the book
1146
+ held elsewhere), `expectedRoot` and
832
1147
  `expectedSize` (a top fingerprint and a number of entries you already
833
1148
  hold a copy of). Give
834
1149
  them whenever you have them.
@@ -843,8 +1158,9 @@ where the record has a seal, the recorder you expect), 1 (no problem
843
1158
  found, but the agent is outside its slip, not every signature could be
844
1159
  checked, or you did not name whom you trust) or 2 (a problem was found,
845
1160
  or what you gave on the command line could not be used). A value that
846
- is not a fingerprint is refused, never set aside. The checking page
847
- gives its green answer on the same terms.
1161
+ is not a fingerprint, or an address given with `--origin` that is not a
1162
+ page address in its plain form, is refused, never set aside. The
1163
+ checking page gives its green answer on the same terms.
848
1164
 
849
1165
  Writing records: `prepareSlip` and `assembleSlip`, `prepareApproval` and
850
1166
  `assembleApproval` (the person), `writeStub` (the agent), `countersign`,
@@ -852,7 +1168,8 @@ Writing records: `prepareSlip` and `assembleSlip`, `prepareApproval` and
852
1168
  `writeRefusal` and `writeTerms` (the service), `writeVouching` and
853
1169
  `writeWithdrawal` (an organisation), `writePass` (an agent, to a helper
854
1170
  agent), `writeSeal` (whoever keeps
855
- the book; it needs Node.js), `writeBook` and `makeShow`, all from
1171
+ the book; it needs Node.js), `writeWitness` and `openWitness` (a
1172
+ witness; `openWitness` needs Node.js), `consistencyProof`, `writeBook` and `makeShow`, all from
856
1173
  `provared`. The library asks no time-stamp service itself: you send
857
1174
  the seal's fingerprint to the service you choose and put its answer
858
1175
  beside the seal. In a browser, `createPasskey`, `signSlipWithPasskey`,
@@ -893,7 +1210,10 @@ And about time:
893
1210
  the agent outside its slip;
894
1211
  - the last seal, with everything after it, can itself be removed. Only a
895
1212
  copy of it, or of its top fingerprint and number of entries, held by
896
- someone else shows that;
1213
+ someone else shows that: hand such a copy of a seal to the checker
1214
+ (`--seal`, or `seals`), such as the latest seal a witness holds;
1215
+ - a witness statement is the witness's own word for when it saw the
1216
+ seal, and is not used as a time-stamp;
897
1217
  - time-stamp services sign with methods of today's kind, which are not
898
1218
  quantum-safe.
899
1219
 
@@ -906,7 +1226,9 @@ connects to anything: to the Bitcoin nodes you name, for block headers.
906
1226
 
907
1227
  ## Not built yet
908
1228
 
909
- A public place where seals are kept; covering fields other than a slip's
1229
+ A public place where seals are kept, which gathers the seals of many
1230
+ books; a witness that runs as a service (the library holds the part that
1231
+ decides and signs); covering fields other than a slip's
910
1232
  names and purpose; connectors for particular agent software (the
911
1233
  general one, `recordTools`, is built); a compact binary form; the
912
1234
  recorder service. The checking page cannot yet take a chain of block