@sanning/proof 0.12.0 → 0.13.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 (48) hide show
  1. package/README.md +25 -12
  2. package/dist/agent-proof.d.ts +2 -2
  3. package/dist/agent-proof.js +4 -4
  4. package/dist/case-file.d.ts +126 -0
  5. package/dist/case-file.js +349 -0
  6. package/dist/case.d.ts +173 -26
  7. package/dist/case.js +469 -123
  8. package/dist/cli.d.ts +38 -5
  9. package/dist/cli.js +557 -208
  10. package/dist/crypto.d.ts +1 -1
  11. package/dist/crypto.js +5 -1
  12. package/dist/declaration.d.ts +58 -52
  13. package/dist/declaration.js +256 -33
  14. package/dist/evidence.d.ts +2 -0
  15. package/dist/evidence.js +134 -19
  16. package/dist/exit-codes.d.ts +17 -0
  17. package/dist/exit-codes.js +50 -0
  18. package/dist/grading.d.ts +28 -13
  19. package/dist/grading.js +90 -31
  20. package/dist/index.d.ts +10 -7
  21. package/dist/index.js +13 -6
  22. package/dist/message-parts.d.ts +11 -9
  23. package/dist/message-parts.js +11 -9
  24. package/dist/types.d.ts +1 -0
  25. package/dist/verifier.d.ts +9 -2
  26. package/dist/verifier.js +28 -12
  27. package/dist/zip-reader.d.ts +50 -0
  28. package/dist/zip-reader.js +220 -0
  29. package/package.json +2 -2
  30. package/src/agent-proof.ts +4 -4
  31. package/src/case-file.ts +419 -0
  32. package/src/case.ts +586 -132
  33. package/src/cli.ts +651 -206
  34. package/src/crypto.ts +5 -1
  35. package/src/declaration.ts +248 -52
  36. package/src/evidence.ts +142 -21
  37. package/src/exit-codes.ts +54 -0
  38. package/src/grading.ts +109 -52
  39. package/src/index.ts +58 -2
  40. package/src/message-parts.ts +11 -9
  41. package/src/types.ts +5 -0
  42. package/src/verifier.ts +35 -13
  43. package/src/zip-reader.ts +229 -0
  44. package/vectors/README.md +23 -5
  45. package/vectors/case-file-index.json +668 -0
  46. package/vectors/case-split.json +218 -9
  47. package/vectors/pack-grading.json +68 -4
  48. package/vectors/stamp-on-chain.json +3 -3
@@ -119,7 +119,7 @@
119
119
  // last-write-wins in both languages, so a second record under a declared id
120
120
  // was graded by neither rule 2 nor rule 7 and was never reported as surplus.
121
121
  // FIRST in bundle order is now the record every other rule grades, and every
122
- // record after it under the same id is a `duplicate-event-id` deviation.
122
+ // record after it under the same id is a `duplicate_event_id` deviation.
123
123
  //
124
124
  // MINIMUM DISCLOSURE IS NOT A DEVIATION. A claimed side whose `event_bytes`
125
125
  // the pack withholds was graded "not a conformant hand-off record", so an
@@ -132,10 +132,130 @@
132
132
  // `src/sanning_proof/canonicalize.py` is the twin (proof#140 gate 5).
133
133
  import { hexToBytes } from "./crypto.js";
134
134
  import { jcsParse } from "./verifier.js";
135
- import { isConformantHandoffRecord } from "./handoff.js";
135
+ import { isConformantHandoffRecord, isHandoffNamespace } from "./handoff.js";
136
136
  import { hasSideContent, inBodyContentOffered } from "./side-content.js";
137
- /** The declaration schema this reader knows. */
137
+ /** The first declaration schema, which every earlier reader knows. */
138
138
  export const DECLARATION_VERSION = "sanning.declaration/v1";
