@sanning/proof 0.12.0 → 0.14.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 (59) 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 +353 -0
  6. package/dist/case.d.ts +217 -26
  7. package/dist/case.js +535 -124
  8. package/dist/cli.d.ts +64 -8
  9. package/dist/cli.js +799 -219
  10. package/dist/compose.js +6 -1
  11. package/dist/crypto.d.ts +1 -1
  12. package/dist/crypto.js +5 -1
  13. package/dist/declaration.d.ts +58 -52
  14. package/dist/declaration.js +256 -33
  15. package/dist/evidence.d.ts +38 -0
  16. package/dist/evidence.js +238 -22
  17. package/dist/exit-codes.d.ts +17 -0
  18. package/dist/exit-codes.js +50 -0
  19. package/dist/file-match.d.ts +105 -0
  20. package/dist/file-match.js +374 -0
  21. package/dist/grading.d.ts +28 -13
  22. package/dist/grading.js +90 -31
  23. package/dist/index.d.ts +14 -7
  24. package/dist/index.js +20 -6
  25. package/dist/message-parts.d.ts +18 -10
  26. package/dist/message-parts.js +25 -15
  27. package/dist/types.d.ts +1 -0
  28. package/dist/verifier.d.ts +63 -4
  29. package/dist/verifier.js +223 -15
  30. package/dist/witness-cache.d.ts +85 -0
  31. package/dist/witness-cache.js +264 -0
  32. package/dist/zip-reader.d.ts +50 -0
  33. package/dist/zip-reader.js +220 -0
  34. package/package.json +2 -2
  35. package/src/agent-proof.ts +4 -4
  36. package/src/case-file.ts +422 -0
  37. package/src/case.ts +671 -131
  38. package/src/cli.ts +932 -217
  39. package/src/compose.ts +8 -1
  40. package/src/crypto.ts +5 -1
  41. package/src/declaration.ts +248 -52
  42. package/src/evidence.ts +257 -23
  43. package/src/exit-codes.ts +54 -0
  44. package/src/file-match.ts +461 -0
  45. package/src/grading.ts +109 -52
  46. package/src/index.ts +101 -2
  47. package/src/message-parts.ts +25 -15
  48. package/src/types.ts +5 -0
  49. package/src/verifier.ts +225 -16
  50. package/src/witness-cache.ts +306 -0
  51. package/src/zip-reader.ts +229 -0
  52. package/vectors/README.md +66 -8
  53. package/vectors/case-file-index.json +668 -0
  54. package/vectors/case-split.json +218 -9
  55. package/vectors/file-match.json +1808 -0
  56. package/vectors/jcs-reader.json +82 -3
  57. package/vectors/pack-grading.json +68 -4
  58. package/vectors/stamp-on-chain.json +20 -5
  59. package/vectors/witness-cache.json +1304 -0
package/src/grading.ts CHANGED
@@ -41,13 +41,13 @@
41
41
  // presents a different number of records than a checkpoint
42
42
  // signed for — UNKNOWN
43
43
  //
44
- // `incomplete` is the surface's word for undetermined, and it is deliberately
45
- // NOT `verdict.status` from `evidence-bundle.md` §3. That enum grades a signed
46
- // bundle; this one grades a PACK — a strictly larger object, the bundle plus
47
- // whatever bytes travelled beside it. A pack can be incomplete while the bundle
48
- // it carries is impeccable, which is exactly the case that used to render green
49
- // over zero hashed bytes. Widening §3 to carry `incomplete` would be a spec
50
- // change; keeping the two enums distinct is not.
44
+ // `incomplete` is one of the eight words of `evidence-bundle.md` §3 (v1.17,
45
+ // A18), and it is a PACK verdict, never a bundle status. The bundle status
46
+ // grades a signed bundle; this grades a PACK — a strictly larger object, the
47
+ // bundle plus whatever bytes travelled beside it. A pack can be incomplete
48
+ // while the bundle it carries is impeccable, which is exactly the case that
49
+ // used to render green over zero hashed bytes. One vocabulary, two layers,
50
+ // each issuing the subset it can reach.
51
51
  //
