provared 0.1.0 → 0.3.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/README.md CHANGED
@@ -2,683 +2,154 @@
2
2
 
3
3
  Proof of what an AI agent was allowed to do, and what it then did.
4
4
 
5
+ A person signs the permission with a passkey. The agent signs a receipt
6
+ for each action. A free checker says whether the agent stayed within the
7
+ permission, and where it did not.
8
+
5
9
  *Evidence, not a verdict.* A record shows what was signed and by which
6
10
  keys. People decide what it means.
7
11
 
8
- ## Status
9
-
10
- **Version 0.1.0, a draft.** Published as it is, by one author in their
11
- own time; fixes as time allows. The format may still change, so do not
12
- rely on a draft record staying checkable.
13
-
14
- ## The four parts
15
-
16
- | Part | What it is | In this draft |
17
- |---|---|---|
18
- | **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 |
19
- | **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 |
20
- | **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 |
21
- | **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 |
22
-
23
- ## What is here
24
-
25
- | Folder | What it holds |
26
- |---|---|
27
- | `spec/provared-format.md` | The format, described exactly |
28
- | `docs/threat-model.md` | Who might attack a record, what each part protects, and what it does not |
29
- | `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 |
30
- | `bin/provared-check.mjs` | The checker for the command line |
31
- | `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 |
32
- | `page/` | The checking page and the demonstration page |
33
- | `demo/` | The demonstration: an invented office agent works under a permission and goes outside it four times |
34
- | `samples/` | A sample record, written once, that the tests keep checking |
35
- | `tools/` | Two small tools: one wrote the sample record, one writes the checking page as a single file |
36
- | `test/` | The tests |
37
-
38
- The npm package holds `src/`, `bin/`, `net/`, `spec/`, `docs/`,
39
- `samples/` and the checking page. The tests, the demonstration and the tools are in the
40
- repository only.
41
-
42
- ## Install
43
-
44
- ```
45
- npm install provared
46
- ```
47
-
48
- You need Node.js 24.7 or later. There are no dependencies. Node.js marks
49
- its ML-DSA and SLH-DSA as experimental and may print a warning about
50
- them; the command-line checker does not repeat it.
51
-
52
- ```js
53
- import { checkBook, openRecorder } from 'provared'; // the whole library
54
- import { checkBook } from 'provared/check'; // the checker alone, as a page uses it
55
- import { createPasskey } from 'provared/passkey-browser'; // a passkey, in a browser
56
- ```
57
-
58
- The checker for the command line is `provared-check`:
59
-
60
- ```
61
- npx provared-check record.jsonl --issuer <the thumbprint of the passkey you trust>
62
- ```
63
-
64
- The examples below import from the package by name. In a copy of the
65
- repository, import from `./src/index.js`, `./src/check.js` and
66
- `./src/passkey-browser.js` instead.
67
-
68
- ## Try it
69
-
70
- These need a copy of the repository. You need Node.js 24.7 or later.
71
- Nothing is installed: there are no dependencies.
72
-
73
- **Run the tests.**
74
-
75
- ```
76
- npm test
77
- ```
78
-
79
- **Check the sample record from the command line.**
80
-
81
- ```
82
- npm run check -- samples/office-supplies.jsonl
83
- ```
84
-
85
- It prints each entry, then its answers: is the record intact, was
86
- everything checked on this device, did the agent stay within its slip, is
87
- it time-stamped in a way you trust, and was it checked against keys you
88
- named. Here you named none, so it is not a pass, whatever else it shows;
89
- the fingerprints that come with the sample are given below.
90
- In the sample the agent goes outside its slip four times: an order the
91
- supplier never confirmed, an order above 60 pounds that the person was
92
- not asked to approve, a total of 230 against a limit of 200, and a
93
- deletion that the slip forbids. The checker says where.
94
-
95
- **Make the checking page as one file.**
96
-
97
- ```
98
- node tools/make-single-page.mjs
99
- ```
100
-
101
- It writes `provared-check.html`: the checking page, its style, the
102
- checker and the sample record in one file, copied in unchanged. Open it
103
- in a browser straight from the disk; it needs no server. The file allows
104
- itself no request of any kind: its content security policy lets nothing
105
- be loaded from anywhere. The tool prints the file's SHA-256 fingerprint,
106
- so that a copy can be compared with it. (Tried on 4 October 2026 in the
107
- three browser builds this repository is tested with.) The file for
108
- version 0.1.0 is attached to the release on GitHub. Its SHA-256
109
- fingerprint is `ca4a15f820c9c715907952c2e7577717ffca92d87bcf633cc9a92f6dd5242746`.
110
-
111
- **Run the demonstration in a browser.**
112
-
113
- ```
114
- npm run demo
115
- ```
116
-
117
- Then open `http://localhost:8787`. You sign a permission with a passkey,
118
- the agent works and stops once to ask your approval of one order, and the
119
- page shows where the agent went outside the permission. Everything in it
120
- is invented: nothing is bought, sent or deleted. The checking
121
- page on its own is at `http://localhost:8787/check`. It takes a record
122
- file, the fingerprints you already trust (keys, time-stamp services,
123
- blocks), and what you were handed with the record: the person's own
124
- copies of cancellations, and disclosures for covered fields. All of it is
125
- read in the browser and sent nowhere.
126
-
127
- ## What a slip can say
128
-
129
- - **What the agent may do**: a list of action names. Use names of your
130
- own, or names from the shared list in the format description, section
131
- 16, such as `provared.order.place`, so that another organisation's
132
- checker knows what is meant.
133
- - **Limits**: a total, a limit on each single action, a number of
134
- actions, and a total or a number within any period.
135
- - **Conditions**: that the other side must countersign an action, and
136
- that the person must approve an action with their passkey, always or
137
- above an amount.
138
- - **Stated rules**: kinds of action the agent must never do, such as
139
- `destroys`, and rules of conduct, such as "never claim to be a human
140
- being". A checker compares the first with the stubs. The second is the
141
- person's signed instruction: no check can show that the agent kept it.
142
- - **With whom**: the services the agent may deal with, and their keys.
143
-
144
- The other side has a voice of its own. A service can sign a **refusal**
145
- ("this agent asked for something outside its slip, and we refused") and
146
- its **terms for agents** (which actions it accepts). Both are shown as
147
- what the service said.
148
-
149
- ## The seal and the outside time-stamp
150
-
151
- Whoever keeps the book seals it from time to time: they sign its top
152
- fingerprint and its number of entries, with three signatures (Ed25519,
153
- ML-DSA-87 and SLH-DSA-SHA2-256s). An outside time-stamp service then
154
- states when that seal existed, in the standard form of RFC 3161.
155
-
156
- - After a seal by a recorder you named (`--sealer`, or `sealKeys`),
157
- nothing before it can be changed, added or removed without the seal no
158
- longer fitting. A seal by keys you did not name shows only that someone
159
- sealed the book as it now stands.
160
- - With a time-stamp from a service you trust, the entries before the seal
161
- existed by the time the service states. An entry dated later than that
162
- is reported.
163
- - **You say whom you trust.** This format names no time-stamp service.
164
- You give the checker the fingerprint of the certificate of each service
165
- you trust; a time-stamp from any other service is shown, and not
166
- counted. In the same way you can give the fingerprint of the key set of
167
- the recorder you expect.
168
-
169
- - **A second clock that rests on no signature.** Beside a time-stamp
170
- from a service, a seal can carry a **block time-stamp**: proof that
171
- its fingerprint was written into a block of a public blockchain (the
172
- open proof format OpenTimestamps, and the Bitcoin blockchain). The
173
- chain is used purely as a clock: no coin, no wallet, no account and no
174
- payment is involved. It stays good even if every signing method of
175
- today is broken. You name the blocks you trust by their fingerprints
176
- (`--block`, or `blocks`). A block's time is right only to within about
177
- two hours, and the checker says so. The tests use made-up blocks. Two
178
- complete proofs that others had published, one made by hand and one
179
- made by public calendar services, were checked once with this reader
180
- against the real blocks they name (5 October 2026): each led to its
181
- blocks, and each was refused with another block or with its file
182
- changed by one bit.
183
-
184
12
  ```
185
- npm run check -- samples/office-supplies.jsonl --stamp-service <fingerprint> --sealer <fingerprint>
13
+ npm install provared # Node.js 24.7 or later, no dependencies
14
+ pip install provared # Python 3.11 or later, one dependency
15
+ npx provared-check --sample # the checker's answer for the sample record
186
16
  ```
