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/CHANGELOG.md +291 -2
- package/README.md +116 -645
- package/bin/provared-check.mjs +36 -1
- package/docs/guide.md +736 -0
- package/docs/threat-model.md +12 -0
- package/package.json +25 -2
- package/spec/provared-format.md +33 -4
- package/src/compare.js +10 -7
- package/src/dev.d.ts +54 -0
- package/src/dev.js +123 -0
- package/src/encoding.js +9 -1
- package/src/keys.js +24 -1
- package/src/recorder.js +32 -4
- package/src/signatures.js +5 -1
- package/src/slip.js +24 -11
- package/src/timestamp.js +59 -16
- package/src/tools.js +7 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,297 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.3.0 (10 October 2026)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- 2026-10-10: **a development stand-in for a passkey**, `provared/dev`
|
|
6
|
+
in JavaScript and `provared.dev` in Python, so that a first record can
|
|
7
|
+
be made without a browser: `developmentPasskey` (an ES256 key made in
|
|
8
|
+
memory that returns the three values a passkey returns, for the page
|
|
9
|
+
address `http://localhost`), `developmentSlip` (a slip it signed, with
|
|
10
|
+
new keys for the agent, a book holding the slip, and the stand-in's
|
|
11
|
+
thumbprint for a checker) and `developmentRecorder` (the stub writer
|
|
12
|
+
opened under such a slip, in one call). It is not a passkey and no
|
|
13
|
+
person confirms anything through it, so the issuer's name in every
|
|
14
|
+
slip it signs is made to end with " (development)", whatever name is
|
|
15
|
+
given, and the checker prints that name. The library itself (`provared`)
|
|
16
|
+
does not export it, and the checker does not use it. A first record in
|
|
17
|
+
one file: `examples/first-record.mjs` and
|
|
18
|
+
`python/examples/first_record.py`, each run by a test.
|
|
19
|
+
- 2026-10-10: `provared-check --sample` checks the sample record that
|
|
20
|
+
comes with the library, naming the stand-in passkey, the recorder and
|
|
21
|
+
the time-stamp service it was made with as trusted, and prints their
|
|
22
|
+
fingerprints; with `--json` the result alone. A file or another option
|
|
23
|
+
given with it is refused.
|
|
24
|
+
- 2026-10-10: the README is now a short landing page (install, a first
|
|
25
|
+
record, the four parts, what is only here, what a record does not
|
|
26
|
+
prove, where everything else is), and its former body is
|
|
27
|
+
`docs/guide.md`, unchanged but for the additions above. New:
|
|
28
|
+
`SECURITY.md` (how to report a fault in the security of a record) and
|
|
29
|
+
`CONTRIBUTING.md` (fault reports and other implementations welcome;
|
|
30
|
+
code only under a contributor agreement, whose text is not ready yet).
|
|
31
|
+
`package.json` and `python/pyproject.toml` gain a homepage, keywords
|
|
32
|
+
and a longer description. Version 0.3.0 in both, not yet published.
|
|
33
|
+
- 2026-10-10: the pages at prova.red: the home page's first screen now
|
|
34
|
+
shows the checker's summary and the install lines before the four
|
|
35
|
+
parts, with the plain word first and the short name after ("the
|
|
36
|
+
permission (a Slip)"); a first record of your own, in three commands,
|
|
37
|
+
with the file above and the LangChain package; "what the author found
|
|
38
|
+
nowhere else (October 2026)"; the status promises that every published
|
|
39
|
+
version stays on npm and the Python Package Index. The plain-words page
|
|
40
|
+
says that the 25 rounds of review were by AI reviewers and that no
|
|
41
|
+
person other than the author has reviewed the code yet, gives the date
|
|
42
|
+
from which the EU AI Act's logging duty applies (2 December 2027),
|
|
43
|
+
marks its charity example as invented, and uses "flight recorder" only
|
|
44
|
+
as a description. The developer page shows the code that made the
|
|
45
|
+
sample record and says the same about the reviews. All three pages
|
|
46
|
+
gain preview tags, an icon (`favicon.svg`, four coloured squares) and
|
|
47
|
+
a preview image (`preview.png`, drawn from a small page in a headless
|
|
48
|
+
browser), with the content security policy widened by `img-src 'self'`
|
|
49
|
+
only; `robots.txt` and `sitemap.xml` are added, and the workflow copies
|
|
50
|
+
everything in `site/`. Nothing in the library changes for this.
|
|
51
|
+
- 2026-10-10: the pages for prova.red, in `site/`: what a record
|
|
52
|
+
proves, the command-line checker's own answer for the sample record
|
|
53
|
+
(run on Node.js 24.19), how to try it, what is only here, how it is
|
|
54
|
+
built, where it is, a worked example for developers ("An agent goes
|
|
55
|
+
over its limit, and the record shows where") and "Provared in plain
|
|
56
|
+
words", three minutes for a reader who does not write code, with two
|
|
57
|
+
drawings that follow the sample record. No script, no font, no image,
|
|
58
|
+
no form: each page loads nothing from anywhere and keeps nothing about
|
|
59
|
+
a reader, and says so. The drawings carry their colours as plain SVG
|
|
60
|
+
attributes as well as through the style sheet, and the pages load the
|
|
61
|
+
style sheet under a versioned address, so a browser that kept an
|
|
62
|
+
earlier copy still draws them right. A workflow, `.github/workflows/pages.yml`,
|
|
63
|
+
publishes them with GitHub Pages from the main branch, with the
|
|
64
|
+
checking page at `/check/` (with the library and the sample it loads,
|
|
65
|
+
copied as they are) and built as one file at `/provared-check.html`.
|
|
66
|
+
The four actions it uses are GitHub's own, each pinned to the commit
|
|
67
|
+
of a released version. The site is not published until the domain
|
|
68
|
+
points at it. Nothing in the library, the checker or the package
|
|
69
|
+
changes.
|
|
70
|
+
- 2026-10-08: two small Python packages for agent frameworks, in
|
|
71
|
+
`python/integrations/`, each in a folder of its own, which names its
|
|
72
|
+
framework. Each puts every tool call of an agent behind the stub
|
|
73
|
+
writer with no change to the tools: the check before acting is asked,
|
|
74
|
+
the tool runs only if the slip allows it, and a stub is written; a call
|
|
75
|
+
that is not allowed is not run, and the model is told why. Each checks
|
|
76
|
+
the call as the tool receives it: one must come last among the parts
|
|
77
|
+
that wrap tool calls, and refuses to run otherwise; the other wraps
|
|
78
|
+
each of the agent's own sets of tools, wherever it stands. Each works with ordinary
|
|
79
|
+
and asynchronous agents: the stub writer runs in a thread of its own
|
|
80
|
+
for each call, and cancelling a run cancels a tool that is waiting.
|
|
81
|
+
Each is tested with its framework's own stand-in model, inside the
|
|
82
|
+
examples' guard. An independent review found eight faults in the first
|
|
83
|
+
draft, among them a reviewer's change that got past the slip and
|
|
84
|
+
asynchronous agents that could hang; each is fixed and tested. They use
|
|
85
|
+
only the public interface of the `provared` package, which itself still
|
|
86
|
+
names no product. Not yet published.
|
|
87
|
+
|
|
88
|
+
## 0.2.0 (8 October 2026)
|
|
89
|
+
|
|
90
|
+
- 2026-10-08: version 0.2.0, for the npm package and, for the first
|
|
91
|
+
time, the Python package `provared` on the Python Package Index. Both
|
|
92
|
+
give the same answers, as the shared test files in `test-vectors/`
|
|
93
|
+
show. The version rises from 0.1.0 because the format's rules changed:
|
|
94
|
+
the exact rule for a page address in a slip, weak Ed25519 keys and
|
|
95
|
+
signatures refused, and strict keys in a time-stamp service's
|
|
96
|
+
certificate. Version 0.1.0 accepts a forged time-stamp where the
|
|
97
|
+
person checking names as trusted a service whose certificate has an RSA
|
|
98
|
+
public number of 1; 0.2.0 refuses it. The entries below say what
|
|
99
|
+
changed.
|
|
100
|
+
- 2026-10-08: fixes from an independent review of `record_function`, the
|
|
101
|
+
examples and the wording for a proposed action. None made a book read
|
|
102
|
+
as intact, or within its slip, when it was not.
|
|
103
|
+
- Python: a whole number in a tool's arguments that JavaScript cannot
|
|
104
|
+
hold exactly (beyond 2^53 - 1, unless a float holds it exactly) is
|
|
105
|
+
refused. Before, `2**60 + 1` and `2**60` left the same fingerprint in
|
|
106
|
+
their stubs, though the tool was handed the exact number. JSON is now
|
|
107
|
+
read as JavaScript reads it here too: a whole number beyond 2^53 - 1
|
|
108
|
+
is the nearest float.
|
|
109
|
+
- Python: a number of a class built on int or float (an Enum of whole
|
|
110
|
+
numbers, say) is written as its value, so its fingerprint is that of
|
|
111
|
+
the same arguments kept as JSON. Before, it was written as its name,
|
|
112
|
+
which is not JSON.
|
|
113
|
+
- A generator function is refused as a tool (`recordTools` and
|
|
114
|
+
`record_tools`) and by `record_function`: calling one does not run
|
|
115
|
+
its body, so its stub was written for an action not yet taken.
|
|
116
|
+
- Python: a dict in a tool's arguments whose names are not all text is
|
|
117
|
+
refused. Before, `{101: 2, "101": 5}` reached the tool as `{"101": 5}`.
|
|
118
|
+
- Python: `record_function` refuses, when it is handed the function, a
|
|
119
|
+
parameter whose type can never be plain data (a class of its own,
|
|
120
|
+
such as a model or a date, a tuple or a set), an object whose
|
|
121
|
+
`__call__` is defined with `async def`, and a function whose name is
|
|
122
|
+
not text, each in words of its own. A call whose arguments are not
|
|
123
|
+
plain data names the argument.
|
|
124
|
+
- Python: the README and the docstring say that the function is run
|
|
125
|
+
with a copy of its arguments, and what the record leaves out (an
|
|
126
|
+
argument bound into a `functools.partial` in place).
|
|
127
|
+
- The check before acting says "the action gives no amount", not "the
|
|
128
|
+
stub gives no amount". The shared file `check-before.json` now holds
|
|
129
|
+
a case for each of the words for a proposed action.
|
|
130
|
+
- Examples: `offline()` also refuses the other kinds of name look-up,
|
|
131
|
+
messages sent without a connection, starting another program, a
|
|
132
|
+
connection made by Python's event loop on Windows (which raises no
|
|
133
|
+
audit event), and a connection to a port on this computer that the
|
|
134
|
+
same program does not hold, such as a proxy's. The tests check that
|
|
135
|
+
nothing was refused while each example ran. Its README says exactly
|
|
136
|
+
what it refuses.
|
|
137
|
+
- 2026-10-08: examples for three widely used Python agent frameworks, in
|
|
138
|
+
`python/examples/`, the one folder that names them. Each records an
|
|
139
|
+
agent's tools with `record_function`, runs the agent with the
|
|
140
|
+
framework's own stand-in model (no AI service is called; tracing is
|
|
141
|
+
switched off; any connection outside the program is refused while it
|
|
142
|
+
runs), and checks the book. Each is tested
|
|
143
|
+
(`python/examples/test_examples.py`). The library itself still names no
|
|
144
|
+
product.
|
|
145
|
+
- 2026-10-08: the check before acting speaks of an action that is only
|
|
146
|
+
proposed: "With this action the total would be 280 GBP", not "With this
|
|
147
|
+
stub the total is", since no stub exists for an action that is not
|
|
148
|
+
taken. Likewise for a count and a period. The codes are unchanged; the
|
|
149
|
+
checker's words for a stub are unchanged.
|
|
150
|
+
- 2026-10-08: the Python version gains `record_function`, which puts one
|
|
151
|
+
ordinary function behind the stub writer and keeps its name, its
|
|
152
|
+
parameters and its description, so that an agent framework describes
|
|
153
|
+
the recorded function to its model as it would the function. Python
|
|
154
|
+
only: a JavaScript tool takes its arguments as one object. Tested
|
|
155
|
+
(`python/tests/test_record_function.py`).
|
|
156
|
+
- 2026-10-08: `test-vectors/README.md` says what each shared file holds
|
|
157
|
+
and what must match: Provared's own libraries give every answer
|
|
158
|
+
exactly, words included; another implementation needs to match the
|
|
159
|
+
codes and the shape of each answer. The Python package is made ready
|
|
160
|
+
for the Python Package Index (version 0.1.0, both licence files); it is
|
|
161
|
+
not yet published there.
|
|
162
|
+
- 2026-10-08: faults found by a second independent review, of the
|
|
163
|
+
Python stub writer and of the changes above. In 1,183 random
|
|
164
|
+
scenarios (about 79,500 steps) played in both languages, and 140 runs
|
|
165
|
+
with several threads at once, no action was taken after the person's
|
|
166
|
+
cancellation was handed over, and the writer's view of its book always
|
|
167
|
+
equalled a whole check. Each fault is fixed and each is now a test
|
|
168
|
+
(`python/tests/test_review2.py`).
|
|
169
|
+
- **An Ed25519 signature whose first half (R) is a point of small
|
|
170
|
+
order is refused**, in both languages, whatever the key. The holder
|
|
171
|
+
of an honest key could make one for any message; Node.js refused it
|
|
172
|
+
and the OpenSSL beneath the Python version accepted it, so one
|
|
173
|
+
record had two answers (format description, section 3.8).
|
|
174
|
+
- A copy of what the stub writer is handed is refused if it nests more
|
|
175
|
+
than 64 levels deep, in both languages. Before, the JavaScript
|
|
176
|
+
library's limit depended on how much of the stack was in use. The
|
|
177
|
+
depth is measured on the copy, so the caller's value is still read
|
|
178
|
+
once.
|
|
179
|
+
- Python: a cancellation handed to `add` beside a member whose name is
|
|
180
|
+
a number, None or something stranger, or in a read-only mapping, is
|
|
181
|
+
read, and stops the writer, as in JavaScript. An error from the other
|
|
182
|
+
side that is not an `Exception` (such as `SystemExit`) leaves a
|
|
183
|
+
one-sided stub, as any other failure does. `snapshot()` hands back
|
|
184
|
+
the book and the cancellations the writer holds, read together, so
|
|
185
|
+
that another thread's call cannot fall between the two. A number too
|
|
186
|
+
large for a float is no time; options holding objects that cannot be
|
|
187
|
+
copied are kept as they are, as the JavaScript library keeps them;
|
|
188
|
+
the record writers take a datetime with a time zone, as the
|
|
189
|
+
JavaScript ones take a Date.
|
|
190
|
+
- 2026-10-08: faults found by an independent review of the Python
|
|
191
|
+
version, which also changed the JavaScript library.
|
|
192
|
+
- **Weak Ed25519 keys are refused.** Under one of the eight Ed25519
|
|
193
|
+
keys of small order, anyone can make a signature that checks.
|
|
194
|
+
Node.js refused such signatures; the OpenSSL beneath the Python
|
|
195
|
+
version accepted them, so with such a key trusted, a slip made by
|
|
196
|
+
someone holding no private key read as sound there. RFC 8032 does
|
|
197
|
+
not say to refuse these keys and libraries differ, so now the
|
|
198
|
+
checker refuses them itself, with any key written in more than one
|
|
199
|
+
way (y not below p): `bad-key` for a key in a record,
|
|
200
|
+
`stamp-invalid` for a time-stamp service's key.
|
|
201
|
+
- **The page address in a slip follows an exact rule.** The checker
|
|
202
|
+
used the URL Standard's own reader of addresses, which for a name
|
|
203
|
+
beginning `xn--` rests on tables of Unicode that differ from one
|
|
204
|
+
version of Node.js to the next, so two checkers could disagree. Now
|
|
205
|
+
`issuer.origin` must be written as a browser writes it, by a rule
|
|
206
|
+
that needs no such tables: `https://` (or `http://localhost`), a
|
|
207
|
+
host of lower-case letters, digits and hyphens in labels, a port
|
|
208
|
+
only where it is not the scheme's own, and nothing more (format
|
|
209
|
+
description, section 4.1). Some addresses the URL Standard allows
|
|
210
|
+
are now refused: underscores and other signs in a host, port 0,
|
|
211
|
+
addresses of numbers.
|
|
212
|
+
- A time-stamp service's key of another kind, or on another curve, is
|
|
213
|
+
refused in words that say so, not as a signature that "does not
|
|
214
|
+
fit".
|
|
215
|
+
- A time is read only from a number of milliseconds or a Date. Text,
|
|
216
|
+
true or a list were read by rules that differ from one device to
|
|
217
|
+
another (text with no time zone was taken as local time).
|
|
218
|
+
- `withoutMethods` that is not a list is set aside when a slip is
|
|
219
|
+
checked, as everywhere else. Before, it made the slip's check fail.
|
|
220
|
+
- 2026-10-08: the Python version gains the stub writer beside an agent
|
|
221
|
+
(`open_recorder`) and the connector for an agent's tools
|
|
222
|
+
(`record_tools`, `arguments_fingerprint`, `NotTaken`). So it now does
|
|
223
|
+
everything the JavaScript library does.
|
|
224
|
+
- They are ported one to one and give the same answers: two new
|
|
225
|
+
shared files, `test-vectors/recorder-scenarios.json` (32 scenarios,
|
|
226
|
+
about 1,000 steps) and `test-vectors/tool-scenarios.json` (8
|
|
227
|
+
scenarios), each a world of records made beforehand and a list of
|
|
228
|
+
steps (actions, records, cancellations in every form the writer
|
|
229
|
+
takes, time-stamps, acknowledgements, a clock that fails, writers
|
|
230
|
+
opened again, tools called), with the transcript of what happened.
|
|
231
|
+
The two libraries play each scenario (`test/helpers/scenarios.mjs`
|
|
232
|
+
and `python/tests/scenarios.py`) and must write the same transcript,
|
|
233
|
+
down to the words of every message and how many times the clock was
|
|
234
|
+
read.
|
|
235
|
+
- In Python every call is an ordinary function. The writer's calls
|
|
236
|
+
wait for one another across threads. A call to the writer from
|
|
237
|
+
inside an action it is taking, or from the other side while it
|
|
238
|
+
countersigns, is refused at once, as in Node.js. The other side is
|
|
239
|
+
asked in a thread of its own and given 30 seconds, as before. A
|
|
240
|
+
function handed over that gives a coroutine is refused.
|
|
241
|
+
- `key_set_from_seeds` and `key_set_seeds`: an agent's keys kept
|
|
242
|
+
between runs as their two 32-byte seeds.
|
|
243
|
+
- The Python reading of base64url and the copy of a check's options
|
|
244
|
+
are faster; they give the same answers (a value shared many times in
|
|
245
|
+
the options is now copied once, as the JavaScript library copies it).
|
|
246
|
+
- `test/helpers/world.mjs` can be given the agent's and the service's
|
|
247
|
+
key sets, so that a scenario can hand the same keys to Python.
|
|
248
|
+
- 2026-10-08: two faults in the check of a time-stamp from a service,
|
|
249
|
+
found while writing the Python version.
|
|
250
|
+
- An RSA key with the public number 1 in a service's certificate was
|
|
251
|
+
accepted. Under it, anyone can make a signature that checks, so a
|
|
252
|
+
time-stamp could be made without the service's private key. It
|
|
253
|
+
counted only where the person checking had named that certificate as
|
|
254
|
+
trusted. An even public number was accepted too. Now the public
|
|
255
|
+
number must be odd, from 3 to 2^32 - 1 (`stamp-invalid`).
|
|
256
|
+
- The public key inside a certificate was read loosely: a compressed or
|
|
257
|
+
"hybrid" point, RSA numbers with needless or missing sign bytes,
|
|
258
|
+
bytes after the key, any parameters, and unused bits all loaded. Now
|
|
259
|
+
the key must be written exactly as its own standard sets out
|
|
260
|
+
(`stamp-bad-data`), as the rest of a time-stamp already must be, so
|
|
261
|
+
that two checkers cannot disagree about whether it can be read.
|
|
262
|
+
- Each is a shared test (`test-vectors/stamps-strict-keys.json`).
|
|
263
|
+
- 2026-10-08: shared test files, and the start of a Python version.
|
|
264
|
+
- `test-vectors/` holds records and values, each with the answer this
|
|
265
|
+
library gives: the canonical form, base64url, times, keys, a
|
|
266
|
+
passkey's signature, the envelope of a record, the content of a stub
|
|
267
|
+
and of a countersignature, page addresses and 170 slips, sound and
|
|
268
|
+
faulty. `tools/make-vectors.mjs` writes them;
|
|
269
|
+
`test/vectors.test.mjs` checks that this library still gives every
|
|
270
|
+
answer. Any other implementation of the format can be tested against
|
|
271
|
+
them.
|
|
272
|
+
- `python/` holds a Python version, built in stages against the same
|
|
273
|
+
files. It is not yet published. It now checks everything the
|
|
274
|
+
JavaScript library checks: slips, stubs, countersignatures, limits,
|
|
275
|
+
approvals, refusals and terms, passes, cancellations and
|
|
276
|
+
acknowledgements, vouching records, seals and their time-stamps,
|
|
277
|
+
block time-stamps, the tree and its proofs, Shows, the chain of
|
|
278
|
+
block headers, and the check before acting. It gives the same answer
|
|
279
|
+
in every one of the 63 shared files, and in 6,091 random books
|
|
280
|
+
compared on this computer. One difference is stated: "cryptography",
|
|
281
|
+
the one package it needs, has no SLH-DSA yet, so a seal's third
|
|
282
|
+
signature is reported as not checked on this device and never as a
|
|
283
|
+
pass. The stub writer and the connector for an agent's tools are
|
|
284
|
+
still to come.
|
|
285
|
+
- A slip whose page address cannot be read, and one whose address is
|
|
286
|
+
not in its plain form, are now refused with the same words. The code
|
|
287
|
+
(`bad-field`) is unchanged.
|
|
288
|
+
- 2026-10-07: the README links to the individual Internet-Draft
|
|
289
|
+
`draft-izmaylov-agent-permission-receipts`, which describes the core of
|
|
290
|
+
the format.
|
|
291
|
+
|
|
292
|
+
## 0.1.0 (6 October 2026)
|
|
293
|
+
|
|
294
|
+
Draft version 0 of the format, published and not frozen.
|
|
6
295
|
|
|
7
296
|
- 2026-10-05: `provared-headers` is marked as experimental, in the
|
|
8
297
|
README and in what the command prints.
|