@sanning/proof 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +30 -29
  2. package/dist/agent-record.d.ts +62 -0
  3. package/dist/agent-record.js +527 -0
  4. package/dist/agent-schemas.generated.d.ts +15 -0
  5. package/dist/agent-schemas.generated.js +5759 -0
  6. package/dist/case-file.d.ts +32 -89
  7. package/dist/case-file.js +41 -93
  8. package/dist/case.d.ts +343 -810
  9. package/dist/case.js +693 -1151
  10. package/dist/chain.d.ts +19 -47
  11. package/dist/chain.js +78 -205
  12. package/dist/claims.d.ts +211 -0
  13. package/dist/claims.js +1081 -0
  14. package/dist/cli.d.ts +45 -68
  15. package/dist/cli.js +618 -1022
  16. package/dist/compose.js +23 -65
  17. package/dist/crypto.d.ts +7 -8
  18. package/dist/crypto.js +60 -147
  19. package/dist/declaration.d.ts +68 -194
  20. package/dist/declaration.js +175 -518
  21. package/dist/environment.d.ts +9 -30
  22. package/dist/environment.js +17 -66
  23. package/dist/evidence.d.ts +119 -160
  24. package/dist/evidence.js +744 -1103
  25. package/dist/exit-codes.d.ts +11 -27
  26. package/dist/exit-codes.js +24 -59
  27. package/dist/file-match.d.ts +33 -79
  28. package/dist/file-match.js +33 -69
  29. package/dist/grading.d.ts +98 -135
  30. package/dist/grading.js +224 -380
  31. package/dist/handoff.d.ts +62 -160
  32. package/dist/handoff.js +153 -393
  33. package/dist/index.d.ts +14 -10
  34. package/dist/index.js +60 -167
  35. package/dist/json-schema.d.ts +29 -0
  36. package/dist/json-schema.js +298 -0
  37. package/dist/merkle.js +16 -28
  38. package/dist/message-parts.d.ts +56 -212
  39. package/dist/message-parts.js +142 -502
  40. package/dist/side-content.d.ts +11 -22
  41. package/dist/side-content.js +22 -65
  42. package/dist/types.d.ts +17 -0
  43. package/dist/types.js +19 -5
  44. package/dist/verifier.d.ts +40 -114
  45. package/dist/verifier.js +196 -535
  46. package/dist/witness-cache.d.ts +24 -45
  47. package/dist/witness-cache.js +38 -81
  48. package/dist/witness-lookup.d.ts +36 -57
  49. package/dist/witness-lookup.js +89 -139
  50. package/dist/zip-reader.d.ts +18 -33
  51. package/dist/zip-reader.js +19 -34
  52. package/package.json +11 -3
  53. package/src/agent-record.ts +608 -0
  54. package/src/agent-schemas.generated.ts +5764 -0
  55. package/src/case-file.ts +45 -109
  56. package/src/case.ts +848 -1322
  57. package/src/chain.ts +85 -224
  58. package/src/claims.ts +1253 -0
  59. package/src/cli.ts +714 -1176
  60. package/src/compose.ts +33 -79
  61. package/src/crypto.ts +60 -147
  62. package/src/declaration.ts +219 -656
  63. package/src/environment.ts +17 -65
  64. package/src/evidence.ts +958 -1381
  65. package/src/exit-codes.ts +24 -63
  66. package/src/file-match.ts +40 -89
  67. package/src/grading.ts +287 -421
  68. package/src/handoff.ts +175 -427
  69. package/src/index.ts +132 -177
  70. package/src/json-schema.ts +295 -0
  71. package/src/merkle.ts +16 -28
  72. package/src/message-parts.ts +155 -533
  73. package/src/side-content.ts +25 -68
  74. package/src/types.ts +55 -36
  75. package/src/verifier.ts +202 -526
  76. package/src/witness-cache.ts +45 -94
  77. package/src/witness-lookup.ts +107 -177
  78. package/src/zip-reader.ts +20 -38
  79. package/vectors/README.md +52 -34
  80. package/vectors/agent-record.json +2175 -0
  81. package/vectors/case-file-index.json +1 -1
  82. package/vectors/case-split.json +2 -2
  83. package/vectors/chain-reading.json +3 -3
  84. package/vectors/file-match.json +285 -148
  85. package/vectors/jcs-reader.json +6 -6
  86. package/vectors/pack-grading.json +102 -25
  87. package/vectors/sandbox-evidence.json +597 -580
  88. package/vectors/stamp-on-chain.json +18 -18
  89. package/vectors/witness-cache.json +49 -49
  90. package/dist/agent-proof.d.ts +0 -28
  91. package/dist/agent-proof.js +0 -226
  92. package/dist/run-adapters.d.ts +0 -10
  93. package/dist/run-adapters.js +0 -35
  94. package/dist/run.d.ts +0 -209
  95. package/dist/run.js +0 -1405
  96. package/src/agent-proof.ts +0 -264
  97. package/src/run-adapters.ts +0 -44
  98. package/src/run.ts +0 -1598