187
17
 
188
- The fingerprints for the sample are in
189
- `samples/office-supplies.expected.json`. The sample's time-stamp service
190
- is made up, as its passkey is.
18
+ The sample is an invented office agent that goes over its limit at its
19
+ fifth order. The checker's answer ends:
191
20
 
192
- ### Knowing that a block is part of the chain
193
-
194
- **Experimental.** `provared-headers` is new in version 0.1.0 and may
195
- change. It is the only part of Provared that connects to the internet,
196
- and it does so only when you run it.
197
-
198
- A block time-stamp counts only for a block you trust to be part of the
199
- Bitcoin blockchain. You need not run a Bitcoin node of your own, or trust
200
- anyone who lists blocks, to know that:
201
-
202
- ```
203
- provared-headers --node <host:port> --node <host:port>
204
- provared-check record.jsonl --headers block-headers.bin --issuer <thumbprint> --sealer <fingerprint>
205
21
  ```
206
-
207
- `provared-headers` fetches the chain of block headers (about 78 MB, a
208
- few minutes the first time) from the Bitcoin nodes you name, and from no
209
- one else, and keeps it in a file. It checks every header on your device,
210
- by the rules every Bitcoin node applies, from the chain's first block:
211
- each names the block before it, carries the work its target asks for,
212
- follows the rule for the difficulty, and states a time that the rules
213
- allow. The chain must also hold, at their places, ten blocks known to be
214
- part of it, which are written into the library, and after the last of
215
- them no target may be more than four times easier than that block's. A
216
- second node, on another network, must hand back the latest six blocks
217
- itself. A node can refuse to answer, but cannot have a false header
218
- kept. A chain file someone else hands you is only as good as they are:
219
- fetch your own. Run it again later and it
220
- fetches only the newer headers. Name your own node if you have one
221
- (`127.0.0.1:8333`); a file of nodes, one `host:port` to a line, can be
222
- given with `--nodes-file`. The nodes you name see your device's address,
223
- and a node named by its name is looked up by your device's own name
224
- service.
225
-
226
- `provared-check --headers` checks that file again, in full, on your
227
- device, and makes no request. A block time-stamp then counts if its block
228
- is in the chain with at least six blocks after it. The checker says, for
229
- each block time-stamp, what the chain shows. In the library:
230
- `checkHeaderChain` and `blocksInChain`.
231
-
232
- ## Cancelling a slip
233
-
234
- The person who signed a slip can end it early with the same passkey, and
235
- have the cancellation time-stamped at once. Every stub after it is then
236
- reported as outside the slip. With a time-stamp on the cancellation, a
237
- stub counts as made before it only if an outside time-stamp shows that
238
- it existed by then: an agent cannot escape a cancellation by writing an
239
- earlier time into a stub. If the person cancelled more than once, every
240
- cancellation counts, in whichever order they stand in the book.
241
-
242
- The person should keep their own copy of a cancellation, with its
243
- time-stamp. Whoever keeps the book could leave the cancellation out, or
244
- put a later time-stamp in its place. Handed to the checker beside the
245
- book (`--cancellation <file>`, or `cancellations`), the person's copy
246
- counts all the same: the checker says whether the book holds it, and
247
- reports every stub that no outside time-stamp shows to have existed
248
- before it.
249
-
250
- A cancellation shows when the person signed it, not that the agent's
251
- side was told. So the agent's side can **acknowledge** it: a short
252
- record, signed with the agent's own keys, that says "this cancellation
253
- was handed to us at this time". The stub writer signs one whenever the
254
- person's cancellation of its slip is handed to its `add`, once for each
255
- cancellation, writes it into the book after the cancellation, and hands
256
- it back for the person to keep. Handed to the checker with the person's
257
- copy (as `acknowledgements` in the same file), it shows which stubs the
258
- agent made after it was told, which passes it handed on after it was
259
- told, and whether a cancellation that the book leaves out had been
260
- handed over. Both dates in that comparison are the agent's own word. An
261
- acknowledgement that does not check is reported, and the cancellation
262
- itself is still used.
263
-
264
- ## Who stands behind a name
265
-
266
- A name in a record is a label. An organisation can stand behind one with
267
- a **vouching record**: "this key belongs to this name". It can vouch for
268
- a person's passkey, an agent's keys, a service's keys and a recorder's
269
- keys, and can later **withdraw** the statement. You tell the checker
270
- which organisations you trust (`--voucher`, or `vouchers`), and it then
271
- shows a name as "vouched for by" that organisation instead of "a label,
272
- not checked". The vouching record must be in force for the whole of the
273
- slip's time. Provared does not decide who anyone is: it records who
274
- vouched.
275
-
276
- A slip can also state the fingerprints of the agent's program and
277
- settings, as the person approved them. That is a statement, not proof of
278
- what ran.
279
-
280
- ## Covered fields
281
-
282
- A slip can be written with its names and its purpose covered: the person's
283
- name, the agent's name, the services' names and the purpose. What is
284
- signed then holds a fingerprint in place of each. To let a reader see a
285
- field, hand over its **disclosure**; to keep it hidden, do not. The
286
- signature, the chain and the tree check either way, and a disclosure
287
- cannot be made for a value that was not signed. A disclosure that does
288
- not fit is refused and said to be, and the slip is checked as it would
289
- be without it: what a slip is does not depend on what is handed over
290
- with it. Nothing that a checker
291
- compares can be covered: not an action, a limit, a condition, a service
292
- or a key. So every reader checks the same slip.
293
-
294
- ```js
295
- const prepared = await prepareSlip(fields, { cover: ['issuer.name', 'purpose'] });
296
- prepared.disclosures; // keep these beside the slip
297
-
298
- await checkBook(book, { disclosures: { [fingerprintOfTheSlip]: theDisclosuresYouWereHanded } });
299
- await makeShow(book, [0, 3], { disclosures: { 0: theOnesThisReaderMaySee } });
22
+ Summary
23
+ Is the record intact? yes
24
+ Was every signature checked? yes
25
+ Did the agent stay within its slip? NO: first at entry 5
26
+ Time-stamped in a way you trust? yes: the first 9 entries existed by 2026-10-05T10:16:00Z
27
+ Checked against keys you named? yes: the passkey you trust, and the recorder you expect
300
28
  ```