139
+ /**
140
+ * v1 plus a signed `records[].role` on every record (evidence-bundle.md
141
+ * 5.4.1 v1.20, A21). A reader that knows only v1 reads a v2 declaration as one
142
+ * it cannot read, which is the safe direction.
143
+ */
144
+ export const DECLARATION_VERSION_V2 = "sanning.declaration/v2";
145
+ /** Every declaration schema this reader knows, in the order they were added. */
146
+ export const DECLARATION_VERSIONS = [DECLARATION_VERSION, DECLARATION_VERSION_V2];
147
+ /**
148
+ * What a v2 declaration may sign a record IS, compared exactly: `handoff`, a
149
+ * record 4.1 governs that the pack claims as a side in `handoffs[]`, or
150
+ * `step`, any other record.
151
+ */
152
+ export const DECLARATION_ROLES = ["handoff", "step"];
153
+ /** The three states a declaration check can reach, from the §3 vocabulary. */
154
+ export const DECLARATION_VERDICTS = ["verified", "failed", "partial"];
155
+ export const DECLARATION_DEVIATION_KINDS = [
156
+ /** The declaration is written to this version and is not readable as one. */
157
+ "declaration_malformed",
158
+ /** A record the declaration expects is not in the pack. */
159
+ "declared_absent",
160
+ /** A record in the pack the declaration never expected. */
161
+ "undeclared_present",
162
+ /**
163
+ * A record declared `content_withheld` whose bytes the pack supplies INSIDE
164
+ * its own signed body, in `events[].content`.
165
+ */
166
+ "withheld_but_disclosed",
167
+ /** The pack carries one `event_id` on two or more records. */
168
+ "duplicate_event_id",
169
+ /** A claimed hand-off names a record the declaration does not declare. */
170
+ "pair_record_undeclared",
171
+ /**
172
+ * A claimed hand-off names a record that is not a conformant hand-off
173
+ * record of the side it is claimed on (envelope-spec.md 4.1).
174
+ */
175
+ "pair_record_not_a_handoff",
176
+ /**
177
+ * Rule 9 (v1.20, A21), `sanning.declaration/v2` only. A record declared
178
+ * `handoff` that no `handoffs[]` entry names as a side.
179
+ */
180
+ "role_handoff_unclaimed",
181
+ /** A record a `handoffs[]` entry names as a side, declared `step`. */
182
+ "role_step_claimed",
183
+ /**
184
+ * A record declared `step` that 4.1's scope test puts in scope: its `event`
185
+ * carries `counterparty_public_key`, or its folded `event_type` has a
186
+ * `handoff` segment. The holder signed that a hand-off record is not one.
187
+ */
188
+ "role_step_governed",
189
+ /**
190
+ * A record declared `step` whose supplied `event_bytes` this reader cannot
191
+ * read under rule 7's parser rule.
192
+ */
193
+ "role_step_unreadable",
194
+ ];
195
+ /**
196
+ * Every way a pack and the UNSIGNED content handed in beside it fail to line
197
+ * up, as a closed set. Neither of these is a deviation, and neither of them
198
+ * fails (proof#147, ratified by Phil 2026-09-22).
199
+ *
200
+ * WHY THEY ARE NOT DEVIATIONS. 5.4 grades a pack's declaration partly against
201
+ * content the reader was HANDED, and that content is not signed by anybody.
202
+ * `events[].content` rides inside `body`, so the wrapper signature and
203
+ * `body_hash` cover it and what it says about the holder is the holder's own
204
+ * word. The side-content map is the other channel, and whoever assembles a
205
+ * case fills it in: an accuser could put an intact pack into
206
+ * `pack_declaration_deviates`, which contributed `failed`, WITH NO KEY AT
207
+ * ALL, by dropping the bytes of a record the pack declared `disclosed` or by
208
+ * adding bytes for one it declared `content_withheld`. The pack was intact,
209
+ * its wrapper verified, and its holder had done nothing.
210
+ *
211
+ * This is the mirror of proof#143. There a counterparty makes itself look
212
+ * better by choosing what it hands over; here an accuser makes a counterparty
213
+ * look worse the same way. Both now read as what the READER established, so
214
+ * `failed` stays for what a signature covers.
215
+ *
216
+ * WHAT A HOSTILE HOLDER GAINS FROM THE WEAKER WORD: nothing, because a
217
+ * cheaper move already dominates it. Declaring a record `content_withheld` is
218
+ * legal and grades `verified`, which is strictly better than the `incomplete`
219
+ * these carry.
220
+ */
221
+ export const DECLARATION_CONTENT_KINDS = [
222
+ /**
223
+ * A record declared `disclosed` whose bytes reached this reader from
224
+ * neither channel. ALWAYS content-dependent: 5.4.1 lets those bytes travel
225
+ * beside the pack as `logs/<event_id>.json`, so "no bytes arrived" is a
226
+ * fact about what the READER was handed and never a fact about what the
227
+ * holder supplied. No signature covers the absence of a file nobody signed.
228
+ */
229
+ "content_dependent_withholding",
230
+ /**
231
+ * A record declared `content_withheld` whose bytes reached this reader ONLY
232
+ * from the unsigned side-content map. The pack's own body supplies none, so
233
+ * the holder committed to no such disclosure. The in-body half of the same
234
+ * rule is the `withheld_but_disclosed` DEVIATION and still fails.
235
+ */
236
+ "content_dependent_disclosure",
237
+ ];
238
+ /** Everything this check reports that is NOT a deviation, as a closed set. */
239
+ export const DECLARATION_NOTE_KINDS = [
240
+ /**
241
+ * Every record declared `disclosed` supplied no bytes, so nothing was
242
+ * selected: the bytes did not travel with the pack.
243
+ */
244
+ "disclosure_did_not_travel",
245
+ /**
246
+ * A claimed hand-off side whose `event_bytes` the pack withholds. The reader
247
+ * cannot see what the record is either way, so it is neither a conformant
248
+ * hand-off record nor a non-conformant one.
249
+ */
250
+ "pair_record_withheld",
251
+ /**
252
+ * A record a v2 declaration signs `step` whose `event_bytes` the pack
253
+ * withholds (rule 9, v1.20). Its role cannot be checked in either direction,
254
+ * so it is a note; the pack grade still holds the pack at `incomplete`,
255
+ * because its payload binding is undetermined.
256
+ */
257
+ "role_step_withheld",
258
+ ];
139
259
  /**
140
260
  * The record committed inside one `events[]` entry, or `null`.
141
261
  *
@@ -250,8 +370,13 @@ const isId = (v) => typeof v === "string" && v.length > 0;
250
370
  * and a reader that names one defect in a claim with four sends its holder
251
371
  * round the loop four times.
252
372
  */