52
52
  // `recordsVerified` counts CONTENT-BOUND records only. The kernel's own
53
53
  // `event.ok` tolerates undisclosed content by design — a bundle may legitimately
@@ -86,15 +86,17 @@ import { jcsParse } from "./verifier.js";
86
86
  // A superset of `EvidenceStatus`: the four the kernel can return, plus the
87
87
  // pack-level `incomplete`, plus `pending` from the shared §3 vocabulary so a
88
88
  // kernel headline this module does not downgrade can pass through unchanged.
89
- export type PackVerdict =
90
- | "verified"
91
- | "partial"
92
- | "pending"
93
- | "not_found"
94
- | "mixed"
95
- | "failed"
96
- | "malformed"
97
- | "incomplete";
89
+ export const PACK_VERDICTS = [
90
+ "verified",
91
+ "partial",
92
+ "pending",
93
+ "not_found",
94
+ "mixed",
95
+ "failed",
96
+ "malformed",
97
+ "incomplete",
98
+ ] as const;
99
+ export type PackVerdict = (typeof PACK_VERDICTS)[number];
98
100
 
99
101
  // ---- Inputs ------------------------------------------------------------------
100
102
 
@@ -109,7 +111,17 @@ export type PackVerdict =
109
111
  */
110
112
  export interface GradeInput {
111
113
  status: string | null | undefined;
112
- events: { ok: boolean; contentOk: boolean | null }[];
114
+ /**
115
+ * `eventId` and `payloadBindingOk` are read by rule version 6 alone (A22):
116
+ * an input that does not carry both is never withheld by its declaration,
117
+ * so an adapter written for rule version 5 keeps the stricter reading.
118
+ */
119
+ events: {
120
+ ok: boolean;
121
+ contentOk: boolean | null;
122
+ eventId?: string;
123
+ payloadBindingOk?: boolean | null;
124
+ }[];
113
125
  }
114
126
 
115
127
  /**
@@ -144,21 +156,23 @@ export type CompletenessNote =
144
156
 
145
157
  // ---- Output ------------------------------------------------------------------
146
158
 
147
- export type PackGradeCauseKind =
148
- | "records-omitted"
149
- | "records-surplus"
150
- | "leaf-count-unreadable"
151
- | "no-disclosure"
152
- | "missing-disclosure"
153
- | "unreadable-disclosure"
154
- | "unaccounted-disclosure"
155
- | "declaration-deviation"
159
+ export const PACK_GRADE_CAUSE_KINDS = [
160
+ "records_omitted",
161
+ "records_surplus",
162
+ "leaf_count_unreadable",
163
+ "no_disclosure",
164
+ "missing_disclosure",
165
+ "unreadable_disclosure",
166
+ "unaccounted_disclosure",
167
+ "declaration_deviation",
156
168
  /**
157
169
  * The pack and the UNSIGNED content handed in beside it do not line up
158
170
  * (proof#147). A cause, so the pack cannot read clean over it and a reader
159
171
  * is told which record; NOT a deviation, so it never reaches `failed`.
160
172
  */
161
- | "declaration-content-dependent";
173
+ "declaration_content_dependent",
174
+ ] as const;
175
+ export type PackGradeCauseKind = (typeof PACK_GRADE_CAUSE_KINDS)[number];
162
176
 
163
177
  /**
164
178
  * One reason a pack is not `verified`, as data.
@@ -186,8 +200,8 @@ export interface PackGradeCause {
186
200
  reason?: string;
187
201
  /**
188
202
  * Which deviation from the pack's own declaration, for
189
- * `declaration-deviation`, or which content-dependent finding, for
190
- * `declaration-content-dependent`.
203
+ * `declaration_deviation`, or which content-dependent finding, for
204
+ * `declaration_content_dependent`.
191
205
  */
192
206
  deviation?: DeclarationDeviationKind | DeclarationContentKind;
193
207
  }
