@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
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,19 @@ 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 two NOTEs (5.5.3 v1.20): findings on the reading with no contribution,
642
+ * printed in the scope sentence so a clean verdict never hides either.
643
+ * `pack_undeclared` is a note only where the reading holds no hand-off record;
644
+ * where it holds one, it is the exception of that name.
602
645
  */
603
- export declare const CASE_HOLDS_NO_HANDOFF_RECORDS = "case_holds_no_handoff_records";
646
+ export declare const CONTENT_WITHHELD_BY_DECLARATION = "content_withheld_by_declaration";
647
+ export declare const CASE_NOTE_KINDS: readonly ["pack_undeclared", "content_withheld_by_declaration"];
648
+ export type CaseNoteKind = (typeof CASE_NOTE_KINDS)[number];
604
649
  /**
605
650
  * Every exception this reader can report, as a closed set. A surface may render
606
651
  * them and a test may assert over them; nothing outside this array is emitted.
607
652
  */
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"];
653
+ 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
654
  export type CaseExceptionKind = (typeof CASE_EXCEPTION_KINDS)[number];
610
655
  /**
611
656
  * One pack in a case, as a reader was handed it.
@@ -647,11 +692,18 @@ export interface CaseException {
647
692
  * (v1.17, A20), and it is how many were set aside.
648
693
  */
649
694
  count?: number;
650
- /** The pack's own bundle verdict, for `PACK_BUNDLE_NOT_VERIFIED`. */
695
+ /** The pack's case grade, for `PACK_GRADE_NOT_VERIFIED`. */
651
696
  status?: CaseVerdict;
697
+ /**
698
+ * The 5.4.5 cause kinds of the grade that decided the case grade, for
699
+ * `PACK_GRADE_NOT_VERIFIED`, in the grade's order. Empty where the grade was
700
+ * not computed (an unreadable body, a body that is not one of the two
701
+ * event-carrying ones, a verifier that graded too few records).
702
+ */
703
+ causes?: PackGradeCauseKind[];
652
704
  /**
653
705
  * 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
706
+ * verifier's grading: `PACK_GRADE_NOT_VERIFIED` and the three record kinds
655
707
  * graded on the record's own envelope and content.
656
708
  */
657
709
  reasons?: string[];
@@ -672,6 +724,52 @@ export interface CaseRecordReading {
672
724
  findings: string[];
673
725
  failedChecks: string[];
674
726
  counterparts: string[];
727
+ /**
728
+ * The role the pack's v2 declaration signs for this record, `handoff` or
729
+ * `step`, or `null` where the pack's declaration is absent, unreadable or
730
+ * v1, or does not declare the record (5.5.1 v1.20).
731
+ */
732
+ role: DeclarationRole | null;
733
+ }
734
+ /**
735
+ * One NOTE on the reading (5.5.3 v1.20): something true a reader needs and
736
+ * nothing a reader could not tell, so it carries no contribution.
737
+ */
738
+ export interface CaseNote {
739
+ kind: CaseNoteKind;
740
+ /** Which pack, by the reader's own name for it. */
741
+ packRef: string;
742
+ /**
743
+ * For `CONTENT_WITHHELD_BY_DECLARATION`, how many of the pack's records its
744
+ * declaration withholds.
745
+ */
746
+ count?: number;
747
+ }
748
+ /**
749
+ * One pack as the reading read it, in the order the reader was given them
750
+ * (5.5.4 v1.20). A surface names a pack by `signingKey`, the key that signed
751
+ * its wrapper, and never by an organization (D10).
752
+ */
753
+ export interface CasePackReading {
754
+ ref: string;
755
+ /** The wrapper key, normalized, or `null` where this reader could not read it. */
756
+ signingKey: string | null;
757
+ /**
758
+ * The case grade (5.5.1 step 1), or `null` where the wrapper did not hold or
759
+ * the key could not be read.
760
+ */
761
+ grade: PackVerdict | null;
762
+ /**
763
+ * Records withheld by the pack's declaration (5.4.5): in scope for their
764
+ * signature and commitment only. Never counted as verified records.
765
+ */
766
+ recordsWithheld: number;
767
+ /**
768
+ * `sanning.declaration/v1`, `sanning.declaration/v2`, or `null` where the
769
+ * pack carries no declaration, one this reader does not know, or a body this
770
+ * reader does not read.
771
+ */
772
+ declarationVersion: string | null;
675
773
  }