301
29
 
302
- The method is the standard "Selective Disclosure for JSON Web Tokens"
303
- (RFC 9901), which digital identity wallets also use. The standard's own
304
- worked example is one of the tests. A slip uses less than the standard
305
- allows: no covered item of a list, no decoy fingerprints, and
306
- disclosures written in one canonical form.
307
-
308
- ## Passing a slip on
309
-
310
- A slip can say that its agent may hand part of its permission to a helper
311
- agent, and how many times in a row (`passes`: from 1 to 10). The agent then
312
- signs a **pass** that names the helper's keys, what the helper may do,
313
- its limits and its times. A helper's stubs name the pass, are signed with
314
- the helper's keys, and are compared with the pass, with every pass above
315
- it, and with the slip the person signed. The totals of the slip, and of
316
- each pass, take in everything done under them, so passing on cannot
317
- widen a permission. A pass the slip does
318
- not allow is reported, with every stub under it.
319
-
320
- ## The check before acting
321
-
322
- `checkBefore` answers, before an action: would this be outside the slip?
323
- It uses the same comparison as the checker, so it refuses everything the
324
- checker would report afterwards. In one case it refuses more: an action
325
- that would add to a period which a stub dated after it already
326
- overfills; the checker reports that against the later stub. The software
327
- around an agent can call it and stop the action.
30
+ ## A first record of your own
328
31
 