253
- function readDeclaration(declaration) {
373
+ function readDeclaration(declaration, version = DECLARATION_VERSION) {
254
374
  const reasons = [];
375
+ // Under v2 every `records[]` entry must also carry `role`, exactly `handoff`
376
+ // or `step` (5.4.1 v1.20, A21); under v1 the member is not read at all, so a
377
+ // v1 declaration carrying `role` reads exactly as the same object without it.
378
+ const roles = [];
379
+ const v2 = version === DECLARATION_VERSION_V2;
255
380
  const note = (r) => reasons.push(r);
256
381
  if (!isId(declaration["case_id"]))
257
382
  note("case_id is not a non-empty string");
@@ -289,12 +414,21 @@ function readDeclaration(declaration) {
289
414
  note(`records[${i}] (${eventId}) has no disclosure of "disclosed" or "content_withheld"`);
290
415
  continue;
291
416
  }
417
+ // Compared exactly: `Handoff`, `hand-off` and `step ` are other values,
418
+ // and a non-string never equals one.
419
+ const role = r["role"];
420
+ if (v2 && !(role === "handoff" || role === "step")) {
421
+ note(`records[${i}] (${eventId}) has no role of "handoff" or "step"`);
422
+ continue;
423
+ }
292
424
  if (seen.has(eventId)) {
293
425
  note(`records[${i}] declares ${eventId} a second time`);
294
426
  continue;
295
427
  }
296
428
  seen.add(eventId);
297
429
  records.push({ eventId, withheld: disclosure === "content_withheld" });
430
+ if (v2)
431
+ roles.push({ eventId, role: role });
298
432
  }
299
433
  }
