@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/dist/case.d.ts CHANGED
@@ -166,7 +166,9 @@
166
166
  * gets, since that reads wrapper keys.
167
167
  *
168
168
  * Not exits: a record whose envelope verifies and whose content does not
169
- * match its commitment is `RECORD_CONTENT_MISMATCH`, and one that fails
169
+ * match its commitment is `RECORD_CONTENT_MISMATCH` where that content is
170
+ * inside the signed body and `RECORD_CONTENT_DEPENDENT` where it was handed
171
+ * in beside the pack (proof#159), and one that fails
170
172
  * another check inside its pack is `RECORD_DOES_NOT_VERIFY_IN_ITS_PACK`.
171
173
  * Both are named and both STAY in the pile, so neither unsigned content
172
174
  * handed in beside a pack nor a broken inclusion proof removes a record
@@ -207,7 +209,9 @@
207
209
  * verdict, exactly as `counterpart_absent` and `not_a_handoff_record` are
208
210
  * findings beside a verdict in 4.1.
209
211
  *
210
- * A case with more than one outcome is `mixed` (anchor#198, criterion 4).
212
+ * A case with any `failed` contribution is `failed`: a proven failure keeps
213
+ * the headline (5.5.4 v1.16, A17, proof#158). Otherwise a case with more than
214
+ * one outcome is `mixed` (anchor#198, criterion 4).
211
215
  *
212
216
  * WHAT THIS READER DOES NOT CLAIM
213
217
  * ===============================
@@ -237,6 +241,8 @@
237
241
  * sequence. Narrowing any of it here with `handoffs[]` is precisely the move
238
242
  * that would reopen proof#137's door.
239
243
  */
244
+ import { type DeclarationRole } from "./declaration.js";
245
+ import { type PackGradeCauseKind, type PackVerdict } from "./grading.js";
240
246
  import type { SideContentMap } from "./side-content.js";
241
247
  /**
242
248
  * The verdicts a case can reach. Every one is already in use in
@@ -550,17 +556,26 @@ export declare const PACK_KEY_UNREADABLE = "pack_key_unreadable";
550
556
  */
551
557
  export declare const PACK_DOES_NOT_VERIFY = "pack_does_not_verify";
552
558
  /**
553
- * The pack's wrapper HOLDS and its own bundle verdict, from
554
- * `verifyEvidenceBundle` run here offline over the pack and the content handed
555
- * in beside it, is not `verified`. The exception carries that verdict
556
- * (`status`) and the verifier's reasons (`reasons`: its bundle errors, then its
557
- * stamp errors, then its checkpoint errors, each as the verifier wrote it), and
558
- * it CONTRIBUTES that verdict, so the case verdict never reads above its
559
- * weakest pack. Gate 3 on proof#140: two unwitnessed (`partial`) packs read as
560
- * a `verified` case with no exception. It does not say who is at fault:
561
- * content handed in beside a pack is not signed by its holder.
562
- */
563
- export declare const PACK_BUNDLE_NOT_VERIFIED = "pack_bundle_not_verified";
559
+ * The pack's wrapper HOLDS and its CASE GRADE is not `verified`
560
+ * (evidence-bundle 5.5.1 step 1 v1.20, A23; it replaces
561
+ * `pack_bundle_not_verified`, which carried the bundle status alone). The case
562
+ * grade is the pack grade of 5.4.5, taken twice: the SIGNED grade over the run
563
+ * with the side map empty, and the FULL grade over the run with it. The signed
564
+ * grade decides where it is `failed` or `malformed`; a failure only the full
565
+ * grade reaches is `incomplete`, because it rests on bytes nobody signed;
566
+ * otherwise the full grade. A body the verifier could not read is `malformed`,
567
+ * a body that is not one of the two event-carrying bodies takes the signed
568
+ * run's status, and a verifier that graded a different number of records than
569
+ * the pack holds takes the signed status, or `malformed` where that status is
570
+ * `verified`.
571
+ *
572
+ * The exception carries `status`, the case grade; `causes`, the 5.4.5 cause
573
+ * kinds of the grade that decided it (none in the first three branches); and
574
+ * `reasons`, the verifier's reasons from the run with the side map empty. It
575
+ * CONTRIBUTES the grade, and `incomplete` for `pending`, `not_found` or
576
+ * `mixed`, so the case verdict never reads above its weakest pack.
577
+ */
578
+ export declare const PACK_GRADE_NOT_VERIFIED = "pack_grade_not_verified";
564
579
  /**
565
580
  * A record in a pack whose wrapper holds, whose OWN envelope does not verify
566
581
  * (`EventResult.envelopeOk` is false). Not read by 4.1, and NAMED with the
@@ -581,12 +596,39 @@ export declare const RECORD_ENVELOPE_DOES_NOT_VERIFY = "record_envelope_does_not
581
596
  export declare const RECORD_DOES_NOT_VERIFY_IN_ITS_PACK = "record_does_not_verify_in_its_pack";
582
597
  /**
583
598
  * A record whose envelope verifies and whose disclosed content does not hash to
584
- * the `content_hash` it commits to (`EventResult.contentOk` is false). `failed`,
585
- * and the record STAYS in the pile: content beside a pack is unsigned and often
599
+ * the hash it commits to, its `content_hash` or a hand-off record's
600
+ * `message_hash` (evidence-bundle v1.19, A20) (`EventResult.contentOk` is false), with the
601
+ * side map EMPTY: the content is inside the signed body. The same mismatch from
602
+ * content handed in beside the pack is `RECORD_CONTENT_DEPENDENT` (proof#159).
603
+ * `failed`, and the record STAYS in the pile: content beside a pack is unsigned and often
586
604
  * supplied by whoever assembled the case, and gate 3 on proof#140 showed that
587
605
  * party erasing an honest verified pair by swapping it.
588
606
  */
589
607
  export declare const RECORD_CONTENT_MISMATCH = "record_content_mismatch";
608
+ /**
609
+ * The two readings proof#147 did not reach, given its shape (proof#159, ruled
610
+ * by Phil 2026-09-22, evidence-bundle.md 5.5.4 v1.16, A17). A pack whose
611
+ * bundle verdict is `failed` ONLY when read with the unsigned content handed
612
+ * in beside it, and `failed` no longer when read with that map empty, is
613
+ * `PACK_BUNDLE_CONTENT_DEPENDENT`; a record whose content mismatch comes from
614
+ * that map alone is `RECORD_CONTENT_DEPENDENT`. Both are `incomplete`: the
615
+ * reader established that the bytes it was handed are not the bytes the record
616
+ * commits to, and nothing about the party that signed the record, because
617
+ * whoever assembled the case chose those bytes with no key. Up to v1.15 both
618
+ * readings carried `failed`, and one flipped nibble of accuser-supplied content
619
+ * put a `failed` against an intact pack; under A17's headline rule that would
620
+ * have become the case's headline.
621
+ *
622
+ * The test that separates the two is the one proof#159 states for the inverse:
623
+ * every deviation a signature covers still reads `failed` with the side map
624
+ * EMPTY. So the pack is verified twice, with the map and without it, and what
625
+ * fails without it is `PACK_GRADE_NOT_VERIFIED`, `RECORD_CONTENT_MISMATCH`
626
+ * (content inside the signed body) or `RECORD_DOES_NOT_VERIFY_IN_ITS_PACK`,
627
+ * each still `failed`. Emptying the map is the most a party that signed
628
+ * nothing can do to starve the check, and it cannot remove any of those.
629
+ */
630
+ export declare const PACK_BUNDLE_CONTENT_DEPENDENT = "pack_bundle_content_dependent";
631
+ export declare const RECORD_CONTENT_DEPENDENT = "record_content_dependent";
590
632
  /**
591
633
  * A record signed under a ratified profile that is under quarantine
592
634
  * (`CASE_QUARANTINED_PROFILES`, pending DL-63). Not read, in any kernel.
@@ -596,16 +638,27 @@ export declare const RECORD_PROFILE_QUARANTINED = "record_profile_quarantined";
596
638
  /** The reader was handed no packs at all. */
597
639
  export declare const CASE_HOLDS_NO_PACKS = "case_holds_no_packs";
598
640
  /**
599
- * Every pack was read and not one hand-off record came out of any of them.
600
- * There is no hand-off to have a verdict about, and saying `verified` over that
601
- * would be the defect class in its purest form.
641
+ * The three NOTEs (5.5.3 v1.20, extended proof#148): findings on the reading
642
+ * with no contribution, printed in the scope sentence so a clean verdict
643
+ * never hides any of them. `pack_undeclared` is a note only where the
644
+ * reading holds no hand-off record; where it holds one, it is the exception
645
+ * of that name.
646
+ */
647
+ export declare const CONTENT_WITHHELD_BY_DECLARATION = "content_withheld_by_declaration";
648
+ /**
649
+ * A pack's wrapper carries a present `co_signatures` (evidence-bundle.md §2,
650
+ * decided by Phil 2026-09-24 on proof#148). The field verifies nothing and
651
+ * sits outside the wrapper's signed scope, so the case reading names it
652
+ * rather than presenting it as evidence, and it contributes no verdict.
602
653
  */
603
- export declare const CASE_HOLDS_NO_HANDOFF_RECORDS = "case_holds_no_handoff_records";
654
+ export declare const CO_SIGNATURES_PRESENT = "co_signatures_present";
655
+ export declare const CASE_NOTE_KINDS: readonly ["pack_undeclared", "content_withheld_by_declaration", "co_signatures_present"];
656
+ export type CaseNoteKind = (typeof CASE_NOTE_KINDS)[number];
604
657
  /**
605
658
  * Every exception this reader can report, as a closed set. A surface may render
606
659
  * them and a test may assert over them; nothing outside this array is emitted.
607
660
  */
608
- export declare const CASE_EXCEPTION_KINDS: readonly ["counterpart_absent", "counterpart_absent_from_counterparty_records", "counterpart_absent_signer_unknown", "counterpart_ambiguous", "counterpart_malformed", "pair_failed", "record_malformed", "record_unclaimed_by_its_pack", "record_bytes_withheld", "record_bytes_unreadable", "pack_declaration_deviates", "pack_declaration_content_dependent", "pack_declaration_unreadable", "pack_undeclared", "pack_body_type_unknown", "pack_holds_no_records", "pack_key_unreadable", "pack_does_not_verify", "pack_bundle_not_verified", "record_envelope_does_not_verify", "record_does_not_verify_in_its_pack", "record_content_mismatch", "record_profile_quarantined", "case_holds_no_packs", "case_holds_no_handoff_records"];
661
+ export declare const CASE_EXCEPTION_KINDS: readonly ["counterpart_absent", "counterpart_absent_from_counterparty_records", "counterpart_absent_signer_unknown", "counterpart_ambiguous", "counterpart_malformed", "pair_failed", "record_malformed", "record_unclaimed_by_its_pack", "record_bytes_withheld", "record_bytes_unreadable", "pack_declaration_deviates", "pack_declaration_content_dependent", "pack_declaration_unreadable", "pack_undeclared", "pack_body_type_unknown", "pack_holds_no_records", "pack_key_unreadable", "pack_does_not_verify", "pack_grade_not_verified", "pack_bundle_content_dependent", "record_envelope_does_not_verify", "record_does_not_verify_in_its_pack", "record_content_mismatch", "record_content_dependent", "record_profile_quarantined", "case_holds_no_packs"];
609
662
  export type CaseExceptionKind = (typeof CASE_EXCEPTION_KINDS)[number];
610
663
  /**
611
664
  * One pack in a case, as a reader was handed it.
@@ -647,11 +700,18 @@ export interface CaseException {
647
700
  * (v1.17, A20), and it is how many were set aside.
648
701
  */
649
702
  count?: number;
650
- /** The pack's own bundle verdict, for `PACK_BUNDLE_NOT_VERIFIED`. */
703
+ /** The pack's case grade, for `PACK_GRADE_NOT_VERIFIED`. */
651
704
  status?: CaseVerdict;
705
+ /**
706
+ * The 5.4.5 cause kinds of the grade that decided the case grade, for
707
+ * `PACK_GRADE_NOT_VERIFIED`, in the grade's order. Empty where the grade was
708
+ * not computed (an unreadable body, a body that is not one of the two
709
+ * event-carrying ones, a verifier that graded too few records).
710
+ */
711
+ causes?: PackGradeCauseKind[];
652
712
  /**
653
713
  * What the bundle verifier said, as it said it, for the kinds that carry a
654
- * verifier's grading: `PACK_BUNDLE_NOT_VERIFIED` and the three record kinds
714
+ * verifier's grading: `PACK_GRADE_NOT_VERIFIED` and the three record kinds
655
715
  * graded on the record's own envelope and content.
656
716
  */
657
717
  reasons?: string[];
@@ -672,6 +732,52 @@ export interface CaseRecordReading {
672
732
  findings: string[];
673
733
  failedChecks: string[];
674
734
  counterparts: string[];
735
+ /**
736
+ * The role the pack's v2 declaration signs for this record, `handoff` or
737
+ * `step`, or `null` where the pack's declaration is absent, unreadable or
738
+ * v1, or does not declare the record (5.5.1 v1.20).
739
+ */
740
+ role: DeclarationRole | null;
741
+ }
742
+ /**
743
+ * One NOTE on the reading (5.5.3 v1.20): something true a reader needs and
744
+ * nothing a reader could not tell, so it carries no contribution.
745
+ */
746
+ export interface CaseNote {
747
+ kind: CaseNoteKind;
748
+ /** Which pack, by the reader's own name for it. */
749
+ packRef: string;
750
+ /**
751
+ * For `CONTENT_WITHHELD_BY_DECLARATION`, how many of the pack's records its
752
+ * declaration withholds.
753
+ */
754
+ count?: number;
755
+ }
756
+ /**
757
+ * One pack as the reading read it, in the order the reader was given them
758
+ * (5.5.4 v1.20). A surface names a pack by `signingKey`, the key that signed
759
+ * its wrapper, and never by an organization (D10).
760
+ */
761
+ export interface CasePackReading {
762
+ ref: string;
763
+ /** The wrapper key, normalized, or `null` where this reader could not read it. */
764
+ signingKey: string | null;
765
+ /**
766
+ * The case grade (5.5.1 step 1), or `null` where the wrapper did not hold or
767
+ * the key could not be read.
768
+ */
769
+ grade: PackVerdict | null;
770
+ /**
771
+ * Records withheld by the pack's declaration (5.4.5): in scope for their
772
+ * signature and commitment only. Never counted as verified records.
773
+ */
774
+ recordsWithheld: number;
775
+ /**
776
+ * `sanning.declaration/v1`, `sanning.declaration/v2`, or `null` where the
777
+ * pack carries no declaration, one this reader does not know, or a body this
778
+ * reader does not read.
779
+ */
780
+ declarationVersion: string | null;
675
781
  }
676
782
  export interface CaseReading {
677
783
  verdict: CaseVerdict;
@@ -714,6 +820,10 @@ export interface CaseReading {
714
820
  * checked packs, which is false.
715
821
  */
716
822
  onChainChecked: boolean;
823
+ /** The NOTEs, in pack order (5.5.3 v1.20). No contribution. */
824
+ notes: CaseNote[];
825
+ /** One reading per pack, in the order the reader was given them. */
826
+ packs: CasePackReading[];
717
827
  }
718
828
  /**
719
829
  * Read a whole case: several packs in, one verdict and its exceptions out.
@@ -744,11 +854,92 @@ export interface CaseReading {
744
854
  */
745
855
  export declare function readCase(packs: readonly CasePack[]): Promise<CaseReading>;
746
856
  export declare function renderException(exception: CaseException): string;
857
+ /** The house wording for one NOTE. Offered, not imposed, as `renderException` is. */
858
+ export declare function renderCaseNote(note: CaseNote): string;
747
859
  /**
748
- * One line a surface can print for a whole case.
860
+ * The scope sentence, printed directly under the headline (evidence-bundle
861
+ * 5.5.6 v1.20, A26), byte for byte the text `render_case_summary` returns.
862
+ *
863
+ * The packs in the reading; the distinct keys, compared as keys, that signed
864
+ * the wrapper of a pack whose wrapper held; the hand-off records; the
865
+ * exceptions; and that the reading was made offline. Where any pack holds
866
+ * records withheld by its declaration, their total; where any pack is noted
867
+ * `pack_undeclared`, how many. Those two lines are part of what the headline
868
+ * MEANS, and a surface MUST print them with it.
749
869
  *
750
- * Both kernels print this sentence, so a reader who runs the Python command and
751
- * the TypeScript one over one case archive reads one sentence rather than two
752
- * that differ by a word.
870
+ * What was not checked on the public record is no longer a clause here. It is
871
+ * the witness line (`renderWitnessLine`), printed with every headline.
753
872
  */
754
873
  export declare function renderCaseSummary(reading: CaseReading): string;
874
+ /**
875
+ * The one display title of each of the eight 3 words. A surface prints the
876
+ * title, never the word, and never a title of its own (#173, A18). The wording
877
+ * is Will's to change through the glossary (DL-71); what each MEANS is pinned
878
+ * in the spec.
879
+ */
880
+ export declare const VERDICT_DISPLAY_TITLES: Readonly<Record<string, string>>;
881
+ /**
882
+ * What a reader may rely on, for each of the six words a case reading reaches
883
+ * (5.5.6 v1.20, A26; the `verified` text at v1.28, A35), the same for every
884
+ * drop. `pending` and `not_found` never head a case reading: they are
885
+ * witness states. `verified` is the base sentence, printed alone where the
886
+ * drop holds no hand-off record; a caller reads `caseReliance` for
887
+ * `verified`, never this mapping directly.
888
+ */
889
+ export declare const CASE_RELIANCE: Readonly<Record<CaseVerdict, string>>;
890
+ /**
891
+ * The clause a `verified` drop that holds a hand-off record gains, inserted
892
+ * after `CASE_RELIANCE`'s first sentence (5.5.6, A35). Never printed alone,
893
+ * and never where `handoffRecords` is zero: most drops hold one agent's
894
+ * pack and no hand-off to say anything about.
895
+ */
896
+ export declare const CASE_RELIANCE_HANDOFF_CLAUSE = "Every hand-off with both sides in this drop is intact.";
897
+ /**
898
+ * What a reader may rely on for one case reading (5.5.6 v1.28, A35), byte
899
+ * for byte the text `case_reliance`'s Python sibling returns.
900
+ *
901
+ * For every word but `verified` this is `CASE_RELIANCE` unchanged. For
902
+ * `verified`, the hand-off clause is inserted after the first sentence
903
+ * where `handoffRecords` is not zero, and left out where it is zero: the
904
+ * base sentence never claims a hand-off the drop does not hold.
905
+ */
906
+ export declare function caseReliance(verdict: CaseVerdict, handoffRecords: number): string;
907
+ /** The three witness states, and how each is printed (5.5.6, D7). No other. */
908
+ export declare const WITNESS_STATES: {
909
+ readonly confirmed: "Confirmed on the public record";
910
+ readonly not_checked: "Not checked";
911
+ readonly differs: "The public record holds different bytes";
912
+ };
913
+ export type WitnessState = keyof typeof WITNESS_STATES;
914
+ /**
915
+ * The witness line: a lookup of the public record, reported beside the
916
+ * headline and never in it (D7). It never raises or lowers the headline and
917
+ * never changes its title. `reasons` says why a `not_checked` line did not
918
+ * settle, in a fixed order; `services` names each service that answered, as
919
+ * the URL the lookup used: named, never trusted (6).
920
+ */
921
+ export interface WitnessLine {
922
+ state: WitnessState;
923
+ reasons: string[];
924
+ services: string[];
925
+ /**
926
+ * Witness times a pack caches that no lookup settled, as `[blockHeight,
927
+ * blockTime]`, distinct and in time order (5.6.6, v1.25, A30). Printed labelled
928
+ * not checked, and never as a time a reader may rely on. Absent reads as none.
929
+ */
930
+ cached?: [number, number][];
931
+ /**
932
+ * Witness times the public record returned where they matched a pack's
933
+ * cache, distinct and in time order. The gateway's answer, named with the
934
+ * service, never the cache's value. Absent reads as none.
935
+ */
936
+ recorded?: [number, number][];
937
+ }
938
+ /** The line printed where no lookup ran, which is every reading this kernel produces on its own. */
939
+ export declare const WITNESS_NOT_LOOKED_UP: Readonly<WitnessLine>;
940
+ /** The witness line as both kernels print it. */
941
+ /** How a witness time a pack caches is printed where nothing checked it (5.5.6, 5.6.6, v1.25, A30). */
942
+ export declare const WITNESS_TIME_CACHED = "the pack dates the witness, not checked";
943
+ /** How a witness time the public record returned is printed where it matched the cache. */
944
+ export declare const WITNESS_TIME_RECORDED = "the public record dates the witness";
945
+ export declare function renderWitnessLine(line: WitnessLine): string;