329
- ```js
330
- import { checkBefore } from 'provared';
331
-
332
- const answer = await checkBefore(
333
- textOfTheBookSoFar,
334
- {
335
- slip: fingerprintOfTheSlip,
336
- action: 'provared.order.place',
337
- amount: { unit: 'GBP', value: 80 },
338
- with: 'supplier',
339
- approval: theSignedApprovalIfTheSlipAsksForOne,
340
- },
341
- { issuerKeys: ['the thumbprint of the passkey you trust'] },
342
- );
32
+ Three commands. The permission is signed with a **development stand-in
33
+ for a passkey**: a key made in memory, which no person confirmed. Every
34
+ permission it signs says so in its issuer's name, and the checker prints
35
+ that name. A real permission is signed by a person, in a browser, with a
36
+ real passkey (`provared/passkey-browser`, and the checking page).
343
37
 
344
- answer.allowed; // true only if the record so far is sound, fully checked, and the action is inside the slip
345
- answer.breaches; // why not, in the checker's own codes
346
- answer.problems; // why the question could not be answered with a yes
347
- answer.needs; // what the slip asks to go with it: 'approval', 'countersignature'
348
38
  ```
349
-
350
- It must be told whose passkey it trusts: a slip signed by any other key
351
- allows nothing. Where the slip asks for the person's approval, the action
352
- is allowed only if the signed approval is handed over. Where the slip
353
- asks for a countersignature, `needs` says so: the action is inside the
354
- slip only if the other side then countersigns it.
355
-
356
- It is a help, not evidence: nothing in a record shows that it was called.
357
-
358
- ## Beside an agent
359
-
360
- `openRecorder` puts the two things an agent's software must do into one
361
- call: ask first, and write the receipt after.
362
-
363
- ```js
364
- import { openRecorder } from 'provared';
365
-
366
- const recorder = await openRecorder({ book, slip, privateKeys, issuerKeys });
367
-
368
- const outcome = await recorder.act(
369
- { action: 'provared.order.place', amount: { unit: 'GBP', value: 45 }, with: 'supplier' },
370
- async () => placeTheOrder(), // taken only if the answer is yes
371
- { countersign: askTheSupplierToCountersign },
372
- );
373
-
374
- outcome.done; // false: the action was outside the slip, was not taken, and nothing was written
375
- recorder.book(); // the book, with the new stub in the agent's chain
376
- await recorder.check(); // what the record shows as it stands, as checkBook would answer
39
+ npm install provared
40
+ node first-record.mjs
41
+ npx provared-check book.jsonl --issuer <the thumbprint it prints>
377
42
  ```
378
43
 