@@ -205,6 +219,17 @@ export interface PackGrade {
205
219
  * check was passed, which is the pre-§5.4 call.
206
220
  */
207
221
  declaration?: DeclarationVerdict;
222
+ /**
223
+ * Records WITHHELD BY THEIR DECLARATION (rule version 6, evidence-bundle
224
+ * 5.4.5 v1.20, A22): a readable declaration lists them in
225
+ * `declaredWithheld`, their `ok` is true, their `contentOk` is `null` and
226
+ * their `payloadBindingOk` is true. Never counted in `recordsVerified`: no
227
+ * byte of their content was hashed, and a surface MUST NOT print them as
228
+ * verified records.
229
+ */
230
+ recordsWithheld: number;
231
+ /** Their `event_id`s, in bundle order. */
232
+ withheldEventIds: string[];
208
233
  }
209
234
 
210
235
  // ---- Reading a bundle --------------------------------------------------------
@@ -522,7 +547,20 @@ export function gradeCauses(
522
547
  account: DisclosureAccount,
523
548
  notes: readonly CompletenessNote[],
524
549
  declaration?: DeclarationCheck | null,
550
+ withheld: readonly string[] = [],
525
551
  ): PackGradeCause[] {
552
+ // The `event_id`s WITHHELD BY THEIR DECLARATION (rule version 6) are taken
553
+ // out of `expected` and `missing` before the disclosure causes are computed,
554
+ // so a pack whose every record its declaration withholds earns neither
555
+ // `no_disclosure` nor `missing_disclosure`.
556
+ if (withheld.length > 0) {
557
+ const gone = new Set(withheld);
558
+ account = {
559
+ ...account,
560
+ expected: account.expected.filter((e) => !gone.has(e)),
561
+ missing: account.missing.filter((e) => !gone.has(e)),
562
+ };
563
+ }
526
564
  const causes: PackGradeCause[] = [];
527
565
 
528
566
  // What the pack CLAIMED to hold comes first, because it frames everything
@@ -532,7 +570,7 @@ export function gradeCauses(
532
570
  // four ways can never be reported as one vague shortfall.
533
571
  for (const deviation of declaration?.deviations ?? []) {
534
572
  causes.push({
535
- kind: "declaration-deviation",
573
+ kind: "declaration_deviation",
536
574
  deviation: deviation.kind,
537
575
  ...(deviation.eventId === undefined ? {} : { firstEventId: deviation.eventId }),
538
576
  reason: renderDeviation(deviation),
@@ -544,7 +582,7 @@ export function gradeCauses(
544
582
  // deviation, so it never reaches `failed`.
545
583
  for (const finding of declaration?.contentFindings ?? []) {
546
584
  causes.push({
547
- kind: "declaration-content-dependent",
585
+ kind: "declaration_content_dependent",
548
586
  deviation: finding.kind,
549
587
  ...(finding.eventId === undefined ? {} : { firstEventId: finding.eventId }),
550
588
  reason: renderContentFinding(finding),
@@ -554,7 +592,7 @@ export function gradeCauses(
554
592
  for (const note of notes) {
555
593
  if (note.kind === "omitted") {
556
594
  causes.push({
557
- kind: "records-omitted",
595
+ kind: "records_omitted",
558
596
  checkpointRef: note.checkpointRef,
559
597
  signed: note.signed,
560
598
  presented: note.presented,
@@ -562,14 +600,14 @@ export function gradeCauses(
562
600
  });
563
601
  } else if (note.kind === "surplus") {
564
602
  causes.push({
565
- kind: "records-surplus",
603
+ kind: "records_surplus",
566
604
  checkpointRef: note.checkpointRef,
567
605
  signed: note.signed,
568
606
  presented: note.presented,
569
607
  count: note.presented - note.signed,
570
608
  });
571
609
  } else {
572
- causes.push({ kind: "leaf-count-unreadable", checkpointRef: note.checkpointRef });
610
+ causes.push({ kind: "leaf_count_unreadable", checkpointRef: note.checkpointRef });
573
611
  }
574
612
  }
575
613
 
@@ -578,10 +616,10 @@ export function gradeCauses(
578
616
  // reader deserves to be told the pack disclosed nothing rather than counting
579
617
  // N absences.
580
618
  if (account.expected.length > 0 && account.present.length === 0 && account.unreadable.length === 0) {
581
- causes.push({ kind: "no-disclosure", count: account.expected.length, of: account.expected.length });
619
+ causes.push({ kind: "no_disclosure", count: account.expected.length, of: account.expected.length });
582
620
  } else if (account.missing.length > 0) {
583
621
  causes.push({
584
- kind: "missing-disclosure",
622
+ kind: "missing_disclosure",
585
623
  count: account.missing.length,
586
624
  of: account.expected.length,
587
625
  ...(account.missing[0] === undefined ? {} : { firstEventId: account.missing[0] }),
@@ -589,12 +627,12 @@ export function gradeCauses(
589
627
  }
590
628
 
591
629
  for (const u of account.unreadable) {
592
- causes.push({ kind: "unreadable-disclosure", firstEventId: u.eventId, reason: u.reason });
630
+ causes.push({ kind: "unreadable_disclosure", firstEventId: u.eventId, reason: u.reason });
593
631
  }
594
632
 
595
633
  if (account.extra.length > 0) {
596
634
  causes.push({
597
- kind: "unaccounted-disclosure",
635
+ kind: "unaccounted_disclosure",
598
636
  count: account.extra.length,
599
637
  ...(account.extra[0] === undefined ? {} : { firstEventId: account.extra[0] }),
600
638
  });
@@ -614,7 +652,8 @@ export function gradeCauses(
614
652
  * the type and renders the verdict verbatim would then print the one word
615
653
  * `evidence-bundle.md` §6 forbids, over a pack this rule never graded.
616
654
  *
617
- * So: the six states of the §3 vocabulary pass through, and ANYTHING ELSE is
655
+ * So: the five words of the §3 vocabulary that a bundle status can pass up to
656
+ * a pack pass through, and ANYTHING ELSE is
618
657
  * `incomplete`. A verdict we cannot interpret is a check we could not run, and
619
658
  * the answer to that is UNKNOWN — never a pass, and never an accusation.
620
659
  * (`failed` and `malformed` are handled before this is reached.)
@@ -637,8 +676,9 @@ function passThrough(status: string | null | undefined): PackVerdict {
637
676
  * The verdict a surface may print, and the counts it may print beside it.
638
677
  *
639
678
  * The invariant, stated so a mutation test can kill it: `verified` is reachable
640
- * ONLY when every record's supplied bytes bound to its own commitment and
641
- * nothing is missing, unreadable, surplus or omitted.
679
+ * ONLY when every record's supplied bytes bound to its own commitment, or
680
+ * (rule version 6) the record is withheld by its declaration, and nothing is
681
+ * missing, unreadable, surplus or omitted.
642
682
  *
643
683
  * Precedence: malformed > failed > incomplete > the kernel's own headline.
644
684
  * A hard verdict wins — "the bytes are absent" is never reported as failure
@@ -696,8 +736,23 @@ export function gradePack(
696
736
  const events = Array.isArray(result?.events) ? result.events : [];
697
737
  const recordsVerified = events.filter((e) => e.ok && e.contentOk === true).length;
698
738
  const recordsFailed = events.filter((e) => e.ok === false || e.contentOk === false).length;
699
- const recordsUndetermined = events.length - recordsVerified - recordsFailed;
700
- const causes = gradeCauses(account, notes, declaration);
739
+ // WITHHELD BY ITS DECLARATION (rule version 6, 5.4.5 v1.20, A22, D5). All
740
+ // four conditions, each on its own. `declaredWithheld` is empty for an
741
+ // absent, unreadable or malformed declaration.
742
+ const declaredWithheld = new Set(declaration?.declaredWithheld ?? []);
743
+ const withheldEventIds = events
744
+ .filter(
745
+ (e) =>
746
+ typeof e.eventId === "string" &&
747
+ declaredWithheld.has(e.eventId) &&
748
+ e.ok === true &&
749
+ e.contentOk === null &&
750
+ e.payloadBindingOk === true,
751
+ )
752
+ .map((e) => e.eventId as string);
753
+ const recordsWithheld = withheldEventIds.length;
754
+ const recordsUndetermined = events.length - recordsVerified - recordsFailed - recordsWithheld;
755
+ const causes = gradeCauses(account, notes, declaration, withheldEventIds);
701
756
 
702
757
  let verdict: PackVerdict;
703
758
  if (result?.status === "malformed") verdict = "malformed";
@@ -714,6 +769,8 @@ export function gradePack(
714
769
  recordsFailed,
715
770
  recordsUndetermined,
716
771
  causes,
772
+ recordsWithheld,
773
+ withheldEventIds,
717
774
  };
718
775
  if (declaration != null) grade.declaration = declaration.verdict;
719
776
  return grade;
@@ -733,31 +790,31 @@ export function gradePack(
733
790
  */
734
791
  export function renderCause(cause: PackGradeCause, subject = "this pack"): string {
735
792
  switch (cause.kind) {
736
- case "records-omitted":
793
+ case "records_omitted":
737
794
  return (
738
795
  `checkpoint ${cause.checkpointRef} commits to ${cause.signed} record(s); ${subject} presents ` +
739
796
  `${cause.presented} — ${cause.count} record(s) OMITTED`
740
797
  );
741
- case "records-surplus":
798
+ case "records_surplus":
742
799
  return (
743
800
  `checkpoint ${cause.checkpointRef} commits to ${cause.signed} record(s); ${subject} presents ` +
744
801
  `${cause.presented} — more records than the signed tree accounts for`
745
802
  );
746
- case "leaf-count-unreadable":
803
+ case "leaf_count_unreadable":
747
804
  return `checkpoint ${cause.checkpointRef} has no readable signed leaf_count — completeness is UNKNOWN`;
748
- case "no-disclosure":
805
+ case "no_disclosure":
749
806
  return (
750
807
  `no event bytes were disclosed — content binding is UNKNOWN for all ${cause.count} ` +
751
808
  `record(s), not verified`
752
809
  );
753
- case "missing-disclosure":
810
+ case "missing_disclosure":
754
811
  return (
755
812
  `${cause.count} of ${cause.of} record(s) disclose no bytes (first: ${cause.firstEventId}) — ` +
756
813
  `content binding is UNKNOWN, not verified`
757
814
  );
758
- case "unreadable-disclosure":
815
+ case "unreadable_disclosure":
759
816
  return `the disclosure for ${cause.firstEventId} is unreadable: ${cause.reason}`;
760
- case "unaccounted-disclosure":
817
+ case "unaccounted_disclosure":
761
818
  return (
762
819
  `${cause.count} disclosed file(s) are not accounted for by any record in this bundle ` +
763
820
  `(first: ${cause.firstEventId})`
@@ -766,9 +823,9 @@ export function renderCause(cause: PackGradeCause, subject = "this pack"): strin
766
823
  // here on `reason`. Re-deriving the sentence from the cause fields would
767
824
  // put the declaration's wording in two places, and one of the two would be
768
825
  // edited alone.
769
- case "declaration-deviation":
826
+ case "declaration_deviation":
770
827
  return `this pack deviates from its own declaration: ${cause.reason}`;
771
- case "declaration-content-dependent":
828
+ case "declaration_content_dependent":
772
829
  // Named as what the reader could not establish, never as what the
773
830
  // holder did: the bytes it turns on are unsigned and whoever assembled
774
831
  // the case chose them (proof#147).
package/src/index.ts CHANGED
@@ -12,7 +12,7 @@
12
12
  // gateway/transport concerns are consumer-layer logic composed above it.
13
13
 
14
14
  // ACCEPTED_SPEC_MAJORS is exported for parity with the sibling kernels'
15
- // registries (Python ACCEPTED_SPEC_VERSIONS, Go SupportedSpecMajor): a consumer
15
+ // registries (Python ACCEPTED_SPEC_MAJORS, Go SupportedSpecMajor): a consumer
16
16
  // can see which profiles this build admits without inferring it from probe
17
17
  // calls. It is frozen at its definition (`Object.freeze`, typed
18
18
  // `readonly string[]`) because the verifier reads that same array — an exported
@@ -60,11 +60,14 @@ export type { ContentRole, Envelope, Subject, VerificationResult, VerifyOptions
60
60
  // bytes, and only for events whose verification bound them to the committed
61
61
  // content_hash. Additive; verifyEvidenceBundle's behavior is unchanged.
62
62
  export {
63
+ ACCEPTED_EVIDENCE_MAJORS,
63
64
  ANCHOR_TRACE_BODY_TYPE,
65
+ CO_SIGNATURES_NOTE,
64
66
  EXPORT_BODY_TYPE,
65
67
  STAMP_BODY_VERSION,
66
68
  STAMP_TRACE_BODY_TYPE,
67
69
  STAMP_EVIDENCE_BODY_TYPE,
70
+ coSignaturesPresent,
68
71
  disclosedContent,
69
72
  recomputeExportVerdict,
70
73
  verifyEvidenceBundle,
@@ -128,6 +131,8 @@ export type {
128
131
  // derivation to it, and diff its copy against ours. Additive — no existing
129
132
  // export changes.
130
133
  export {
134
+ PACK_GRADE_CAUSE_KINDS,
135
+ PACK_VERDICTS,
131
136
  accountDisclosure,
132
137
  completenessNotes,
133
138
  gradeCauses,
@@ -162,7 +167,14 @@ export type {
162
167
  // optional fourth argument, so a surface gets one verdict; omitting it is the
163
168
  // pre-§5.4 behavior, unchanged. Additive: no existing export changes.
164
169
  export {
170
+ DECLARATION_CONTENT_KINDS,
171
+ DECLARATION_DEVIATION_KINDS,
172
+ DECLARATION_NOTE_KINDS,
173
+ DECLARATION_ROLES,
174
+ DECLARATION_VERDICTS,
165
175
  DECLARATION_VERSION,
176
+ DECLARATION_VERSION_V2,
177
+ DECLARATION_VERSIONS,
166
178
  checkDeclaration,
167
179
  declarationClaims,
168
180
  packRecords,
@@ -180,6 +192,7 @@ export type {
180
192
  DeclarationDeviationKind,
181
193
  DeclarationNote,
182
194
  DeclarationNoteKind,
195
+ DeclarationRole,
183
196
  DeclarationVerdict,
184
197
  PackRecord,
185
198
  } from "./declaration.js";
@@ -206,20 +219,94 @@ export {
206
219
  CASE_EXCEPTION_KINDS,
207
220
  CASE_QUARANTINED_PROFILES,
208
221
  CASE_RECORD_PROFILES,
222
+ CASE_NOTE_KINDS,
223
+ CASE_RELIANCE,
224
+ CASE_RELIANCE_HANDOFF_CLAUSE,
209
225
  CASE_VERDICTS,
226
+ CONTENT_WITHHELD_BY_DECLARATION,
227
+ CO_SIGNATURES_PRESENT,
228
+ VERDICT_DISPLAY_TITLES,
229
+ WITNESS_NOT_LOOKED_UP,
230
+ WITNESS_STATES,
231
+ WITNESS_TIME_CACHED,
232
+ WITNESS_TIME_RECORDED,
233
+ caseReliance,
210
234
  readCase,
235
+ renderCaseNote,
211
236
  renderCaseSummary,
212
237
  renderException,
238
+ renderWitnessLine,
213
239
  } from "./case.js";
240
+ // The witnessed-time cache (evidence-bundle 5.6.6, v1.25, A30): read it, look it
241
+ // up, print it. Never an input to a verdict.
242
+ export {
243
+ MAX_BLOCK_TIME,
244
+ MAX_WITNESS_CACHE_DEPTH,
245
+ WITNESS_CACHE_NAME,
246
+ WITNESS_CACHE_VERSION,
247
+ checkWitnessCache,
248
+ formatBlockTime,
249
+ readWitnessCache,
250
+ renderWitnessTime,
251
+ witnessTimeQuery,
252
+ } from "./witness-cache.js";
253
+ export type { CachedWitnessTime, WitnessTimeAnswer, WitnessTimeCheck } from "./witness-cache.js";
214
254
  export type {
215
255
  CaseException,
216
256
  CaseExceptionKind,
257
+ CaseNote,
258
+ CaseNoteKind,
217
259
  CasePack,
260
+ CasePackReading,
218
261
  CaseReading,
219
262
  CaseRecordReading,
220
263
  CaseVerdict,
264
+ WitnessLine,
265
+ WitnessState,
221
266
  } from "./case.js";
222
267
 
268
+ // The case file's index (src/case-file.ts, evidence-bundle.md §5.6, A25):
269
+ // compared with the drop it describes, with NO verdict contribution. The index
270
+ // is never an input to `readCase`. Pure: bytes and a file list in.
271
+ export {
272
+ CASE_FILE_FINDING_KINDS,
273
+ CASE_FILE_INDEX_NAME,
274
+ CASE_FILE_VERSION,
275
+ HOW_TO_VERIFY_NAME,
276
+ REPORT_README_FIRST_LINE,
277
+ howToVerifyText,
278
+ isPackRef,
279
+ readCaseFileIndex,
280
+ renderCaseFileFinding,
281
+ } from "./case-file.js";
282
+ export type {
283
+ CaseFileFinding,
284
+ CaseFileFindingKind,
285
+ CaseFileIndexReading,
286
+ } from "./case-file.js";
287
+
288
+ // Files handed in beside a pack (src/file-match.ts, evidence-bundle.md §5.7,
289
+ // A29, proof#92): each file's SHA-256 matched against the commitments the
290
+ // pack's SIGNED records make, by hash alone. Never moves a verdict.
291
+ export {
292
+ FILE_MATCH_SCOPE,
293
+ NOT_READ_REASONS,
294
+ fileMatchToJson,
295
+ matchCaseFiles,
296
+ matchFiles,
297
+ renderFileMatch,
298
+ } from "./file-match.js";
299
+ export type {
300
+ FileCommitment,
301
+ FileMatchPack,
302
+ FileMatchReading,
303
+ MatchedFile,
304
+ NotRead,
305
+ NotReadReason,
306
+ OfferedFile,
307
+ UnmatchedFile,
308
+ } from "./file-match.js";
309
+
223
310
  // Chain reading (src/chain.ts): following the pointer every sanning.events/v2
224
311
  // record carries to the record before it, over the records ONE bundle presents.
225
312
  //
@@ -258,13 +345,25 @@ export type {
258
345
  // signed, offline-verifiable sanning.evidence.export/v2 artifact from a source
259
346
  // anchor-trace bundle + operator attestations + an exporter Ed25519 key. The
260
347
  // inverse of verifyEvidenceBundle's verifyExportBody dispatch.
348
+ // One exit-code table for every verb (0.13.0): the verdict word to the exit
349
+ // code, and the help text every verb prints. Additive.
350
+ export {
351
+ EXIT_CODES,
352
+ EXIT_CODES_HELP,
353
+ EXIT_FAILED,
354
+ EXIT_MALFORMED,
355
+ EXIT_MIXED,
356
+ EXIT_UNDETERMINED,
357
+ EXIT_VERIFIED,
358
+ exitCodeFor,
359
+ } from "./exit-codes.js";
261
360
  export { attestationSignatureToHex, composeExport } from "./compose.js";
262
361
  export type { ComposeExportOptions, ComposeExportResult, ExporterKey } from "./compose.js";
263
362
 
264
363
  // Agent inclusion-proof bundle verification: the
265
364
  // sanning.agent.proof/v1 bundle. The CLI sniffs spec_version and routes here so
266
365
  // one verifier covers both agent and anchor bundles.
267
- export { AGENT_PROOF_SPEC_PREFIX, isAgentProofSpec, verifyAgentProofBundle } from "./agent-proof.js";
366
+ export { AGENT_PROOF_SPEC_VERSION, isAgentProofSpec, verifyAgentProof } from "./agent-proof.js";
268
367
  export type {
269
368
  AgentProofBundle,
270
369
  AgentProofResult,
@@ -278,23 +278,33 @@ const HELD_UNCHECKED_RESULTS: readonly string[] = [
278
278
 
279
279
  /**
280
280
  * The keys that make a part carry its content INLINE, in the order they are
281
- * read. Named rather than inferred: `bytes` is A2A's spelling of an inline file
282
- * part, and `data` and `content` are the two other spellings in the wild. The
283
- * FIRST of them present with a value that is not `null` is the inline content,
284
- * so a part spelling it twice reads one way in both kernels.
281
+ * read. Settled at `bytes` alone (envelope-spec §4.1, A31, proof#163): that
282
+ * is A2A's spelling, the one the wedge actually emits, and it is the only
283
+ * inline spelling either reference kernel has read from a real producer.
284
+ * `content` and `data` were added on inference rather than on an observed
285
+ * wire shape, and sanning-io/anchor's `filePart()` and `file_part()` SDK
286
+ * helpers, the only producer this plane ships, confirm the inference was
287
+ * wrong: both emit a descriptor part, `kind: "file"` plus `content_hash` and
288
+ * `content_length`, and neither ever writes a `content` or `data` wire key
289
+ * for inline bytes. None is A2A 1.0.0's either: its `Part` has no `kind` and
290
+ * carries a file as `raw` or `url`, so a literal A2A part reads
291
+ * `part_not_a_file` (envelope-spec §4.1, A19). The one key present with a
292
+ * value that is not `null` is the inline content.
285
293
  *
286
294
  * A key present with `null` is an ABSENT inline value, on A14's ruling that an
287
- * absent value and one written as `null` name one thing. That matters: an A2A
288
- * `FilePart` with `uri` set carries `"bytes": null`, and so does proto3 JSON,
289
- * and reading those as inline made a conformant DESCRIPTOR part uncheckable
290
- * (proof#156 gate). They fall through to the descriptor rule, which is what
291
- * they are.
292
- */
293
- // A conformant DESCRIPTOR part that also carries a `content` or `data` key
294
- // meaning something else (a filename, a metadata blob) is reclassified by this
295
- // list and never checked against supplied bytes. That is inside section 4.1's
296
- // stated rule, and whether these parts ought to fall into it is proof#163.
297
- export const INLINE_BYTES_KEYS = ["bytes", "content", "data"] as const;
295
+ * absent value and one written as `null` name one thing. That matters: a
296
+ * proto3 JSON encoder can write an unset field as `"bytes": null`, and reading
297
+ * that as inline made a conformant DESCRIPTOR part uncheckable
298
+ * (proof#156 gate). Such a part falls through to the descriptor rule, which is
299
+ * what it is.
300
+ */
301
+ // Before A31 this list also carried `content` and `data`, and a conformant
302
+ // DESCRIPTOR part that happened to carry either key meaning something else (a
303
+ // filename, a caption) was silently reclassified as inline and never checked
304
+ // against supplied bytes: the swapped-bytes check A18 exists to run
305
+ // (proof#145) never ran on it. proof#163 is resolved by narrowing to `bytes`
306
+ // alone, closing that gap.
307
+ export const INLINE_BYTES_KEYS = ["bytes"] as const;
298
308
 
299
309
  const HEX64 = /^[0-9a-fA-F]{64}$/;
300
310
  const HEX_CHARSET = /^[0-9a-fA-F]+$/;
package/src/types.ts CHANGED
@@ -48,6 +48,11 @@ export interface VerifyOptions {
48
48
  // Content bind: the caller's own hash of an artifact (e.g. the in-browser
49
49
  // hash of a user's file) to match against the hashes the envelope commits to.
50
50
  expectedContentHash?: string;
51
+ // NARROWS the accept-set for this call and never widens it: the set used is
52
+ // `ACCEPTED_SPEC_MAJORS` intersected with this list, so a caller cannot
53
+ // admit a profile this kernel does not. The Python twin is
54
+ // `accepted_spec_majors` (P-10, 0.13.0).
55
+ acceptedSpecMajors?: readonly string[];
51
56
  }
52
57
 
53
58
  export interface VerificationResult {