package/dist/handoff.d.ts CHANGED
@@ -1,50 +1,28 @@
1
- export declare const CANDIDATE_NAMESPACE = "handoff.";
2
- export declare const DEFINED_EVENT_TYPES: readonly ["handoff.sent", "handoff.received"];
1
+ export declare const CANDIDATE_NAMESPACE = "agent.handoff.";
2
+ export declare const DEFINED_EVENT_TYPES: readonly ["agent.handoff.sent", "agent.handoff.received"];
3
3
  /**
4
- * The finding a record that is not a hand-off record at all reads with.
5
- *
6
- * A FINDING and not a verdict: the ratified verdict words are closed
7
- * (anchor#188) and this reading names an outcome beside one, exactly as
8
- * `counterpart_absent`, `counterpart_ambiguous` and `malformed_record` do. It
9
- * exists because 4.1 had a SILENT branch, and a rule with a silent branch is a
10
- * rule the party being audited steers into (proof#136). A record TRYING to be
11
- * a hand-off and getting it wrong reads `malformed`; a record that is not a
12
- * hand-off at all reads this. Both are readings. Neither is silence.
4
+ * The finding for a record that is not a hand-off record at all. A finding
5
+ * beside a verdict, not a verdict: a record that tries to be a hand-off and
6
+ * gets it wrong reads `malformed` or `nonconforming` instead. Neither is silence.
13
7
  */
14
8
  export declare const NOT_A_HANDOFF_FINDING = "not_a_handoff_record";
15
9
  /**
16
- * An envelope `public_key` as a KEY rather than as a string: the 32 key bytes
17
- * written as lowercase hex, or `null` where the value is not 64 characters of
18
- * hex and so names no key this reading can compare.
10
+ * An envelope `public_key` as a key: the 32 key bytes as lowercase hex, or
11
+ * `null` where the value is not 64 hex characters.
19
12
  *
20
- * Case is the whole point: §2 states lowercase as a PRODUCER convention, the
21
- * envelope layer accepts either case in every kernel, and the party being
22
- * audited is the producer (proof#133 adversarial gate, ruling 1). Nothing
23
- * wider is folded: whitespace inside the value is not stripped, because
24
- * Python's `bytes.fromhex` skips ASCII whitespace and this kernel's charset
25
- * check does not, a live divergence at the ENVELOPE layer and a separate
26
- * founder call (proof#135). A reading rule that silently repaired it would
27
- * hide it.
13
+ * Case is folded because the envelope layer accepts either case and the
14
+ * audited party is the producer. Whitespace is not stripped: Python's
15
+ * `bytes.fromhex` skips it and this kernel does not, and repairing that here
16
+ * would hide the divergence.
28
17
  */
29
18
  export declare function normalizedSigningKey(value: unknown): string | null;
30
19
  /**
31
- * Whether two envelope keys are the SAME KEY. Two values that name a key are
32
- * equal when their key bytes are equal.
33
- *
34
- * Where either side names no key, the question cannot be answered from key
35
- * bytes and the answer is the conservative one: the two are reported as one
36
- * key, so the pair reads `failed[self_handoff]` rather than clean. Exact
37
- * string equality was the answer here and it was the wrong way round: one key
38
- * that signed both ends, spelled `abcd...` on one envelope and `abcd ...` on
39
- * the other, is one party, and string equality called it two and read the
40
- * self-hand-off `verified` (proof#133 fourth hunt, finding 1). A record whose
41
- * own envelope key this reading cannot read is `malformed` under
42
- * `recordIsMalformed` and a formed pair reaches `malformed` before it reaches
43
- * this line, so the two rules are belt and braces rather than one rule stated
44
- * twice.
20
+ * Whether two envelope keys are the same key. Where either side names no key,
21
+ * the answer is the conservative one (same key), so the pair reads
22
+ * `failed[self_handoff]` rather than clean.
45
23
  */