379
- If taking the action fails with an error, no stub is written; the caller
380
- must deal with whatever part of the action did happen. `record` writes a
381
- stub without asking, for an action that was taken some other way: the
382
- record is of what happened, inside the slip or not.
383
-
384
- The writer's calls wait for one another, so two actions asked for at the
385
- same moment are taken one after the other, each against the book as the
386
- one before left it. A countersignature that the other side hands back is
387
- checked before it goes into the book; one that does not check is left
388
- out, the stub stands one-sided, and `stub.countersignature.problem` says
389
- why. `add` puts an entry that someone else made (a seal, a cancellation,
390
- a refusal) into the book only if the book still passes its check with
391
- it, and it is not dated ahead of the clock. The other side is given 30
392
- seconds to countersign (`countersignWithin`); after that the stub is
393
- written one-sided. Where the slip asks for a countersignature, `act`
394
- takes the action only if it is given a way to ask for one. Before an
395
- action is taken, its stub is written and checked with the whole book, so
396
- that the receipt is known to fit. `act` takes an action now: a date
397
- handed to it must be within 300 seconds of the writer's clock (`now`).
398
- Nothing is written that is dated more than 300 seconds ahead of that
399
- clock. What the writer is handed (the request, an approval, what the
400
- other side hands back) is copied when it is handed over, and the copy is
401
- what is checked and written. `record` hands a stub to the other side
402
- only once it has passed the check of the whole book. The function
403
- that takes the action must not call the writer. Use one writer for one
404
- chain: two writers on two copies of a book know nothing of each other.
405
-
406
- The writer reads its book once, when it is opened, and then carries it
407
- on: each new line is read by itself, and no signature is checked twice.
408
- So the time an action takes grows only slowly with the length of the
409
- book. Measured on one computer on 4 October 2026, with countersigned
410
- stubs: 9, 14 and 23 thousandths of a second for one action after 400,
411
- 1,600 and 6,400 stubs. (Before the book was carried on: 2.0 seconds
412
- after 400 stubs and 7.7 after 1,600.) Opening the writer still reads
413
- the whole book: about one second for every 450 stubs. Every line
414
- is tried on a reader split off the book's own before it joins the book,
415
- so an entry that does not fit, or an error part of the way through a
416
- call, leaves the writer's view of its book exact. `recorder.check()`
417
- gives the checker's answer for the book as it stands, without reading
418
- it again. `before` waits its turn with the other calls. What the writer
419
- is told to trust (`options`) is read once, when it is opened, and
420
- fixed.
421
-
422
- A cancellation that the person signed is never lost in `add`. If it
423
- cannot be added as it was handed over (a faulty time-stamp beside it, a
424
- member that cannot be written as a line, such as a list with a gap in
425
- it), the writer adds the cancellation with its time-stamps, or with
426
- those of them that pass their check, or by itself. If it cannot be added even so (it is dated ahead
427
- of the clock), the writer keeps it, allows nothing more under the slip,
428
- and writes it into the book, at its next call, once the clock has
429
- reached its date. In both cases `add` fails, and the error says what was
430
- done. (Where the clock fails, the error is the clock's own; the
431
- cancellation is held all the same, as `cancellations()` shows.) A
432
- cancellation that the writer keeps lives only in that writer:
433
- `recorder.cancellations()` hands back what it holds, each with the
434
- acknowledgement the writer signed for it, and so does an acknowledgement
435
- that still waits to follow its cancellation into the book. Keep that
436
- with the book, and hand it in again (`options.cancellations`, an empty
437
- list included) when you open a writer again; a writer opened from the
438
- book alone knows nothing of it. Read `recorder.book()` and
439
- `recorder.cancellations()` together, with nothing awaited in between,
440
- so that the two you keep belong to the same moment. A cancellation given in that way is written into the book once
441
- its date has come, and stops the writer from the start. A copy that
442
- the writer holds takes the time-stamps handed over with it later: each
443
- is judged by itself, those the writer counts come first, and a copy
444
- holds four at most. The writer is stopped before it reads its clock, so
445
- a clock that fails then does not lose the cancellation, nor the sound
446
- time-stamps, nor the acknowledgements handed over with it. A clock that
447
- gives anything but a time counts as a clock that failed. An
448
- acknowledgement that is handed over with a cancellation is taken as the
449
- agent's own only if it checks by itself, and only the first such one is
450
- taken: the writer never puts a second one of the agent's own with a
451
- copy (a copy given at opening that already holds two is handed back as
452
- it was given). A copy holds four
453
- acknowledgements at most; the writer's own is always among them, and
454
- where the copy already holds four, the last of the others gives way to
455
- it. A writer that is stopped
456
- takes no action, but `record` still writes the stub of an action that
457
- was taken all the same: the record is of what happened, and a checker
458
- reports such a stub.
459
-
460
- ### The agent's tools
461
-
462
- An agent acts by calling tools: functions that its software offers it.
463
- `recordTools` puts each tool behind the stub writer, so that no call is
464
- forgotten. The agent's software calls the tools as before.
44
+ `first-record.mjs` ([examples/first-record.mjs](examples/first-record.mjs)):
465
45
 
466
46
  ```js
467
- import { openRecorder, recordTools } from 'provared';
468
-
469
- const recorder = await openRecorder({ book, slip, privateKeys, issuerKeys });
470
- const tools = recordTools(recorder, {
471
- placeOrder: {
472
- action: 'provared.order.place', // the name the slip uses for it
473
- with: 'supplier',
474
- amount: (order) => ({ unit: 'GBP', value: order.total }),
475
- run: (order) => sendTheOrder(order), // run only if the slip allows it
476
- countersign: (stub) => askTheSupplierToCountersign(stub),
477
- },
478
- readStock: { action: 'provared.data.read', run: (query) => lookUpStock(query) },
479
- });
480
-
481
- await tools.placeOrder({ item: 'paper', boxes: 5, total: 45 });
482
- ```
483
-
484
- A call that the slip does not allow throws `NotTaken`, with the answer
485
- of the check before acting, and the tool is not run. Where the slip asks
486
- for the person's own approval, a tool's `approve` function is asked for
487
- it once. By default the stub of a call names one document,
488
- `arguments`: the fingerprint of the call's arguments, written in one
489
- form (RFC 8785). So the record fixes exactly what was asked for without
490
- holding it; `argumentsFingerprint` works the same fingerprint out again.
491
- The arguments must be JSON: a list with a gap in it, or with a named
492
- member, or a member that is `undefined`, is refused, and the tool is
493
- not run. A fingerprint of arguments
494
- that are few and easy to guess can be guessed. What a tool is, and what
495
- it is called with, is copied when it is handed over; each function a
496
- tool is described by gets a copy of the arguments of its own, and is
497
- called by itself, without `this`: it is not handed the description and
498
- cannot change it. What `amount`, `with` and `details` give is
499
- copied too, so a function that keeps what it gave cannot change it
500
- afterwards. `amount` and `with` must give their value at once,
501
- not a promise. The numbers `0` and `-0` share a fingerprint, as RFC
502
- 8785 writes both as `0`. If `onStub` fails, its error is passed on,
503
- though the tool has run and its stub is in the book. `recordTools`
504
- names no agent software, and works with any function.
505
-
506
- ## How signing works
47
+ import { writeFileSync } from 'node:fs';
48
+ import { developmentRecorder } from 'provared/dev';
507
49
 