676
774
  export interface CaseReading {
677
775
  verdict: CaseVerdict;
@@ -714,6 +812,10 @@ export interface CaseReading {
714
812
  * checked packs, which is false.
715
813
  */
716
814
  onChainChecked: boolean;
815
+ /** The NOTEs, in pack order (5.5.3 v1.20). No contribution. */
816
+ notes: CaseNote[];
817
+ /** One reading per pack, in the order the reader was given them. */
818
+ packs: CasePackReading[];
717
819
  }
718
820
  /**
719
821
  * Read a whole case: several packs in, one verdict and its exceptions out.
@@ -744,11 +846,56 @@ export interface CaseReading {
744
846
  */
745
847
  export declare function readCase(packs: readonly CasePack[]): Promise<CaseReading>;
746
848
  export declare function renderException(exception: CaseException): string;
849
+ /** The house wording for one NOTE. Offered, not imposed, as `renderException` is. */
850
+ export declare function renderCaseNote(note: CaseNote): string;
747
851
  /**
748
- * One line a surface can print for a whole case.
852
+ * The scope sentence, printed directly under the headline (evidence-bundle
853
+ * 5.5.6 v1.20, A26), byte for byte the text `render_case_summary` returns.
749
854
  *
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.
855
+ * The packs in the reading; the distinct keys, compared as keys, that signed
856
+ * the wrapper of a pack whose wrapper held; the hand-off records; the
857
+ * exceptions; and that the reading was made offline. Where any pack holds
858
+ * records withheld by its declaration, their total; where any pack is noted
859
+ * `pack_undeclared`, how many. Those two lines are part of what the headline
860
+ * MEANS, and a surface MUST print them with it.
861
+ *
862
+ * What was not checked on the public record is no longer a clause here. It is
863
+ * the witness line (`renderWitnessLine`), printed with every headline.
753
864
  */
754
865
  export declare function renderCaseSummary(reading: CaseReading): string;
866
+ /**
867
+ * The one display title of each of the eight 3 words. A surface prints the
868
+ * title, never the word, and never a title of its own (#173, A18). The wording
869
+ * is Will's to change through the glossary (DL-71); what each MEANS is pinned
870
+ * in the spec.
871
+ */
872
+ export declare const VERDICT_DISPLAY_TITLES: Readonly<Record<string, string>>;
873
+ /**
874
+ * What a reader may rely on, for each of the six words a case reading reaches
875
+ * (5.5.6 v1.20, A26), the same for every drop. `pending` and `not_found`
876
+ * never head a case reading: they are witness states.
877
+ */
878
+ export declare const CASE_RELIANCE: Readonly<Record<CaseVerdict, string>>;
879
+ /** The three witness states, and how each is printed (5.5.6, D7). No other. */
880
+ export declare const WITNESS_STATES: {
881
+ readonly confirmed: "Confirmed on the public record";
882
+ readonly not_checked: "Not checked";
883
+ readonly differs: "The public record holds different bytes";
884
+ };
885
+ export type WitnessState = keyof typeof WITNESS_STATES;
886
+ /**
887
+ * The witness line: a lookup of the public record, reported beside the
888
+ * headline and never in it (D7). It never raises or lowers the headline and
889
+ * never changes its title. `reasons` says why a `not_checked` line did not
890
+ * settle, in a fixed order; `services` names each service that answered, as
891
+ * the URL the lookup used: named, never trusted (6).
892
+ */
893
+ export interface WitnessLine {
894
+ state: WitnessState;
895
+ reasons: string[];
896
+ services: string[];
897
+ }
898
+ /** The line printed where no lookup ran, which is every reading this kernel produces on its own. */
899
+ export declare const WITNESS_NOT_LOOKED_UP: Readonly<WitnessLine>;
900
+ /** The witness line as both kernels print it. */
901
+ export declare function renderWitnessLine(line: WitnessLine): string;