46
24
  export declare function sameSigningKey(left: unknown, right: unknown): boolean;
47
- export type HandoffVerdict = "verified" | "failed" | "malformed";
25
+ export type HandoffVerdict = "verified" | "failed" | "incomplete";
48
26
  export interface HandoffRecord {
49
27
  event_type?: unknown;
50
28
  event?: unknown;
@@ -53,87 +31,49 @@ export interface HandoffRecord {
53
31
  [key: string]: unknown;
54
32
  }
55
33
  /**
56
- * One hand-off record as a reader holds it: the committed record, plus the
57
- * `public_key` of the envelope that carries it. The key comes from the ENVELOPE
58
- * and never from the record, which is the whole point of the amended rule.
34
+ * One hand-off record as a reader holds it: the committed record plus the
35
+ * `public_key` of the envelope that carries it. The key comes from the
36
+ * envelope, never from the record.
59
37
  *
60
- * PRECONDITION: THE ENVELOPE MUST ALREADY HAVE VERIFIED. "Unforgeable" is a
61
- * property of a VERIFIED envelope and of nothing else. The key on an envelope
62
- * whose signature has not been checked is an assertion like any other, and
63
- * every reading in this module is then worth exactly as much as that
64
- * assertion. A reader builds a `HeldRecord` only from an envelope that
65
- * `verifyEnvelope` returned `ok` for, bound to the canonical bytes of this
66
- * record. Nothing in this module can check that for itself, which is why it is
67
- * stated here (proof#133 adversarial gate, ruling 3), and why candidacy and
68
- * the self-hand-off check read `normalizedSigningKey(publicKey)` rather than
69
- * the spelling the audited party chose.
38
+ * Precondition: build a `HeldRecord` only from an envelope `verifyEnvelope`
39
+ * returned `ok` for. An unverified key is an assertion like any other, and this
40
+ * module cannot check it.
70
41
  */
71
42
  export interface HeldRecord {
72
43
  publicKey: string;
73
44
  record: HandoffRecord;
74
45
  ref: string;
46
+ /**
47
+ * The agent reader read this record `failed` (agent-events.md section
48
+ * 10). Set only by a reader that read the record with the pile it is held in
49
+ * (the case reading); the section 4.1 reading then takes the record as
50
+ * nonconforming, so its counterpart reads its candidate as nonconforming.
51
+ */
52
+ agentNonconforming?: boolean;
75
53
  }
76
54
  /**
77
- * Whether a reader reads this record as a hand-off record at all.
78
- *
79
- * §4.1 says a reader reads EVERY hand-off record it holds, and an exact
80
- * `handoff.` prefix did not deliver that: `HANDOFF.RECEIVED`,
81
- * `sanning.handoff.received`, `handoff-received` and a non-string `event_type`
82
- * all vanished from the reading entirely, not even as malformed, so a receipt
83
- * typed one character outside the prefix was never read and, beside a clean decoy,
84
- * published `verified` over proven tampering (proof#133 adversarial gate,
85
- * ruling 2).
86
- *
87
- * The rule is checkable rather than a judgement, and it is exactly two
88
- * clauses. The FIRST is the one the adversary cannot evade: the record's
89
- * `event` carries `counterparty_public_key`, the field §4.1 defines and no
90
- * other section does, WHATEVER its `event_type` says. The second keeps a
91
- * record that omits the field in scope: an `event_type` that is a string whose
92
- * folded form has `handoff` as one of its `.`-separated segments, which leaves
93
- * `asset_missing` and the other families alone.
94
- *
95
- * The order is the fix, not decoration. Every earlier version of this test was
96
- * an ALLOW-LIST OVER A NAME THE AUDITED PARTY WRITES, so each repair narrowed
97
- * the class and the next spelling walked around it: an exact `handoff.` prefix
98
- * fell to `HANDOFF.RECEIVED`, and the ASCII fold that replaced it fell to the
99
- * `ff` ligature, full-width letters, a Cyrillic `a`, `hand-off`, `handoffs`, a
100
- * zero-width space, a BOM and a non-breaking space, each of which left a
101
- * tampered receipt with no reading at all beside a clean decoy (proof#133
102
- * fourth hunt, finding 2). The structural field closes all of them in one rule
103
- * rather than by chasing Unicode, because the attack needs the field: a
104
- * receipt that drops `counterparty_public_key` says nothing about who it came
105
- * from.
55
+ * Whether a record is in the hand-off namespace: its `event` carries
56
+ * `counterparty_public_key` (the field §4.1 defines), whatever its `event_type`
57
+ * says, or its folded `event_type` has `handoff` as a `.`-separated segment.
106
58
  *
107
- * Anything in scope that is not exactly `handoff.sent` or `handoff.received`
108
- * is `malformed`, so a spelling is never a way out.
109
- *
110
- * THIS IS NO LONGER A GATE ON BEING READ (proof#136). Both clauses are written
111
- * by the party being audited, and one code point on each put a record in scope
112
- * by neither, which produced silence. `readHandoffs` reads every record it
113
- * holds, and this test now decides only WHICH reading a record gets: in scope
114
- * and wrong reads `malformed`, out of scope reads `NOT_A_HANDOFF_FINDING`. A
115
- * respelling buys a different line rather than no line.
59
+ * The structural clause comes first because a name is written by the audited
60
+ * party and Unicode respellings walk around any allow-list. This decides only
61
+ * which reading a record gets: in scope and wrong reads `malformed`, out of
62
+ * scope reads `NOT_A_HANDOFF_FINDING`.
116
63
  */
117
64
  export declare function isHandoffNamespace(held: HeldRecord): boolean;
118
65
  /**
119
- * What one candidate graded to, kept beside the aggregate. 4.1 requires a
120
- * reader to name the record and which check failed, and `malformed` outranks
121
- * `failed`, so one junk record signed by the counterparty key would otherwise
122
- * turn a named `message_hash` failure into a `malformed` with nothing named
123
- * (proof#133 review, ruling 6).
66
+ * What one candidate graded to, kept beside the aggregate, so a junk record
67
+ * that outranks it (`malformed` over `failed`) cannot hide a named failure.
124
68
  */
125
69
  export interface CandidateReading {
126
70
  ref: string;
127
71
  verdict: HandoffVerdict;
128
72
  failedChecks: string[];
129
73
  /**
130
- * The ref of the record that ACCOUNTS FOR this candidate, or `null` (4.1
131
- * v1.17, A20). Set only on a candidate that graded `failed` and that the
132
- * reader holds a verified pair for elsewhere; such a candidate is still
133
- * reported here and takes no part in the subject's verdict, failed checks or
134
- * ambiguity. Every reading this kernel returns carries it. It is optional in
135
- * the type only so that code which builds a `CandidateReading` itself keeps
136
- * compiling: the field is an addition.
74
+ * The ref of the record that accounts for this failed candidate, or `null`.
75
+ * An accounted-for candidate takes no part in the subject's verdict, failed
76
+ * checks or ambiguity. Optional only so caller-built readings compile.
137
77
  */
138
78
  accountedFor?: string | null;
139
79
  }
@@ -146,79 +86,41 @@ export interface HandoffReading {
146
86
  /** Ordered, never filtered. */
147
87
  candidates: string[];
148
88
  /**
149
- * One entry per candidate, in the same order, and nothing left out of it. A
150
- * candidate another record accounts for is here with `accountedFor` set, and
151
- * where every candidate is accounted for the reading is `counterpart_absent`
152
- * with the candidates still listed.
89
+ * One entry per candidate, in the same order. Where every candidate is
90
+ * accounted for, the reading is `counterpart_absent` with them still listed.
153
91
  */
154
92
  candidateReadings: CandidateReading[];
155
93
  }
156
94
  /**
157
- * A record missing a REQUIRED field of its own type, or carrying one that is
158
- * not the type §4.1 states, is malformed and fails. Stated as a rule because
159
- * §4.1 had none: a pair where both sides omitted `message_hash` compared absent
160
- * to absent, found them equal, and graded intact (proof#132).
95
+ * What is wrong with a record section 4.1 governs, read on its own, or `null`.
161
96
  *
162
- * A record whose ENVELOPE key this reading cannot read is malformed too, and
163
- * that one is a repair rather than an omission. The rule used to be "a value
164
- * that is not 64 characters of hex names no key and matches nothing", so such a
165
- * record dropped out of candidacy in silence and the subject read
166
- * `counterpart_absent`, which §4.1 obliges a reader to call innocent. Nine
167
- * whitespace spellings of a real key produce a VERIFIED envelope in exactly
168
- * that state in the Python kernel, because `bytes.fromhex` skips ASCII
169
- * whitespace, which reopened the whole of the first attack through the repair
170
- * rather than around it (proof#133 fourth hunt, finding 1). A record a reader
171
- * HOLDS, whose own key it cannot read, is a defect in the record and not an
172
- * absence of one. This holds whatever the envelope layer decides about its hex
173
- * charset (proof#135).
97
+ * `malformed` where it cannot be read as a hand-off record: its envelope key
98
+ * does not parse (an unreadable key would drop it out of candidacy and the
99
+ * other side would read an innocent `counterpart_absent`), or its type is a
100
+ * respelling of the two names. `nonconforming` where it is one of the two types
101
+ * and a required field is missing or mistyped (two records that both omit
102
+ * `message_hash` would otherwise compare equal), or the agent reader read it
103
+ * `failed` (`agentNonconforming`).
174
104
  */
105
+ export declare function recordDefect(held: HeldRecord): "malformed" | "nonconforming" | null;
106
+ /** `recordDefect` is `malformed`. */
175
107
  export declare function recordIsMalformed(held: HeldRecord): boolean;
108
+ /** `recordDefect` is `nonconforming`. */
109
+ export declare function recordIsNonconforming(held: HeldRecord): boolean;
176
110
  /**
177
- * Read ONE record against everything the reader holds.
178
- *
179
- * Every record gets a reading, and the two findings are how the distinction a
180
- * reader needs survives (proof#136): a record that is not a hand-off record at
181
- * all reads `NOT_A_HANDOFF_FINDING` with no verdict, and a record 4.1 DOES
182
- * govern and that is wrong reads `malformed`. The scope test lives HERE and
183
- * not in `readHandoffs`, so the two entry points cannot answer differently
184
- * about one record.
111
+ * Read one record against everything the reader holds. The scope test lives
112
+ * here, not in `readHandoffs`, so the two entry points cannot disagree.
185
113
  */
186
114
  export declare function readHandoff(subject: HeldRecord, held: readonly HeldRecord[]): HandoffReading;
187
115
  /**
188
- * EVERY record the reader holds gets a reading, in the order given.
189
- *
190
- * This function does not filter, and the branch that used to decide whether a
191
- * record was read at all is gone (proof#136, ratified by Phil 2026-09-22).
192
- * Scope was two clauses, an exact test for the key name
193
- * `counterparty_public_key` and an ASCII fold of `event_type`, and both
194
- * operands are written by the party being audited: one code point on each put
195
- * a record in scope by NEITHER route, so it had no reading at all and the pile
196
- * read clean. Five repairs narrowed which values reached that silent branch
197
- * and five hunts found a value outside it, so the branch is removed rather
198
- * than narrowed again.
199
- *
200
- * The distinction a reader needs survives as two findings rather than as a
201
- * reading and a silence: a record TRYING to be a hand-off and getting it wrong
202
- * reads `malformed` with `malformed_record`, and a record that is not a
203
- * hand-off record at all reads `NOT_A_HANDOFF_FINDING` with no verdict.
204
- * `isHandoffNamespace` decides which of the two, in `readHandoff`, and no
205
- * longer whether a record is read.
206
- *
207
- * THE COST, stated rather than left to be found: a reader given a mixed pile
208
- * produces a line per record, including for records that have nothing to do
209
- * with hand-offs. 4.1 says so too.
116
+ * Read every held record, in the order given. Nothing is filtered: a record
117
+ * outside the hand-off namespace reads `NOT_A_HANDOFF_FINDING`, so a mixed pile
118
+ * produces a line per record.
210
119
  */
211
120
  export declare function readHandoffs(held: readonly HeldRecord[]): HandoffReading[];
212
121
  /**
213
- * Whether this record is a conformant hand-off record of 4.1: it is read as a
214
- * hand-off record at all (`isHandoffNamespace`) and it is not `malformed`
215
- * (`recordIsMalformed`), which between them mean it is exactly a
216
- * `handoff.sent` or a `handoff.received` carrying every REQUIRED field of its
217
- * own type, under an envelope key this reading can read.
218
- *
219
- * `./declaration.ts` reads this for evidence-bundle.md 5.4: a claimed
220
- * `sent_event_id` or `received_event_id` naming a record that is not one of
221
- * these is a DEVIATION, so a record cannot be unreadable at the record layer
222
- * and usable at the pack layer at the same time (proof#136).
122
+ * Whether a record is a conformant hand-off: in the namespace, with no
123
+ * defect. `./declaration.ts` uses it for evidence-bundle.md section 5.4, so a record
124
+ * cannot be unreadable here and usable at the pack layer.
223
125
  */
224
126
  export declare function isConformantHandoffRecord(held: HeldRecord): boolean;