508
- - **The person** signs a slip with a passkey. A passkey signs a short
509
- value handed to it; that value is the SHA-256 fingerprint of the slip's
510
- exact signed bytes, so the signature covers the whole slip.
511
- - **The agent and the service** each sign with two keys at once: Ed25519,
512
- which is today's kind, and ML-DSA-87, which is quantum-safe. A record
513
- counts only if both signatures check. A forger must break both.
514
- - **The passkey signature is of today's kind only**, because no device
515
- offers a quantum-safe passkey yet.
516
- - **Whoever keeps the book** signs each seal with three keys: the same
517
- two, and SLH-DSA-SHA2-256s, which rests only on fingerprint functions.
518
- - **Browsers are only now adding ML-DSA, and have no SLH-DSA.** Where a
519
- browser lacks a method, the checking page says that those signatures
520
- were not checked, and does not show a pass. Node.js checks all of them.
50
+ const SEND = 'provared.message.send';
521
51
 
522
- ## Using the library
523
-
524
- ```js
525
- import { checkBook } from 'provared/check';
526
-
527
- const result = await checkBook(textOfTheBook, {
528
- issuerKeys: ['the thumbprint of an issuer key you already trust'],
52
+ // A permission: the agent may send messages, at most two.
53
+ const { recorder, issuerKeys } = await developmentRecorder({
54
+ actions: [SEND],
55
+ limits: [{ action: SEND, count: 2 }],
56
+ purpose: 'Send a few messages, to make a first record.',
529
57
  });
530
58
 
531
- result.summary.intact; // no problem found, and every signature checked
532
- result.summary.problemFound; // was a problem found in the record?
533
- result.summary.fullyChecked; // was every signature checked on this device?
534
- result.summary.withinSlips; // every stub compared with its slip, none outside it
535
- result.summary.firstBreach; // the first entry that shows the agent outside its slip
536
- ```
537
-
538
- Type declarations come with the library (`src/types.d.ts`), so an editor
539
- can check how it is called.
540
-
541
- **A book that grows.** `checkBook` reads a whole book each time. To
542
- follow a book as lines are added to it, open a checker once and carry it
543
- on. It reads each new line against what it worked out from the lines
544
- before it, and checks no signature twice. Its answer is the answer
545
- `checkBook` gives for the same text; a test compares the two at every
546
- length of a book that holds every kind of entry.
547
-
548
- ```js
549
- import { openChecker } from 'provared/check';
59
+ // Each action is asked for first, taken only if the permission allows it,
60
+ // and receipted after. The third message is outside it, so it is not sent.
61
+ for (let i = 1; i <= 3; i++) {
62
+ const outcome = await recorder.act({ action: SEND }, async () => console.log(`message ${i} sent`));
63
+ if (!outcome.done) console.log(`message ${i} not sent: ${outcome.answer.breaches[0].message}`);
64
+ }
550
65
 
551
- const checker = await openChecker(textOfTheBookSoFar, { issuerKeys });
552
- await checker.add(newLines); // whole lines, each ending with a line feed
553
- const result = await checker.result(); // the same shape as checkBook returns
66
+ writeFileSync('book.jsonl', recorder.book());
67
+ console.log(`npx provared-check book.jsonl --issuer ${issuerKeys[0]}`);
554
68
  ```
555
69
 
556
- What the checker is told to trust is fixed when it is opened. Each answer
557
- is a copy of its own.
558
-
559
- The checker never throws for a bad record. A failed check is a normal
560
- answer with a named reason. It keeps two things apart:
70
+ The same in Python is
71
+ [python/examples/first_record.py](python/examples/first_record.py), with
72
+ `from provared.dev import development_recorder`. For a LangChain agent,
73
+ `pip install provared-langchain` puts every tool call behind the same
74
+ check with no change to the tools
75
+ ([python/integrations/provared-langchain](python/integrations/provared-langchain)).
561
76
 
562
- - **problems**: reasons the record cannot be relied on (a changed entry, a
563
- signature that does not fit, a broken chain);
564
- - **breaches**: what a sound record shows about the agent (an action the
565
- slip does not allow, a limit passed).
566
-
567
- ### What the checker returns
77
+ ## The four parts
568
78
 