300
434
  const pairs = [];
@@ -321,7 +455,7 @@ function readDeclaration(declaration) {
321
455
  pairs.push({ sent: sent, received: received });
322
456
  }
323
457
  }
324
- return reasons.length > 0 ? { ok: false, reasons } : { ok: true, read: { records, pairs } };
458
+ return reasons.length > 0 ? { ok: false, reasons } : { ok: true, read: { records, pairs, roles } };
325
459
  }
326
460
  // ---- The check ---------------------------------------------------------------
327
461
  const noDeclaration = () => ({
@@ -332,7 +466,40 @@ const noDeclaration = () => ({
332
466
  present: false,
333
467
  declared: [],
334
468
  declaredWithheld: [],
469
+ version: null,
470
+ roles: [],
335
471
  });
472
+ /**
473
+ * The `declaration_version` of an object this reader knows, or `null`: a
474
+ * declaration that is not an object, an absent or `null` version and a version
475
+ * outside `DECLARATION_VERSIONS` are each a claim this reader cannot read.
476
+ */
477
+ function knownVersion(declaration) {
478
+ if (!isObject(declaration))
479
+ return null;
480
+ const version = declaration["declaration_version"];
481
+ return version === DECLARATION_VERSION || version === DECLARATION_VERSION_V2 ? version : null;
482
+ }
483
+ /**
484
+ * Rule 9 over one record the pack holds: the deviation or note it earns, or
485
+ * `null` where the signed role agrees with the pack and the record. The first
486
+ * of the four deviations that applies, in the order 5.4.2 lists them. Whether
487
+ * 4.1 governs the record is `isHandoffNamespace`, CALLED, never restated.
488
+ */
489
+ function roleReading(p, role, claimed) {
490
+ if (role === "handoff")
491
+ return claimed ? null : "role_handoff_unclaimed";
492
+ if (claimed)
493
+ return "role_step_claimed";
494
+ if (!p.bytesSupplied)
495
+ return "role_step_withheld";
496
+ if (p.record === null)
497
+ return "role_step_unreadable";
498
+ if (isHandoffNamespace({ publicKey: p.publicKey, record: p.record, ref: p.eventId })) {
499
+ return "role_step_governed";
500
+ }
501
+ return null;
502
+ }
336
503
  /**
337
504
  * Check a pack against the declaration inside its body.
338
505
  *
@@ -368,22 +535,29 @@ export function checkDeclaration(bundle, content) {
368
535
  // a deleted one, a null one, and a `declaration` that is a string, an array
369
536
  // or null. Each of those hides the evidence of a trim inside a signed body,
370
537
  // so none of them may reach a clean pack verdict.
371
- if (!isObject(declaration) || declaration["declaration_version"] !== DECLARATION_VERSION) {
538
+ const version = knownVersion(declaration);
539
+ if (version === null) {
372
540
  return { ...noDeclaration(), present: true };
373
541
  }
374
- const read = readDeclaration(declaration);
542
+ const read = readDeclaration(declaration, version);
375
543
  if (!read.ok) {
376
544
  return {
377
545
  verdict: "failed",
378
- deviations: read.reasons.map((reason) => ({ kind: "declaration-malformed", reason })),
546
+ deviations: read.reasons.map((reason) => ({
547
+ kind: "declaration_malformed",
548
+ reason,
549
+ version,
550
+ })),
379
551
  contentFindings: [],
380
552
  notes: [],
381
553
  present: true,
382
554
  declared: [],
383
555
  declaredWithheld: [],
556
+ version,
557
+ roles: [],
384
558
  };
385
559
  }
386
- const { records, pairs } = read.read;
560
+ const { records, pairs, roles } = read.read;
387
561
  const declared = records.map((r) => r.eventId);
388
562
  const declaredSet = new Set(declared);
389
563
  const pack = packRecords(bundle, content);
@@ -404,7 +578,7 @@ export function checkDeclaration(bundle, content) {
404
578
  // declaration exists: nothing else in the pack can see it.
405
579
  for (const r of records) {
406
580
  if (!byId.has(r.eventId))
407
- deviations.push({ kind: "declared-absent", eventId: r.eventId });
581
+ deviations.push({ kind: "declared_absent", eventId: r.eventId });
408
582
  }
409
583
  // Present and undeclared, and the pack carrying one id twice.
410
584
  //
@@ -421,13 +595,13 @@ export function checkDeclaration(bundle, content) {
421
595
  const seenInPack = new Set();
422
596
  for (const p of pack) {
423
597
  if (p.nameable && seenInPack.has(p.eventId)) {
424
- deviations.push({ kind: "duplicate-event-id", eventId: p.eventId });
598
+ deviations.push({ kind: "duplicate_event_id", eventId: p.eventId });
425
599
  continue;
426
600
  }
427
601
  if (p.nameable)
428
602
  seenInPack.add(p.eventId);
429
603
  if (!p.nameable || !declaredSet.has(p.eventId)) {
430
- deviations.push({ kind: "undeclared-present", eventId: p.eventId });
604
+ deviations.push({ kind: "undeclared_present", eventId: p.eventId });
431
605
  }
432
606
  }
433
607
  // The bytes that did not travel. Over the records declared `disclosed` that
@@ -441,7 +615,7 @@ export function checkDeclaration(bundle, content) {
441
615
  .filter((p) => p !== undefined);
442
616
  const nothingTravelled = disclosedHeld.length > 0 && !disclosedHeld.some((p) => p.disclosed);
443
617
  const notes = nothingTravelled
444
- ? [{ kind: "disclosure-did-not-travel", count: disclosedHeld.length }]
618
+ ? [{ kind: "disclosure_did_not_travel", count: disclosedHeld.length }]
445
619
  : [];
446
620
  // The two disclosure rules, in declaration order, over the records the pack
447
621
  // actually holds: an absent record is already named above, and naming it
@@ -467,14 +641,14 @@ export function checkDeclaration(bundle, content) {
467
641
  // the pack, so no pack can prove it supplied them and no reader can
468
642
  // prove it did not: an accuser holding a twenty-file drop reaches this
469
643
  // reading by deleting one file.
470
- contentFindings.push({ kind: "content-dependent-withholding", eventId: r.eventId });
644
+ contentFindings.push({ kind: "content_dependent_withholding", eventId: r.eventId });
471
645
  }
472
646
  else if (r.withheld && p.disclosed) {
473
647
  if (p.disclosedInBody) {
474
- deviations.push({ kind: "withheld-but-disclosed", eventId: r.eventId });
648
+ deviations.push({ kind: "withheld_but_disclosed", eventId: r.eventId });
475
649
  }
476
650
  else {
477
- contentFindings.push({ kind: "content-dependent-disclosure", eventId: r.eventId });
651
+ contentFindings.push({ kind: "content_dependent_disclosure", eventId: r.eventId });
478
652
  }
479
653
  }
480
654
  }
@@ -504,7 +678,7 @@ export function checkDeclaration(bundle, content) {
504
678
  if (id === null)
505
679
  continue;
506
680
  if (!declaredSet.has(id)) {
507
- deviations.push({ kind: "pair-record-undeclared", eventId: id, side, pairIndex: i });
681
+ deviations.push({ kind: "pair_record_undeclared", eventId: id, side, pairIndex: i });
508
682
  continue;
509
683
  }
510
684
  const p = byId.get(id);
@@ -512,13 +686,39 @@ export function checkDeclaration(bundle, content) {
512
686
  continue;
513
687
  const reading = sideReading(p, side);
514
688
  if (reading === "withheld") {
515
- notes.push({ kind: "pair-record-withheld", eventId: id, side, pairIndex: i });
689
+ notes.push({ kind: "pair_record_withheld", eventId: id, side, pairIndex: i });
516
690
  }
517
691
  else if (reading === "not-a-handoff") {
518
- deviations.push({ kind: "pair-record-not-a-handoff", eventId: id, side, pairIndex: i });
692
+ deviations.push({ kind: "pair_record_not_a_handoff", eventId: id, side, pairIndex: i });
519
693
  }
520
694
  }
521
695
  }
696
+ // Rule 9 (5.4.2 v1.20, A21), `sanning.declaration/v2` only: a record's
697
+ // signed role against the pack and against the record. Its deviations follow
698
+ // every deviation rules 1 to 8 report, and its note follows every other
699
+ // note, both in `records[]` order. It reads the signed body alone and NEVER
700
+ // the side-content map, so no party that signed nothing can create, move or
701
+ // remove one of its findings.
702
+ if (roles.length > 0) {
703
+ const claimed = new Set();
704
+ for (const pair of pairs) {
705
+ if (pair.sent !== null)
706
+ claimed.add(pair.sent);
707
+ if (pair.received !== null)
708
+ claimed.add(pair.received);
709
+ }
710
+ for (const { eventId, role } of roles) {
711
+ const p = byId.get(eventId);
712
+ // Declared and absent: rule 1 named it, and one defect is one finding.
713
+ if (p === undefined)
714
+ continue;
715
+ const kind = roleReading(p, role, claimed.has(eventId));
716
+ if (kind === "role_step_withheld")
717
+ notes.push({ kind, eventId });
718
+ else if (kind !== null)
719
+ deviations.push({ kind, eventId });
720
+ }
721
+ }
522
722
  // A proven deviation wins: it is a mismatch between what the pack claimed
523
723
  // and what its own signed body carries, and a failing check always wins.
524
724
  // With no deviation and a content-dependent finding, the pack lines up with
@@ -534,6 +734,8 @@ export function checkDeclaration(bundle, content) {
534
734
  present: true,
535
735
  declared,
536
736
  declaredWithheld: records.filter((r) => r.withheld).map((r) => r.eventId),
737
+ version,
738
+ roles,
537
739
  };
538
740
  }
539
741
  /**
@@ -550,15 +752,18 @@ export function declarationClaims(bundle) {
550
752
  readable: false,
551
753
  caseId: null,
552
754
  claimedSides: [],
755
+ version: null,
756
+ roles: [],
553
757
  };
554
758
  const body = bodyOf(bundle);
555
759
  if (!isObject(body) || !("declaration" in body))
556
760
  return none;
557
761
  const declaration = body["declaration"];
558
- if (!isObject(declaration) || declaration["declaration_version"] !== DECLARATION_VERSION) {
762
+ const version = knownVersion(declaration);
763
+ if (version === null || !isObject(declaration)) {
559
764
  return { ...none, present: true };
560
765
  }
561
- const read = readDeclaration(declaration);
766
+ const read = readDeclaration(declaration, version);
562
767
  if (!read.ok)
563
768
  return { ...none, present: true };
564
769
  const claimedSides = [];
@@ -574,6 +779,8 @@ export function declarationClaims(bundle) {
574
779
  readable: true,
575
780
  caseId: typeof caseId === "string" ? caseId : null,
576
781
  claimedSides,
782
+ version,
783
+ roles: read.read.roles,
577
784
  };
578
785
  }
579
786
  export function renderDeclarationSummary(check) {
@@ -621,13 +828,16 @@ export function renderDeclarationSummary(check) {
621
828
  */
622
829
  export function renderNote(note) {
623
830
  switch (note.kind) {
624
- case "disclosure-did-not-travel":
831
+ case "disclosure_did_not_travel":
625
832
  return (`this pack declares ${note.count} record(s) disclosed and supplies no bytes for ` +
626
833
  "any of them: the bytes did not travel with the pack, so their content is UNKNOWN");
627
- case "pair-record-withheld":
834
+ case "pair_record_withheld":
628
835
  return (`the claimed hand-off at position ${note.pairIndex} names ${note.eventId} on its ` +
629
836
  `${note.side} side, whose record bytes this pack withholds, so what that record ` +
630
837
  "IS is UNKNOWN");
838
+ case "role_step_withheld":
839
+ return (`${note.eventId} is signed as a step and this pack withholds its ` +
840
+ "record bytes, so whether it is one is UNKNOWN");
631
841
  }
632
842
  }
633
843
  /**
@@ -640,11 +850,11 @@ export function renderNote(note) {
640
850
  */
641
851
  export function renderContentFinding(finding) {
642
852
  switch (finding.kind) {
643
- case "content-dependent-withholding":
853
+ case "content_dependent_withholding":
644
854
  return (`${finding.eventId} is declared disclosed and no bytes for it reached this ` +
645
855
  "verifier, from the pack or from beside it, so whether the holder supplied " +
646
856
  "them is UNKNOWN");
647
- case "content-dependent-disclosure":
857
+ case "content_dependent_disclosure":
648
858
  return (`${finding.eventId} is declared withheld and bytes for it were handed in ` +
649
859
  "beside this pack, which the pack does not carry and nobody signed, so " +
650
860
  "whether the holder disclosed them is UNKNOWN");
@@ -652,23 +862,36 @@ export function renderContentFinding(finding) {
652
862
  }
653
863
  export function renderDeviation(deviation) {
654
864
  switch (deviation.kind) {
655
- case "declaration-malformed":
656
- return `the declaration is not readable as ${DECLARATION_VERSION}: ${deviation.reason}`;
657
- case "declared-absent":
865
+ case "declaration_malformed":
866
+ return ("the declaration is not readable as " +
867
+ `${deviation.version || DECLARATION_VERSION}: ${deviation.reason}`);
868
+ case "declared_absent":
658
869
  return `${deviation.eventId} is declared and is NOT in this pack`;
659
- case "undeclared-present":
870
+ case "undeclared_present":
660
871
  return `${deviation.eventId} is in this pack and is NOT declared`;
661
- case "withheld-but-disclosed":
872
+ case "withheld_but_disclosed":
662
873
  return (`${deviation.eventId} is declared withheld and this pack carries its bytes ` +
663
874
  "in its own signed body");
664
- case "duplicate-event-id":
875
+ case "duplicate_event_id":
665
876
  return `this pack carries ${deviation.eventId} more than once`;
666
- case "pair-record-undeclared":
877
+ case "pair_record_undeclared":
667
878
  return (`the claimed hand-off at position ${deviation.pairIndex} names ` +
668
879
  `${deviation.eventId} on its ${deviation.side} side, which the declaration does not declare`);
669
- case "pair-record-not-a-handoff":
880
+ case "pair_record_not_a_handoff":
670
881
  return (`the claimed hand-off at position ${deviation.pairIndex} names ` +
671
882
  `${deviation.eventId} on its ${deviation.side} side, which is not a conformant ` +
672
883
  `${SIDE_EVENT_TYPES[deviation.side ?? "sent"]} record`);
884
+ case "role_handoff_unclaimed":
885
+ return (`${deviation.eventId} is signed as a hand-off record and no claimed ` +
886
+ "hand-off names it");
887
+ case "role_step_claimed":
888
+ return (`${deviation.eventId} is signed as a step and a claimed hand-off ` +
889
+ "names it as a side");
890
+ case "role_step_governed":
891
+ return (`${deviation.eventId} is signed as a step and is a record the ` +
892
+ "hand-off rules govern");
893
+ case "role_step_unreadable":
894
+ return (`${deviation.eventId} is signed as a step and this pack carries ` +
895
+ "record bytes for it that this verifier cannot read");
673
896
  }
674
897
  }
@@ -1,4 +1,6 @@
1
1
  import type { Envelope } from "./types.js";
2
+ /** The accepted `sanning.evidence/v1` wrapper majors; exported in both kernels (P-01, 0.13.0). */
3
+ export declare const ACCEPTED_EVIDENCE_MAJORS: readonly string[];
2
4
  export declare const ANCHOR_TRACE_BODY_TYPE = "sanning.anchor.trace/v3";
3
5
  /**
4
6
  * The name `event_bytes` replaced. Under the new majors a body entry that still