@sanning/proof 0.19.0 → 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.
Files changed (98) hide show
  1. package/README.md +30 -29
  2. package/dist/agent-record.d.ts +62 -0
  3. package/dist/agent-record.js +527 -0
  4. package/dist/agent-schemas.generated.d.ts +15 -0
  5. package/dist/agent-schemas.generated.js +5759 -0
  6. package/dist/case-file.d.ts +32 -89
  7. package/dist/case-file.js +41 -93
  8. package/dist/case.d.ts +343 -810
  9. package/dist/case.js +693 -1151
  10. package/dist/chain.d.ts +19 -47
  11. package/dist/chain.js +78 -205
  12. package/dist/claims.d.ts +211 -0
  13. package/dist/claims.js +1081 -0
  14. package/dist/cli.d.ts +45 -68
  15. package/dist/cli.js +618 -1022
  16. package/dist/compose.js +23 -65
  17. package/dist/crypto.d.ts +7 -8
  18. package/dist/crypto.js +60 -147
  19. package/dist/declaration.d.ts +68 -194
  20. package/dist/declaration.js +175 -518
  21. package/dist/environment.d.ts +9 -30
  22. package/dist/environment.js +17 -66
  23. package/dist/evidence.d.ts +119 -160
  24. package/dist/evidence.js +744 -1103
  25. package/dist/exit-codes.d.ts +11 -27
  26. package/dist/exit-codes.js +24 -59
  27. package/dist/file-match.d.ts +33 -79
  28. package/dist/file-match.js +33 -69
  29. package/dist/grading.d.ts +98 -135
  30. package/dist/grading.js +224 -380
  31. package/dist/handoff.d.ts +62 -160
  32. package/dist/handoff.js +153 -393
  33. package/dist/index.d.ts +14 -10
  34. package/dist/index.js +60 -167
  35. package/dist/json-schema.d.ts +29 -0
  36. package/dist/json-schema.js +298 -0
  37. package/dist/merkle.js +16 -28
  38. package/dist/message-parts.d.ts +56 -212
  39. package/dist/message-parts.js +142 -502
  40. package/dist/side-content.d.ts +11 -22
  41. package/dist/side-content.js +22 -65
  42. package/dist/types.d.ts +17 -0
  43. package/dist/types.js +19 -5
  44. package/dist/verifier.d.ts +40 -114
  45. package/dist/verifier.js +196 -535
  46. package/dist/witness-cache.d.ts +24 -45
  47. package/dist/witness-cache.js +38 -81
  48. package/dist/witness-lookup.d.ts +36 -57
  49. package/dist/witness-lookup.js +89 -139
  50. package/dist/zip-reader.d.ts +18 -33
  51. package/dist/zip-reader.js +19 -34
  52. package/package.json +11 -3
  53. package/src/agent-record.ts +608 -0
  54. package/src/agent-schemas.generated.ts +5764 -0
  55. package/src/case-file.ts +45 -109
  56. package/src/case.ts +848 -1322
  57. package/src/chain.ts +85 -224
  58. package/src/claims.ts +1253 -0
  59. package/src/cli.ts +714 -1176
  60. package/src/compose.ts +33 -79
  61. package/src/crypto.ts +60 -147
  62. package/src/declaration.ts +219 -656
  63. package/src/environment.ts +17 -65
  64. package/src/evidence.ts +958 -1381
  65. package/src/exit-codes.ts +24 -63
  66. package/src/file-match.ts +40 -89
  67. package/src/grading.ts +287 -421
  68. package/src/handoff.ts +175 -427
  69. package/src/index.ts +132 -177
  70. package/src/json-schema.ts +295 -0
  71. package/src/merkle.ts +16 -28
  72. package/src/message-parts.ts +155 -533
  73. package/src/side-content.ts +25 -68
  74. package/src/types.ts +55 -36
  75. package/src/verifier.ts +202 -526
  76. package/src/witness-cache.ts +45 -94
  77. package/src/witness-lookup.ts +107 -177
  78. package/src/zip-reader.ts +20 -38
  79. package/vectors/README.md +52 -34
  80. package/vectors/agent-record.json +2175 -0
  81. package/vectors/case-file-index.json +1 -1
  82. package/vectors/case-split.json +2 -2
  83. package/vectors/chain-reading.json +3 -3
  84. package/vectors/file-match.json +285 -148
  85. package/vectors/jcs-reader.json +6 -6
  86. package/vectors/pack-grading.json +102 -25
  87. package/vectors/sandbox-evidence.json +597 -580
  88. package/vectors/stamp-on-chain.json +18 -18
  89. package/vectors/witness-cache.json +49 -49
  90. package/dist/agent-proof.d.ts +0 -28
  91. package/dist/agent-proof.js +0 -226
  92. package/dist/run-adapters.d.ts +0 -10
  93. package/dist/run-adapters.js +0 -35
  94. package/dist/run.d.ts +0 -209
  95. package/dist/run.js +0 -1405
  96. package/src/agent-proof.ts +0 -264
  97. package/src/run-adapters.ts +0 -44
  98. package/src/run.ts +0 -1598
package/src/case-file.ts CHANGED
@@ -1,33 +1,21 @@
1
1
  /**
2
- * The case file's index, compared with the drop it describes (A25).
2
+ * A case file's index compared with the drop it describes (evidence-bundle §5.6).
3
3
  *
4
- * The TypeScript half of `src/sanning_proof/case_file.py`. Both kernels are
5
- * held to one table, `vectors/case-file-index.json`, so neither can pass by
6
- * agreeing with the other, and `cross-kernel/run_case_cli.sh` holds the two
7
- * commands to the same `case_file` output.
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
- * WHAT A CASE FILE IS (evidence-bundle.md 5.6 v1.20). A ZIP archive holding one
10
- * case's packs, each unpacked under `packs/<ref>/`, an index (`contents.json`),
11
- * the one command that checks them (`HOW-TO-VERIFY.txt`) and an optional
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
- * WHAT THIS MODULE DOES, AND WHAT IT MUST NEVER DO. It compares the index with
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). An absent or unknown value makes the index unreadable. */
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
- // `<ref>`: 1 to 128 characters from A-Z, a-z, 0-9, `.`, `_` and `-`, and not
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 readable or a usage error,\n" +
98
- "3 could not be settled (partial, pending, incomplete, not found), 4 mixed,\n" +
99
- "5 sandbox (records signed environment dev, not production evidence).\n" +
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 its own line and never changes the verdict word.\n" +
103
- "With it, a witness that does not match exits 1.\n"
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; the same scan as `json_depth_exceeds` in the Python twin, so
147
- * both kernels refuse the same text. Given bytes, it reads each byte as the
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 rule 7 pins, and checked for every
230
- * REQUIRED member and its type. JCS is not required and an unknown member is
231
- * ignored (5.6.2). The FIRST problem is the reason.
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 BYTES, before any decode, so both kernels refuse the same index
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, because an index that lists one pack twice under one member name
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 that charges a budget for hashing charges
297
- * these files first, so a file the index does not list, which needs no digest,
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 in the folder, as `{ path, sha256 }`
319
- * with `path` from the folder's root, `/`-separated, or `{ path, too_large: true }`
320
- * for a file the caller did not hash because it is over its cap (reported as
321
- * `case_index_file_too_large` where the index lists it, never as a match or as
322
- * absent); only the files under
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
- * Nothing here is a verdict, and nothing here is ever an input to `readCase`.
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` is a file that is here and was too large to hash (a file entry of
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, in index order: packs in order, each
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":