@sanning/proof 0.19.1 → 0.20.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 +30 -29
- package/dist/agent-record.d.ts +62 -0
- package/dist/agent-record.js +527 -0
- package/dist/agent-schemas.generated.d.ts +15 -0
- package/dist/agent-schemas.generated.js +5759 -0
- package/dist/case-file.d.ts +32 -89
- package/dist/case-file.js +41 -93
- package/dist/case.d.ts +339 -822
- package/dist/case.js +667 -1160
- package/dist/chain.d.ts +19 -47
- package/dist/chain.js +78 -205
- package/dist/claims.d.ts +211 -0
- package/dist/claims.js +1081 -0
- package/dist/cli.d.ts +45 -68
- package/dist/cli.js +618 -1022
- package/dist/compose.js +23 -65
- package/dist/crypto.d.ts +7 -8
- package/dist/crypto.js +60 -147
- package/dist/declaration.d.ts +68 -194
- package/dist/declaration.js +175 -518
- package/dist/environment.d.ts +9 -30
- package/dist/environment.js +17 -66
- package/dist/evidence.d.ts +119 -160
- package/dist/evidence.js +744 -1103
- package/dist/exit-codes.d.ts +11 -27
- package/dist/exit-codes.js +24 -59
- package/dist/file-match.d.ts +33 -79
- package/dist/file-match.js +33 -69
- package/dist/grading.d.ts +98 -135
- package/dist/grading.js +224 -380
- package/dist/handoff.d.ts +62 -160
- package/dist/handoff.js +153 -393
- package/dist/index.d.ts +14 -10
- package/dist/index.js +60 -167
- package/dist/json-schema.d.ts +29 -0
- package/dist/json-schema.js +298 -0
- package/dist/merkle.js +16 -28
- package/dist/message-parts.d.ts +56 -212
- package/dist/message-parts.js +142 -502
- package/dist/side-content.d.ts +11 -22
- package/dist/side-content.js +22 -65
- package/dist/types.d.ts +17 -0
- package/dist/types.js +19 -5
- package/dist/verifier.d.ts +40 -114
- package/dist/verifier.js +196 -535
- package/dist/witness-cache.d.ts +24 -45
- package/dist/witness-cache.js +38 -81
- package/dist/witness-lookup.d.ts +36 -57
- package/dist/witness-lookup.js +89 -139
- package/dist/zip-reader.d.ts +18 -33
- package/dist/zip-reader.js +19 -34
- package/package.json +11 -3
- package/src/agent-record.ts +608 -0
- package/src/agent-schemas.generated.ts +5764 -0
- package/src/case-file.ts +45 -109
- package/src/case.ts +814 -1328
- package/src/chain.ts +85 -224
- package/src/claims.ts +1253 -0
- package/src/cli.ts +714 -1176
- package/src/compose.ts +33 -79
- package/src/crypto.ts +60 -147
- package/src/declaration.ts +219 -656
- package/src/environment.ts +17 -65
- package/src/evidence.ts +958 -1381
- package/src/exit-codes.ts +24 -63
- package/src/file-match.ts +40 -89
- package/src/grading.ts +287 -421
- package/src/handoff.ts +175 -427
- package/src/index.ts +131 -177
- package/src/json-schema.ts +295 -0
- package/src/merkle.ts +16 -28
- package/src/message-parts.ts +155 -533
- package/src/side-content.ts +25 -68
- package/src/types.ts +55 -36
- package/src/verifier.ts +202 -526
- package/src/witness-cache.ts +45 -94
- package/src/witness-lookup.ts +107 -177
- package/src/zip-reader.ts +20 -38
- package/vectors/README.md +52 -34
- package/vectors/agent-record.json +2175 -0
- package/vectors/case-file-index.json +1 -1
- package/vectors/case-split.json +2 -2
- package/vectors/chain-reading.json +3 -3
- package/vectors/file-match.json +285 -148
- package/vectors/jcs-reader.json +6 -6
- package/vectors/pack-grading.json +102 -25
- package/vectors/sandbox-evidence.json +573 -581
- package/vectors/stamp-on-chain.json +18 -18
- package/vectors/witness-cache.json +49 -49
- package/dist/agent-proof.d.ts +0 -28
- package/dist/agent-proof.js +0 -226
- package/dist/run-adapters.d.ts +0 -10
- package/dist/run-adapters.js +0 -35
- package/dist/run.d.ts +0 -209
- package/dist/run.js +0 -1405
- package/src/agent-proof.ts +0 -264
- package/src/run-adapters.ts +0 -44
- package/src/run.ts +0 -1598
package/src/case-file.ts
CHANGED
|
@@ -1,33 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* A case file's index compared with the drop it describes (evidence-bundle §5.6).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* A case file is a ZIP of packs, each under `packs/<ref>/`, plus an index
|
|
5
|
+
* (`contents.json`), `HOW-TO-VERIFY.txt` and an optional `report/` of files no
|
|
6
|
+
* record commits to. Nobody signs it: each pack is byte for byte what its signer
|
|
7
|
+
* produced, and a reader reads the packs, never the file.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* the
|
|
12
|
-
* `report/` of files no record commits to. **Nobody signs it.** Each pack in it
|
|
13
|
-
* is byte for byte the pack its signer produced, and a reader reads the packs,
|
|
14
|
-
* never the file.
|
|
9
|
+
* Trust boundary: the index is never an input to the case reading (`readCase`).
|
|
10
|
+
* Adding, removing or editing it moves no verdict, exception, note or exit code.
|
|
11
|
+
* A lie in the index is a finding here and nothing more.
|
|
15
12
|
*
|
|
16
|
-
*
|
|
17
|
-
* the files the reader was handed and returns findings with NO verdict
|
|
18
|
-
* contribution. **The index is never an input to the case reading**
|
|
19
|
-
* (`readCase`): adding, removing or editing `contents.json` moves no verdict,
|
|
20
|
-
* no exception, no note, no contribution and no exit code. An assembler can lie
|
|
21
|
-
* in every member of the index, and each lie is a finding here and nothing
|
|
22
|
-
* more; it can leave the index out, which is no finding at all.
|
|
23
|
-
*
|
|
24
|
-
* Pure: bytes and a file list in, a reading out. No filesystem, no zip, no
|
|
25
|
-
* network. How the files were listed and hashed is the caller's.
|
|
13
|
+
* Pure: bytes and a file list in, a reading out. No filesystem, zip or network.
|
|
26
14
|
*/
|
|
27
15
|
|
|
28
16
|
import { DuplicateKeyError, jcsParse } from "./verifier.js";
|
|
29
17
|
|
|
30
|
-
/** The index's dispatch key (5.6.2)
|
|
18
|
+
/** The index's dispatch key (5.6.2); an absent or unknown value makes the index unreadable. */
|
|
31
19
|
export const CASE_FILE_VERSION = "sanning.case-file/v1";
|
|
32
20
|
|
|
33
21
|
/** The index, at the root of the case file. */
|
|
@@ -40,17 +28,10 @@ export const HOW_TO_VERIFY_NAME = "HOW-TO-VERIFY.txt";
|
|
|
40
28
|
export const REPORT_README_FIRST_LINE =
|
|
41
29
|
"Not evidence: no record in this case file commits to the files in this folder.";
|
|
42
30
|
|
|
43
|
-
/**
|
|
44
|
-
* How deep `[` and `{` may nest in the index before it is refused unread. The
|
|
45
|
-
* same limit `bundle.json` is read under, so a hostile index cannot make one
|
|
46
|
-
* kernel's parser recurse where the other's does not.
|
|
47
|
-
*/
|
|
31
|
+
/** How deep `[` and `{` may nest in the index before it is refused unread (the limit `bundle.json` is read under). */
|
|
48
32
|
export const MAX_INDEX_DEPTH = 512;
|
|
49
33
|
|
|
50
|
-
/**
|
|
51
|
-
* Every finding this comparison can report, in the order it reports them
|
|
52
|
-
* (5.6.3). None of them is a verdict word and none contributes to one.
|
|
53
|
-
*/
|
|
34
|
+
/** Every finding this comparison can report, in report order (5.6.3). None is a verdict word. */
|
|
54
35
|
export const CASE_FILE_FINDING_KINDS = [
|
|
55
36
|
"case_index_unreadable",
|
|
56
37
|
"case_index_pack_missing",
|
|
@@ -63,23 +44,15 @@ export const CASE_FILE_FINDING_KINDS = [
|
|
|
63
44
|
] as const;
|
|
64
45
|
export type CaseFileFindingKind = (typeof CASE_FILE_FINDING_KINDS)[number];
|
|
65
46
|
|
|
66
|
-
//
|
|
67
|
-
// `.` or `..` (5.6.1). The class holds no newline, so `$` cannot match before
|
|
68
|
-
// one here.
|
|
47
|
+
// 1 to 128 of A-Z a-z 0-9 `.` `_` `-`, not `.` or `..` (5.6.1). No newline in the class, so `$` is safe.
|
|
69
48
|
const REF = /^[A-Za-z0-9._-]{1,128}$/;
|
|
70
49
|
|
|
71
|
-
/**
|
|
72
|
-
* Whether `ref` is a legal pack folder name in a case file. For a producer; a
|
|
73
|
-
* reader takes whatever folders it finds and names them.
|
|
74
|
-
*/
|
|
50
|
+
/** Whether `ref` is a legal pack folder name. For producers; a reader takes whatever folders it finds. */
|
|
75
51
|
export function isPackRef(ref: unknown): boolean {
|
|
76
52
|
return typeof ref === "string" && REF.test(ref) && ref !== "." && ref !== "..";
|
|
77
53
|
}
|
|
78
54
|
|
|
79
|
-
/**
|
|
80
|
-
* `HOW-TO-VERIFY.txt`, exactly (5.6.4): ASCII, LF line endings, with `name` the
|
|
81
|
-
* case file's name without `.zip`.
|
|
82
|
-
*/
|
|
55
|
+
/** `HOW-TO-VERIFY.txt`, exactly (5.6.4): ASCII, LF endings; `name` is the case file's name without `.zip`. */
|
|
83
56
|
export function howToVerifyText(name: string): string {
|
|
84
57
|
return (
|
|
85
58
|
"This case file holds evidence packs. Each pack is signed by its own key.\n" +
|
|
@@ -94,20 +67,17 @@ export function howToVerifyText(name: string): string {
|
|
|
94
67
|
"\n" +
|
|
95
68
|
` sanning-proof case ${name}\n` +
|
|
96
69
|
"\n" +
|
|
97
|
-
"Exit codes: 0 verified, 1 failed, 2 not
|
|
98
|
-
"3
|
|
99
|
-
"
|
|
70
|
+
"Exit codes: 0 verified, 1 failed, 2 incomplete (not everything could be\n" +
|
|
71
|
+
"checked), 3 verified sandbox evidence (records signed environment dev),\n" +
|
|
72
|
+
"4 could not run (a bad flag, or an input that is not a case).\n" +
|
|
100
73
|
"\n" +
|
|
101
74
|
"To also look each witness up on the public record, add --gateways <url,url>.\n" +
|
|
102
|
-
"The lookup is reported on
|
|
103
|
-
"
|
|
75
|
+
"The lookup is reported on the witness line. A lookup that does not settle\n" +
|
|
76
|
+
"changes nothing; a witness that does not match fails the case, exit 1.\n"
|
|
104
77
|
);
|
|
105
78
|
}
|
|
106
79
|
|
|
107
|
-
/**
|
|
108
|
-
* One way the index and the drop disagree, as data. What the index SAYS
|
|
109
|
-
* against what the folder HOLDS, and never a statement about any party.
|
|
110
|
-
*/
|
|
80
|
+
/** One way the index and the folder disagree: what the index says against what the folder holds, never a statement about a party. */
|
|
111
81
|
export interface CaseFileFinding {
|
|
112
82
|
kind: CaseFileFindingKind;
|
|
113
83
|
/** The pack folder name, for the two pack kinds. */
|
|
@@ -118,36 +88,24 @@ export interface CaseFileFinding {
|
|
|
118
88
|
listedSha256?: string;
|
|
119
89
|
/** What the file in the folder hashes to, for `case_index_file_differs`. */
|
|
120
90
|
sha256?: string;
|
|
121
|
-
/**
|
|
122
|
-
* Why the index is unreadable, or the assembler's reason a pack is absent,
|
|
123
|
-
* for `case_index_unreadable` and `case_index_names_absent`.
|
|
124
|
-
*/
|
|
91
|
+
/** Why the index is unreadable, or the assembler's reason a pack is absent. */
|
|
125
92
|
reason?: string;
|
|
126
|
-
/**
|
|
127
|
-
* The key the assembler says the absent pack would be signed by, as the index
|
|
128
|
-
* writes it, for `case_index_names_absent`.
|
|
129
|
-
*/
|
|
93
|
+
/** The key the assembler says the absent pack is signed by, for `case_index_names_absent`. */
|
|
130
94
|
publicKey?: string;
|
|
131
95
|
}
|
|
132
96
|
|
|
133
97
|
export interface CaseFileIndexReading {
|
|
134
98
|
/** `absent` (no index was handed in), `unreadable` or `read`. */
|
|
135
99
|
index: "absent" | "unreadable" | "read";
|
|
136
|
-
/**
|
|
137
|
-
* The index's `case_id`, a label the assembler wrote and nothing is compared
|
|
138
|
-
* with, or `null` where the index was not read.
|
|
139
|
-
*/
|
|
100
|
+
/** The index's `case_id`, a label nothing is compared with; `null` where the index was not read. */
|
|
140
101
|
caseId: string | null;
|
|
141
102
|
findings: CaseFileFinding[];
|
|
142
103
|
}
|
|
143
104
|
|
|
144
105
|
/**
|
|
145
106
|
* Whether `[` and `{` outside strings ever nest deeper than `limit`. One pass,
|
|
146
|
-
* no recursion
|
|
147
|
-
*
|
|
148
|
-
* character of that code, which is the same answer for UTF-8: the six
|
|
149
|
-
* characters it reads are single bytes and no multi-byte sequence holds one.
|
|
150
|
-
* The CLI's `bundle.json` reader uses it too.
|
|
107
|
+
* no recursion. Bytes are read as character codes, which is exact for UTF-8
|
|
108
|
+
* because the six characters it reads never occur inside a multi-byte sequence.
|
|
151
109
|
*/
|
|
152
110
|
export function jsonDepthExceeds(text: string | Uint8Array, limit: number): boolean {
|
|
153
111
|
let depth = 0;
|
|
@@ -226,13 +184,12 @@ interface ReadIndex {
|
|
|
226
184
|
}
|
|
227
185
|
|
|
228
186
|
/**
|
|
229
|
-
* The index, read under the JSON rule 5.4.2
|
|
230
|
-
*
|
|
231
|
-
*
|
|
187
|
+
* The index, read under the JSON rule of 5.4.2 and checked for every required
|
|
188
|
+
* member and its type. JCS is not required and unknown members are ignored
|
|
189
|
+
* (5.6.2). The first problem found is the reason.
|
|
232
190
|
*/
|
|
233
191
|
function readIndex(raw: Uint8Array): ReadIndex {
|
|
234
|
-
// Scanned as
|
|
235
|
-
// in the same order whatever it holds.
|
|
192
|
+
// Scanned as bytes, before any decode.
|
|
236
193
|
if (jsonDepthExceeds(raw, MAX_INDEX_DEPTH)) {
|
|
237
194
|
throw new Unreadable(`nests deeper than ${MAX_INDEX_DEPTH} levels`);
|
|
238
195
|
}
|
|
@@ -240,8 +197,7 @@ function readIndex(raw: Uint8Array): ReadIndex {
|
|
|
240
197
|
try {
|
|
241
198
|
value = jcsParse(raw);
|
|
242
199
|
} catch (e) {
|
|
243
|
-
// Named
|
|
244
|
-
// reads as whichever copy came last (I-JSON, proof#144).
|
|
200
|
+
// Named: a duplicated member name would otherwise read as whichever copy came last (I-JSON).
|
|
245
201
|
if (e instanceof DuplicateKeyError) throw new Unreadable(e.message);
|
|
246
202
|
throw new Unreadable("not readable as JSON");
|
|
247
203
|
}
|
|
@@ -277,12 +233,7 @@ function readIndex(raw: Uint8Array): ReadIndex {
|
|
|
277
233
|
return { caseId, packs: listedPacks, absent: listedAbsent, report };
|
|
278
234
|
}
|
|
279
235
|
|
|
280
|
-
/**
|
|
281
|
-
* The reading of an index the caller could not hold at all (it is over the
|
|
282
|
-
* run's whole-read total, or it cannot be read): one `case_index_unreadable`
|
|
283
|
-
* finding with the caller's `reason`, and no comparison. The index is never an
|
|
284
|
-
* input to the verdict, so a file that cannot be read keeps nothing from it.
|
|
285
|
-
*/
|
|
236
|
+
/** The reading of an index the caller could not hold (over the run's read total, or unreadable): one `case_index_unreadable` finding, no comparison. */
|
|
286
237
|
export function unreadableCaseFileIndex(reason: string): CaseFileIndexReading {
|
|
287
238
|
return {
|
|
288
239
|
index: "unreadable",
|
|
@@ -293,9 +244,8 @@ export function unreadableCaseFileIndex(reason: string): CaseFileIndexReading {
|
|
|
293
244
|
|
|
294
245
|
/**
|
|
295
246
|
* Every path the index lists (each pack's files and `report`), or `null` where
|
|
296
|
-
* the index is unreadable. A caller
|
|
297
|
-
*
|
|
298
|
-
* cannot turn a real mismatch into `too_large`.
|
|
247
|
+
* the index is unreadable. A caller with a hashing budget charges these first,
|
|
248
|
+
* so an unlisted file cannot turn a real mismatch into `too_large`.
|
|
299
249
|
*/
|
|
300
250
|
export function caseFileListedPaths(index: Uint8Array): ReadonlySet<string> | null {
|
|
301
251
|
let read: ReadIndex;
|
|
@@ -315,22 +265,15 @@ export function caseFileListedPaths(index: Uint8Array): ReadonlySet<string> | nu
|
|
|
315
265
|
* Compare a case file's index with the folder it sits at the root of.
|
|
316
266
|
*
|
|
317
267
|
* `index` is the bytes of `contents.json`, or `null` where the folder holds
|
|
318
|
-
* none. `files` is every regular file
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
* `case_index_file_too_large` where
|
|
322
|
-
*
|
|
323
|
-
* `packs/` and `report/` are compared, so a caller may list the rest or not. A
|
|
324
|
-
* folder with no index is a folder of packs, with no finding.
|
|
325
|
-
*
|
|
326
|
-
* Findings come in the order of `CASE_FILE_FINDING_KINDS`, and within a kind in
|
|
327
|
-
* the order of their `ref` or `path` by UTF-8 bytes (the order a caller charges
|
|
328
|
-
* its hashing budget in is another, UTF-16 code units: evidence-bundle 5.6.3);
|
|
329
|
-
* `case_index_names_absent`
|
|
330
|
-
* is in the index's own order, one per `absent[]` entry. An unreadable index is
|
|
331
|
-
* one finding and no other comparison runs.
|
|
268
|
+
* none (a folder of packs, no finding). `files` is every regular file, as
|
|
269
|
+
* `{ path, sha256 }` with a `/`-separated path from the root, or
|
|
270
|
+
* `{ path, too_large: true }` for a file left unhashed over the caller's cap
|
|
271
|
+
* (reported as `case_index_file_too_large` where listed). Only files under
|
|
272
|
+
* `packs/` and `report/` are compared.
|
|
332
273
|
*
|
|
333
|
-
*
|
|
274
|
+
* Findings follow `CASE_FILE_FINDING_KINDS`, and within a kind sort by UTF-8
|
|
275
|
+
* bytes of `ref` or `path` (5.6.3); `case_index_names_absent` keeps the index's
|
|
276
|
+
* order. An unreadable index is one finding and no other comparison runs.
|
|
334
277
|
*/
|
|
335
278
|
export function readCaseFileIndex(
|
|
336
279
|
index: Uint8Array | null,
|
|
@@ -351,9 +294,7 @@ export function readCaseFileIndex(
|
|
|
351
294
|
};
|
|
352
295
|
}
|
|
353
296
|
|
|
354
|
-
// `null
|
|
355
|
-
// `{ path, too_large: true }`): neither a match nor absent, reported as
|
|
356
|
-
// neither.
|
|
297
|
+
// `null`: present but too large to hash; neither a match nor absent.
|
|
357
298
|
const here = new Map<string, string | null>();
|
|
358
299
|
for (const entry of files) {
|
|
359
300
|
const path = isObject(entry) ? own(entry, "path") : undefined;
|
|
@@ -365,8 +306,7 @@ export function readCaseFileIndex(
|
|
|
365
306
|
}
|
|
366
307
|
|
|
367
308
|
const listedRefs = new Set(read.packs.map(([ref]) => ref));
|
|
368
|
-
// The first listing of a path wins
|
|
369
|
-
// pack's files in order, then report/. A path listed twice is compared once.
|
|
309
|
+
// The first listing of a path wins (packs in order, then report/).
|
|
370
310
|
const listed = new Map<string, string>();
|
|
371
311
|
for (const [, packFiles] of read.packs) {
|
|
372
312
|
for (const [path, digest] of packFiles) if (!listed.has(path)) listed.set(path, digest);
|
|
@@ -435,11 +375,7 @@ export function readCaseFileIndex(
|
|
|
435
375
|
return { index: "read", caseId: read.caseId, findings };
|
|
436
376
|
}
|
|
437
377
|
|
|
438
|
-
/**
|
|
439
|
-
* The house wording for one finding: what the index says against what the
|
|
440
|
-
* folder holds, and the assembler's own statement for an absent pack. Never a
|
|
441
|
-
* sentence about a party. Both kernels return this text.
|
|
442
|
-
*/
|
|
378
|
+
/** The house wording for one finding. Never a sentence about a party. */
|
|
443
379
|
export function renderCaseFileFinding(finding: CaseFileFinding): string {
|
|
444
380
|
switch (finding.kind) {
|
|
445
381
|
case "case_index_unreadable":
|