569
- | Member | Meaning |
79
+ | Part | What it is |
570
80
  |---|---|
571
- | `size`, `root` | How many entries the book holds, and its top fingerprint |
572
- | `problems` | Problems with the book as a whole |
573
- | `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 |
574
- | `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) |
575
- | `summary.problemFound` | A problem was found somewhere in the record |
576
- | `summary.fullyChecked` | Every signature was checked on this device |
577
- | `summary.intact` | No problem was found **and** every signature was checked. The only answer that is a pass |
578
- | `summary.methodsMissing` | The signing methods this device lacks, if any, for example `ML-DSA-87` |
579
- | `summary.withinSlips` | Every stub was compared with its slip, and none is outside it |
580
- | `summary.firstBreach` | The first entry that shows the agent outside its slip, if any |
581
- | `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 |
582
- | `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 |
583
- | `summary.limits` | What no check can show, in plain sentences |
584
-
585
- A signature state is `valid`, `invalid`, `unavailable` (this device
586
- lacks the method; never a pass), or `unchecked` (not checked, because
587
- what it rests on failed its check). Each problem and each breach has a `code`
588
- in plain words and a `message`. The codes are listed in
589
- `docs/threat-model.md`, section 8.
590
-
591
- The `content` of an entry that has problems, or whose `verified` is
592
- `none`, is unverified. It is there for whoever must find out what went
593
- wrong. Never show it as fact: the checking page and the command-line
594
- checker do not.
595
-
596
- Options: `issuerKeys` (the thumbprints of issuer keys you trust),
597
- `sealKeys` (the fingerprints of the key sets of the recorders you
598
- expect), `stampServices` (the fingerprints of the certificates of the
599
- time-stamp services you trust), `blocks` (the fingerprints of the
600
- blocks of a public blockchain you trust), `vouchers` (the fingerprints of the key
601
- sets of the organisations whose vouching you trust), `disclosures` (the
602
- disclosures of covered fields that you were handed), `cancellations`
603
- (the person's own copies of cancellations), `expectedRoot` and
604
- `expectedSize` (a top fingerprint and a number of entries you already
605
- hold a copy of). Give
606
- them whenever you have them.
607
-
608
- The checker reads a whole book into memory. A book of several hundred
609
- megabytes may be too large for a browser or for Node.js to hold as one
610
- text.
611
-
612
- The command-line checker ends with code 0 (intact, nothing outside the
613
- slip, and checked against keys you named: the passkey you trust and,
614
- where the record has a seal, the recorder you expect), 1 (no problem
615
- found, but the agent is outside its slip, not every signature could be
616
- checked, or you did not name whom you trust) or 2 (a problem was found,
617
- or what you gave on the command line could not be used). A value that
618
- is not a fingerprint is refused, never set aside. The checking page
619
- gives its green answer on the same terms.
620
-
621
- Writing records: `prepareSlip` and `assembleSlip`, `prepareApproval` and
622
- `assembleApproval` (the person), `writeStub` (the agent), `countersign`,
623
- `prepareCancellation` and `assembleCancellation` (the person),
624
- `writeRefusal` and `writeTerms` (the service), `writeVouching` and
625
- `writeWithdrawal` (an organisation), `writePass` (an agent, to a helper
626
- agent), `writeSeal` (whoever keeps
627
- the book; it needs Node.js), `writeBook` and `makeShow`, all from
628
- `provared`. The library asks no time-stamp service itself: you send
629
- the seal's fingerprint to the service you choose and put its answer
630
- beside the seal. In a browser, `createPasskey`, `signSlipWithPasskey`,
631
- `approveWithPasskey` and `cancelWithPasskey` from
632
- `provared/passkey-browser`. For a slip with covered fields in a browser:
633
- `prepareSlip` with `cover`, then `signChallengeWithPasskey` with what it
634
- prepared, then `assembleSlip`; keep the disclosures it prepared.
81
+ | **The permission (a Slip)** | A person signs it with a passkey: which agent, which actions, what limits and conditions, with whom, from when until when, and why |
82
+ | **The receipt (a Stub)** | The agent signs one for each action, pointing back to its slip; the other side countersigns it where it can, and the person approves it where the slip asks |
83
+ | **The sealed book (a Seal)** | The receipts chained, gathered under one fingerprint, signed by whoever keeps the book and time-stamped by an outside service |
84
+ | **One page with proof (a Show)** | One entry handed 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 |
85
+
86
+ The checker answers two questions and keeps them apart: **is the record
87
+ intact**, and **did the agent stay within its slip**. Going over a limit
88
+ is not a fault in the record. It is what a sound record shows.
89
+
90
+ ## What the author found nowhere else
91
+
92
+ Signed, chained and time-stamped receipts for an agent's actions exist
93
+ in other open projects too, some with quantum-safe signatures. As far as
94
+ the author knows, in October 2026, three things are only here: **limits
95
+ a machine can check** in a permission the person signs, with the
96
+ checker's answer "did the agent stay within it"; **passing a permission
97
+ on** to a helper agent so that it can narrow and never widen; and
98
+ **cancelling** with the agent's signed acknowledgement, so that a record
99
+ shows when the agent's side was told. If you know of another project
100
+ that does any of these, open an issue: the list is kept honest.
635
101
 
636
102
  ## What a record does not prove
637
103
 
638
- - It proves what was recorded, not what was left out. An agent that acts
639
- and writes no stub leaves no trace.
640
- - A one-sided stub (one with no countersignature) proves what the agent's
641
- side said, not what the other side did.
642
- - A refusal proves what the service's side said, not what the agent did.
643
- - A rule of conduct in a slip is the person's signed instruction. No
644
- check can show that the agent kept it.
645
- - An approval proves that the passkey approved that one action, not that
646
- the person read what they approved.
647
- - It does not show that an action was wise, lawful or wanted.
648
- - A badly written slip is faithfully recorded as a badly written slip.
649
- - It does not prove who was holding the device when the passkey signed.
650
- - A name in a record is a label. Only keys are checked. A vouching
651
- record shows which organisation stood behind a name, not how well it
652
- checked.
653
-
654
- And about time:
655
-
656
- - only an outside time-stamp you trust, from a service or a block you
657
- named, proves when entries existed. The times inside records are the signer's own word,
658
- so a limit within a period rests on the agent's word;
659
- - after the last seal, the latest stubs under a slip or a pass, or a
660
- slip or a pass with all its stubs, can be removed unnoticed, which can hide a stub that shows
661
- the agent outside its slip;
662
- - the last seal, with everything after it, can itself be removed. Only a
663
- copy of it, or of its top fingerprint and number of entries, held by
664
- someone else shows that;
665
- - time-stamp services sign with methods of today's kind, which are not
666
- quantum-safe.
104
+ It shows what was recorded, not what was left out: an agent that acts
105
+ and writes no stub leaves no trace. A one-sided stub shows what the
106
+ agent's side said. It does not show that an action was wise, lawful or
107
+ wanted, nor who was holding the device when the passkey signed. A name
108
+ in a record is a label; only keys are checked. The full list, with what
109
+ each part protects against, is in
110
+ [docs/threat-model.md](docs/threat-model.md).
111
+
112
+ ## Where everything else is
113
+
114
+ - **[The guide](docs/guide.md)**: the whole library, in one document.
115
+ Installing and importing; the command-line checker; what a slip can
116
+ say; seals, time-stamps and the chain of block headers; cancelling;
117
+ who stands behind a name; covered fields; passing a slip on; the check
118
+ before acting; the stub writer beside an agent and the connector for
119
+ its tools; what the checker returns; what it never does; what is not
120
+ built yet.
121
+ - **[The format, described exactly](spec/provared-format.md)**, and
122
+ the shared test files in [test-vectors/](test-vectors/) that any
123
+ implementation can be held to.
124
+ - **[The Python version](python/README.md)**, which gives the same
125
+ answers, with examples for three agent frameworks and packages for
126
+ two of them.
127
+ - **[The pages at prova.red](https://prova.red/)**: the checker's answer
128
+ for the sample record, a worked example for developers, and the same
129
+ in plain words for a reader who does not write code.
130
+ - **[The checking page](https://prova.red/check/)**, which checks a
131
+ record in a browser and loads nothing from anywhere; also built as
132
+ one file, attached to each release.
133
+ - **The core of the format as an individual Internet-Draft** at the
134
+ IETF:
135
+ [draft-izmaylov-agent-permission-receipts](https://datatracker.ietf.org/doc/draft-izmaylov-agent-permission-receipts/).
136
+ An Internet-Draft is a working document, not a standard.
137
+ - **[CHANGELOG.md](CHANGELOG.md)**, [SECURITY.md](SECURITY.md) and
138
+ [CONTRIBUTING.md](CONTRIBUTING.md).
667
139
 
668
- ## What it never does
669
-
670
- It never holds money, never starts a payment and issues no token. It
671
- reports nothing back from the machine it runs on. The checker makes no
672
- network request. Only `provared-headers`, which you run yourself,
673
- connects to anything: to the Bitcoin nodes you name, for block headers.
674
-
675
- ## Not built yet
140
+ ## Status
676
141
 
677
- A public place where seals are kept; covering fields other than a slip's
678
- names and purpose; connectors for particular agent software (the
679
- general one, `recordTools`, is built); a compact binary form; the
680
- recorder service. The checking page cannot yet take a chain of block
681
- headers: use the command-line checker.
142
+ **Version 0.3.0, a draft.** Published as it is, by one author in their
143
+ own time, built with AI tools since 3 October 2026; fixes as time
144
+ allows. The code has been through 25 rounds of review by AI reviewers,
145
+ each a fresh session given only the code and the documents; every fault
146
+ was fixed and kept as a test. No person other than the author has
147
+ reviewed it yet. That is an invitation. The format may still change.
148
+ Every published version stays on npm and the Python Package Index, so a
149
+ record made with one version can always be checked by that version.
150
+
151
+ Questions, faults and suggestions: hello@prova.red, or
152
+ [issues on GitHub](https://github.com/provared/provared/issues).
682
153
 
683
154
  ## Licences
684
155