@sanning/proof 0.10.0 → 0.11.1

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.
package/dist/case.d.ts ADDED
@@ -0,0 +1,745 @@
1
+ /**
2
+ * One verdict for a CASE, across packs and across organizations (anchor#198).
3
+ *
4
+ * The TypeScript half of `src/sanning_proof/case.py`. Both kernels are held to
5
+ * the same corpus set, `test-vectors/case/`, so neither can pass by agreeing
6
+ * with the other.
7
+ *
8
+ * WHY THIS LAYER EXISTS, AND WHAT IT IS THE CLOSURE FOR
9
+ * =====================================================
10
+ *
11
+ * `envelope-spec.md` 4.1 reads records. `evidence-bundle.md` 5.4 reads one pack
12
+ * against its own declaration. Neither can answer the question an audit team
13
+ * actually asks, and 4.1 (v1.10, A15) and 5.4.2 rule 7 (v1.10) now say so in
14
+ * these words:
15
+ *
16
+ * **A pack a party assembles about itself is not self-proving about what
17
+ * that party LEFT OUT.**
18
+ *
19
+ * `handoffs[]` MAY be empty (5.4.1), so a holder declares a record in
20
+ * `records[]` and claims it nowhere: rule 2 cannot reach it because it is
21
+ * declared, rule 7 cannot reach it because it is unclaimed, and the pack reads
22
+ * `verified` with no deviation, which is the same reading the honest pack gets.
23
+ * The corpus pins that pack as EXPECTED
24
+ * (`test-vectors/declaration/declaration-unclaimed-record-01`) rather than
25
+ * leaving it to be rediscovered. Six repairs treated a property of one-sided
26
+ * evidence as a defect in a rule (proof#137, ratified by Phil 2026-09-22), and
27
+ * the omission happens BEFORE any reader runs, so no rule at either of those
28
+ * layers reaches it.
29
+ *
30
+ * **This module is the closure, and the reason it can be is that it reads
31
+ * several parties' packs at once.** A `handoff.sent` with no counterpart among
32
+ * the records of the case is always NAMED, and when a pack signed by the key
33
+ * that record names is in the case, the reading says that pack holds no
34
+ * counterpart. No party can make a missing counterpart read clean.
35
+ *
36
+ * What it does NOT establish is that a party which presented no pack signed by
37
+ * the named key recorded nothing. Nothing binds a pack's wrapper key to the
38
+ * keys its records are signed with, so a counterparty that hands over its
39
+ * account under any other key, or inside an export an issuer signed, or not at
40
+ * all, gets the reading that says only that no pack in the case is signed by
41
+ * the named key. Proving that a party did NOT record something needs that
42
+ * party's account or a completeness mechanism over its records, and which of
43
+ * those this reading waits for is proof#143.
44
+ *
45
+ * WHAT THAT MEANS FOR HOW THE JOIN IS BUILT
46
+ * =========================================
47
+ *
48
+ * **The case reader does not consult `handoffs[]` to pair anything.** That is
49
+ * not an oversight, it is the whole mechanism. The declaration is written by
50
+ * the party being audited, so a join keyed on it inherits exactly the hole it
51
+ * is meant to close: claim the clean pair, keep the tampered receipt
52
+ * unclaimed, and a declaration-keyed reader never grades the receipt at all.
53
+ * This reader pairs records with records, through `readHandoff`, over the
54
+ * union of every record every pack in the case supplied. `handoffs[]` is read
55
+ * for ONE thing and one only: to name a record its own holder DECLARED and
56
+ * claimed nowhere (`RECORD_UNCLAIMED`), which is the pinned vector above
57
+ * becoming visible. That claim check runs over every record a pack declares,
58
+ * and not only over the ones 4.1 governs, because "is this a conformant
59
+ * hand-off record" is an answer the audited party writes.
60
+ *
61
+ * **Matching is on hashes and identifiers alone and NEVER on tenancy**
62
+ * (anchor#198, and DL-60: producer identity gates what the plane accepts and
63
+ * never whether evidence verifies). The union pile is not partitioned by pack,
64
+ * by issuer, by `tenant_id` or by organization anywhere in this module, so a
65
+ * pair assembled by two organizations reads exactly as a pair assembled by
66
+ * one. There is no code path here that reads a tenant.
67
+ *
68
+ * THE DEFECT CLASS THIS MODULE IS WRITTEN AGAINST
69
+ * ===============================================
70
+ *
71
+ * Seventeen findings on this program share one generator: **a deviation
72
+ * designed as a comparison between two present things, where the deviation
73
+ * itself removes one of them, so the code takes the "nothing to compare"
74
+ * branch written for the honest case.** A rule shaped that way reads the
75
+ * absence of a signal as "fine" rather than as "I could not tell", and a real
76
+ * deviation grades clean.
77
+ *
78
+ * Acceptance criterion 3 of anchor#198 is made of exactly that shape: *a
79
+ * one-sided hand-off is an exception, not a failure*. So the four rules below
80
+ * are structural and are the thing to preserve in any change here.
81
+ *
82
+ * 1. **Every exception is NAMED and appears in the output, and every exception
83
+ * contributes a verdict.** There is no path through this module that
84
+ * removes a record, a pack or a claim from the reading. An empty
85
+ * `exceptions` is the ONLY shape a reader may read as clean, and `readCase`
86
+ * is invariant: `verified` is reachable only when that array is empty.
87
+ * 2. **Nothing a party writes decides whether grading happens.** No graded
88
+ * value is part of any key that selects a code path. The pile is everything
89
+ * every pack supplied; there is no filter.
90
+ * 3. **Every field that selects a code path and is chosen by the party being
91
+ * audited has an accept-set, and an unknown value goes to a named exception
92
+ * rather than to silence.** `body_type` is `CASE_BODY_TYPES`;
93
+ * `declaration_version` is `checkDeclaration`'s; `event_type` and
94
+ * `event.counterparty_public_key` are 4.1's, through `readHandoffs`. A pack
95
+ * that supplies zero records is `PACK_HOLDS_NO_RECORDS` and a case with no
96
+ * hand-off record in it at all is `CASE_HOLDS_NO_HANDOFF_RECORDS`, because
97
+ * "nothing to grade" is never "nothing is wrong".
98
+ * 4. **The 4.1 predicates are CALLED, never restated.** `readHandoffs`,
99
+ * `normalizedSigningKey` and `packRecords` are the one implementation of
100
+ * each rule. A validation copied beside the one that enforces it drifts,
101
+ * and a copy that is looser blesses a value the real one then rejects.
102
+ *
103
+ * EVERY EARLY EXIT IN THIS MODULE, AND WHAT IT SKIPS
104
+ * ==================================================
105
+ *
106
+ * Three defects on this layer were one shape: an exit between a record
107
+ * entering the pile and a check running over it, with the audited party
108
+ * choosing whether the exit was taken. So the exits are enumerated here rather
109
+ * than left to be rediscovered a fourth time. `src/sanning_proof/case.py` has
110
+ * the same exits in the same order, and the pull request that added this list
111
+ * carries the same table.
112
+ *
113
+ * 1. **No packs** (`return`). Skips everything. Reached by the reader, who
114
+ * chose what to hand in. Named: `CASE_HOLDS_NO_PACKS`.
115
+ * 2. **A wrapper key this reader cannot read** (`continue`). Skips the bundle
116
+ * verification, the declaration check and every record in the pack. The
117
+ * holder writes the key, so it steers here freely, and the pack is NAMED
118
+ * (`PACK_KEY_UNREADABLE`). Both bundle verifiers now refuse every such
119
+ * spelling in the same words (proof#142), so this exit and exit 3 agree on
120
+ * which packs they take; it stays first because it names WHY. While any
121
+ * pack takes this exit, a one-sided record whose named key signed no pack
122
+ * reads `COUNTERPART_ABSENT_SIGNER_UNKNOWN` and never the innocent
123
+ * `COUNTERPART_ABSENT`.
124
+ * 3. **The wrapper does not hold** (`continue`): `signatureOk` and
125
+ * `bodyHashOk` are not both true. Skips the declaration check and every
126
+ * record in the pack, and the key it claims is never counted as a pack
127
+ * signer. **This is the forger's case and the only exit that turns on
128
+ * provenance**: those two fields are the whole of what ties a pack to a
129
+ * key. Reached by whoever built the pack, and by anyone who alters its
130
+ * unsigned `signature` field, which is indistinguishable from a forgery.
131
+ * Named: `PACK_DOES_NOT_VERIFY`, and it moves one-sided records to
132
+ * `COUNTERPART_ABSENT_SIGNER_UNKNOWN` exactly as exit 2 does.
133
+ *
134
+ * A pack whose wrapper HOLDS is never sent here, whatever is wrong inside
135
+ * it (gate 3 on proof#140): its key is counted, its own bundle verdict is
136
+ * `PACK_BUNDLE_NOT_VERIFIED` when that verdict is not `verified`, and each
137
+ * record is graded on its own envelope below.
138
+ * 3b. **The bundle verdict is `malformed`** (`continue`, after exit 3 and after
139
+ * `PACK_BUNDLE_NOT_VERIFIED`). The body is one the verifier could not read,
140
+ * so it graded no record. The key is already counted.
141
+ * 4. **`body_type` outside `CASE_BODY_TYPES`** (`continue`). Skips the
142
+ * declaration check and every record in the pack. The holder writes
143
+ * `body_type`, so the holder steers here freely, which is why it is named
144
+ * every time (`PACK_BODY_TYPE_UNKNOWN`) and why the pack's wrapper key is
145
+ * counted ABOVE it: a counterparty cannot leave the damning counterpart
146
+ * line by choosing a body (F3). `sanning.evidence.export/v2` lands here and
147
+ * is not unwrapped.
148
+ * 5. **No records** (`continue`). Skips the declaration check, over nothing.
149
+ * The holder steers here by handing over an empty `events[]`. Named:
150
+ * `PACK_HOLDS_NO_RECORDS`; the wrapper key is already counted.
151
+ * 5b. **A record under a quarantined profile** and 5c. **a record whose own
152
+ * envelope does not verify** (`continue`). Each skips 4.1 for that record,
153
+ * its candidacy for any other record, and the claim check, because 4.1
154
+ * compares only envelopes that verified (`HeldRecord`). Named:
155
+ * `RECORD_PROFILE_QUARANTINED` (`incomplete`, pending DL-63) and
156
+ * `RECORD_ENVELOPE_DOES_NOT_VERIFY` (`failed`). Neither removes a named
157
+ * failure from another record: the other records of the pack are still
158
+ * graded, and pairs still form over every record that verified.
159
+ * 6. **`event_bytes` withheld** and 7. **`event_bytes` unreadable**
160
+ * (`continue`). Each skips 4.1 for that record, its candidacy for any other
161
+ * record, and the claim check. The holder steers into both by what it
162
+ * discloses. Each is named in the claim check's place
163
+ * (`RECORD_BYTES_WITHHELD` `incomplete`, `RECORD_BYTES_UNREADABLE`
164
+ * `malformed`), so neither buys a cleaner reading than an unclaimed record
165
+ * would get, and neither decides which counterpart kind another record
166
+ * gets, since that reads wrapper keys.
167
+ *
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
170
+ * another check inside its pack is `RECORD_DOES_NOT_VERIFY_IN_ITS_PACK`.
171
+ * Both are named and both STAY in the pile, so neither unsigned content
172
+ * handed in beside a pack nor a broken inclusion proof removes a record
173
+ * from pairing.
174
+ * 8. **`not_a_handoff_record`** (`continue`). Skips the hand-off count and the
175
+ * counterpart, ambiguity and pair reporting, which 4.1 does not give a
176
+ * record outside its scope. The holder steers here by respelling a field.
177
+ * It no longer skips the claim check, which runs ABOVE it because it asks
178
+ * what the pack SAID and not what the record is (F1).
179
+ * 9. **THIS record is malformed** (`continue`). Skips the counterpart,
180
+ * ambiguity and pair reporting, and 4.1 computed none of them for a
181
+ * malformed subject, so nothing is lost. A party steers here only by
182
+ * breaking a record it signed, and the record is named `RECORD_MALFORMED`.
183
+ * A well formed record whose CANDIDATE is malformed does not take this
184
+ * exit: it is `COUNTERPART_MALFORMED` and the reporting below still runs
185
+ * (F2).
186
+ * 10. **`counterpart_absent`** (`continue`). Skips the ambiguity and pair
187
+ * reporting, and there is nothing to skip: 4.1 emits that finding only
188
+ * for an EMPTY candidate list. Which of the three counterpart kinds it
189
+ * gets reads the keys of the packs that verified, counted before exit 4,
190
+ * and whether any pack took exit 2 or 3.
191
+ *
192
+ * Not an exit, and listed because it is one more place this shape could
193
+ * hide: the claim check runs only where the pack's declaration is READABLE. An
194
+ * absent or unreadable one is named once for the pack (`PACK_UNDECLARED`,
195
+ * `PACK_DECLARATION_UNREADABLE`) rather than once per record.
196
+ *
197
+ * THE VERDICT VOCABULARY IS CLOSED
198
+ * ================================
199
+ *
200
+ * `verified`, `failed`, `partial`, `mixed`, `incomplete` and `malformed`,
201
+ * every one of them already in use (`evidence-bundle.md` 3 and
202
+ * `PACK_VERDICTS`). **No new word is coined anywhere in this module**, and
203
+ * `CASE_VERDICTS` is asserted against `PACK_VERDICTS` by the test suite so a
204
+ * future addition here cannot invent one quietly. Everything this module has
205
+ * to say that is not one of those six is an EXCEPTION KIND reported beside the
206
+ * verdict, exactly as `counterpart_absent` and `not_a_handoff_record` are
207
+ * findings beside a verdict in 4.1.
208
+ *
209
+ * A case with more than one outcome is `mixed` (anchor#198, criterion 4).
210
+ *
211
+ * WHAT THIS READER DOES NOT CLAIM
212
+ * ===============================
213
+ *
214
+ * * **Not that the case is complete.** Joining is voluntary. A counterparty
215
+ * that never handed over a pack leaves `COUNTERPART_ABSENT`, which is an
216
+ * exception and never a failure. It says that no pack in the case is signed
217
+ * by the named key, and nothing about what that party recorded.
218
+ * * **Not that a party which presented no pack under the named key recorded
219
+ * nothing.** See the top of this comment, and proof#143.
220
+ * * **Not that two organizations are independent.** One team holding both keys
221
+ * proves which agent said what, not that two parties agreed (4.1, 5.4.4).
222
+ * * **Not that a `verified` case has no other problem.** It reads hand-offs.
223
+ * It runs `verifyEvidenceBundle` over every pack itself, offline and with no
224
+ * gateways, and a pack whose own bundle verdict is not `verified` is named
225
+ * with that verdict and its reasons, so the case never reads above its
226
+ * weakest pack. On-chain re-fetch and `gradePack` are a caller's to run.
227
+ *
228
+ * THE COST OF 4.1's "GRADE EVERY CANDIDATE", INHERITED HERE AND STATED RATHER
229
+ * THAN LEFT TO BE FOUND (proof#134, ratified by Phil 2026-09-22). A reader
230
+ * whose pile holds two or more records of the complementary type signed by one
231
+ * counterparty key cannot reach `verified`: the other exchanges' records are
232
+ * candidates too, they commit to other messages, and they fail. One agent
233
+ * sending two messages to the same counterparty inside one case lands there,
234
+ * and it surfaces as `COUNTERPART_AMBIGUOUS` beside the failure. An
235
+ * alternating exchange (A sends, B replies) does NOT, because a reply is
236
+ * signed by the other key and is the other type. Narrowing that here with
237
+ * `handoffs[]` is precisely the move that would reopen proof#137's door.
238
+ */
239
+ import type { SideContentMap } from "./side-content.js";
240
+ /**
241
+ * The verdicts a case can reach. Every one is already in use in
242
+ * `evidence-bundle.md` 3 or `PACK_VERDICTS`, and anchor#198 says in terms that
243
+ * no new verdict word is introduced anywhere.
244
+ */
245
+ export declare const CASE_VERDICTS: readonly ["verified", "failed", "partial", "mixed", "incomplete", "malformed"];
246
+ export type CaseVerdict = (typeof CASE_VERDICTS)[number];
247
+ /**
248
+ * The two pack bodies that carry `events[]` and may carry a `declaration`
249
+ * (`evidence-bundle.md` 5.4.1). This is an ACCEPT-SET and not a hint.
250
+ *
251
+ * `body_type` selects a code path and is written by the party assembling the
252
+ * pack. Without this set, a pack whose records live under a key this reader
253
+ * does not know yields zero records, contributes nothing, and lets the rest of
254
+ * the case read clean over it: the exact defect class this module is written
255
+ * against. An unknown value is `PACK_BODY_TYPE_UNKNOWN`.
256
+ *
257
+ * **`sanning.evidence.export/v2` is NOT in this set and is NOT unwrapped.** It
258
+ * verifies as a bundle and carries a whole `sanning.anchor.trace/v3` pack under
259
+ * `body.source_bundle`, so its records do not reach the pile and the export is
260
+ * named `PACK_BODY_TYPE_UNKNOWN`, `incomplete`. Reading inside it changes which
261
+ * packs this reader grades, so it is a decision of its own. A reader who needs
262
+ * the records hands in the source bundle as a pack. The export's wrapper key is
263
+ * still read, so wrapping a pack in one does not change which counterpart kind
264
+ * another record gets.
265
+ */
266
+ export declare const CASE_BODY_TYPES: readonly ["sanning.anchor.trace/v3", "sanning.stamp.evidence/v2"];
267
+ /**
268
+ * The envelope profiles the case reader reads records under: the ratified
269
+ * accept-set (`envelope-spec.md` 5) minus every profile under quarantine in
270
+ * `cross-kernel/accept-set.json`. A test in each kernel derives both arrays
271
+ * from that file, and a test here pins this one equal to this kernel's own
272
+ * `ACCEPTED_SPEC_MAJORS`, which is what `verifyEvidenceBundle` reads under:
273
+ * the Python kernel narrows its verifier to this set for the case reader, and
274
+ * this kernel's verifier already reads exactly it. If this kernel ever admits
275
+ * a quarantined profile, that test fails, and the case reader has to narrow
276
+ * its verifier the way the Python one does before it can pass.
277
+ */
278
+ export declare const CASE_RECORD_PROFILES: readonly ["sanning.agent/v1", "sanning.events/v2"];
279
+ /** The ratified profiles under quarantine, pending DL-63. */
280
+ export declare const CASE_QUARANTINED_PROFILES: readonly ["sanning.mlflow/v1"];
281
+ /**
282
+ * A conformant hand-off record with no counterpart among the records this
283
+ * reader read, where every pack in the case verified under a key this reader
284
+ * can read and NO pack in the case is signed by the key the record names.
285
+ * **An exception, never a failure** (anchor#198, criterion 3).
286
+ *
287
+ * That is ALL it says. It does not say the counterparty's account is absent,
288
+ * and it does not say nothing was tampered with: a counterparty that signs its
289
+ * own pack under a second key, or hands over an export an issuer signed,
290
+ * reaches this reading too, and nothing offline binds a pack's key to the keys
291
+ * of the records inside it (proof#143). The rendered text states the fact and
292
+ * stops.
293
+ *
294
+ * It says nothing about RECORDS signed by that key either. A party may hold records
295
+ * its counterparty signed, and those are still graded as candidates, but they
296
+ * are not the counterparty's account of itself, so they do not move this
297
+ * record to `COUNTERPART_ABSENT_FROM_COUNTERPARTY_RECORDS`.
298
+ */
299
+ export declare const COUNTERPART_ABSENT = "counterpart_absent";
300
+ /**
301
+ * THE CLOSURE anchor#198 exists for.
302
+ *
303
+ * A conformant hand-off record with no counterpart, where a pack that VERIFIES
304
+ * and is SIGNED by the key this record names is in the case, and not one
305
+ * record this reader could read is this record's counterpart. It states that a
306
+ * pack signed by the named key is present and holds no counterpart this reader
307
+ * could read, and nothing more. 4.1 (v1.10, A15) and 5.4.2 rule 7 (v1.10) both point here, and
308
+ * `declaration/declaration-unclaimed-record-01` is the pack that reaches this
309
+ * line.
310
+ *
311
+ * **"Its own account" is read from the key that signed the PACK, and never
312
+ * from the keys that signed records.** A pack's wrapper key signs `body_hash`
313
+ * and `body` carries the declaration (`evidence-bundle.md` 5.4, "Who signs
314
+ * it"), so a wrapper signed by B is provenance the accusing party cannot forge
315
+ * without B's private key. Records signed by B that A merely HOLDS are not B's
316
+ * account of itself: keyed on record keys, one pack from A plus one unrelated
317
+ * record B signed at some other time reached this line when B had handed over
318
+ * nothing at all.
319
+ *
320
+ * What it does NOT prove, stated so no surface claims it: that B handed the
321
+ * pack over for THIS case. A pack B signed for some other purpose and a third
322
+ * party replayed reads the same, because the only values that could say
323
+ * otherwise (the declaration's `case_id` and `period`) are written by B and
324
+ * would let B leave this line by writing them differently. The cost the other
325
+ * way: a pack B assembles under any key other than the one its records name, a
326
+ * per-organization hand-over key (anchor#196) included, reads
327
+ * `COUNTERPART_ABSENT`, because nothing in the case binds that key to B. That
328
+ * reading is still true: no pack signed by the named key is here. So a
329
+ * counterparty can AVOID this line by choosing the key it signs under; what it
330
+ * cannot do is make the missing counterpart read clean.
331
+ *
332
+ * It is deliberately NOT also conditioned on a record signed by B being in the
333
+ * pile. B's pack can hold none this reader reads (a body it does not unwrap,
334
+ * bytes B withheld, an empty `events[]`), and each of those is a choice B makes
335
+ * and is named on its own. A second conjunct on them hands B the innocent line
336
+ * for making one.
337
+ *
338
+ * It is a DISTINCT kind from `COUNTERPART_ABSENT` and not a louder spelling of
339
+ * it. Collapsing the two would be the defect class again: "the counterparty
340
+ * never joined" and "the counterparty joined and its own account does not
341
+ * contain this hand-off" are the honest reading and the damning one, and one
342
+ * name for both lets the second read as the first.
343
+ *
344
+ * It is still an EXCEPTION and not a failure, which criterion 3 requires and
345
+ * which is also the honest call: the counterparty's pack is a selection over
346
+ * its own interval, and a hand-off outside that interval is legitimately not
347
+ * in it. What this names is that the question can now be ASKED of the other
348
+ * party's own evidence, which is what no single pack could ever support.
349
+ */
350
+ export declare const COUNTERPART_ABSENT_FROM_COUNTERPARTY_RECORDS = "counterpart_absent_from_counterparty_records";
351
+ /**
352
+ * A conformant hand-off record with no counterpart, no VERIFIED pack in the
353
+ * case signed by the key it names, and at least one pack in the case that this
354
+ * reader could not tie to a key: its wrapper key is unreadable
355
+ * (`PACK_KEY_UNREADABLE`) or it does not verify (`PACK_DOES_NOT_VERIFY`).
356
+ * Whether a pack signed by the named key is in the case is UNKNOWN, so neither
357
+ * of the other two kinds is true.
358
+ *
359
+ * The gate on the previous version of this reader showed why it exists. A
360
+ * counterparty that signed its own pack under `" " + key` got
361
+ * `PACK_KEY_UNREADABLE` for the pack and the innocent `COUNTERPART_ABSENT` for
362
+ * the record, and the output contradicted itself: whose pack it was is
363
+ * UNKNOWN, and then no pack is signed by the named key. The second sentence
364
+ * was not established.
365
+ *
366
+ * `incomplete`, like the other two one-sided kinds: "I could not tell".
367
+ */
368
+ export declare const COUNTERPART_ABSENT_SIGNER_UNKNOWN = "counterpart_absent_signer_unknown";
369
+ /**
370
+ * More than one candidate counterpart (4.1, and proof#134's stated cost). The
371
+ * reading is the worst of them and the ambiguity is named: a reader permitted
372
+ * to take one match can be handed an honest decoy beside a tampered record.
373
+ */
374
+ export declare const COUNTERPART_AMBIGUOUS = "counterpart_ambiguous";
375
+ /**
376
+ * A formed pair that failed a check. Carries which checks, from 4.1's own
377
+ * fixed order, so a reader is told the record AND the check.
378
+ */
379
+ export declare const PAIR_FAILED = "pair_failed";
380
+ /**
381
+ * THIS record is wrong: 4.1 governs it and `recordIsMalformed` says so of the
382
+ * record itself. A statement about the record this exception names, and about
383
+ * no other.
384
+ */
385
+ export declare const RECORD_MALFORMED = "record_malformed";
386
+ /**
387
+ * A CANDIDATE counterpart of this record is malformed, and this record is not.
388
+ *
389
+ * 4.1 grades every candidate and reports the worst of them, and `malformed`
390
+ * outranks `failed`, so one junk record signed by the counterparty's key makes
391
+ * the reading of a perfectly well formed record `malformed` too. Filed under
392
+ * `RECORD_MALFORMED`, that read as "this record is not a well formed hand-off
393
+ * record" about the HONEST record, and the branch it took skipped the
394
+ * counterpart, ambiguity and `failedChecks` reporting entirely: one junk record
395
+ * both blamed the wrong party and stripped a named `message_hash` failure out
396
+ * of the case. That is proof#133 ruling 6, which 4.1 closed by keeping the
397
+ * UNION of `failedChecks`, reappearing one layer out because this layer
398
+ * discarded that union.
399
+ *
400
+ * A record's own conformance and its candidate's are different facts and do
401
+ * not share a kind. This one is `incomplete` and not `malformed`: the proven
402
+ * defect belongs to the candidate, which carries its own `RECORD_MALFORMED`
403
+ * line, and what is true of THIS record is that its pairing could not be
404
+ * determined. Contributing `malformed` here would attribute the candidate's
405
+ * defect to the record a party being accused did not write.
406
+ */
407
+ export declare const COUNTERPART_MALFORMED = "counterpart_malformed";
408
+ /**
409
+ * A record that its own pack DECLARES and its own pack's READABLE declaration
410
+ * claims in no `handoffs[]` entry. This is proof#137's door, named at the layer
411
+ * that can see it: the holder declared the record and claimed it nowhere, so
412
+ * neither 5.4.2 rule 2 nor rule 7 reaches it and the pack reads `verified`.
413
+ *
414
+ * **It turns on what the PACK said about the record, and never on whether 4.1
415
+ * governs it.** Gating it on "is this a conformant hand-off record" put it
416
+ * behind a value the audited party writes: the same second receipt, respelled
417
+ * with an `ff` ligature in `event_type` and a Cyrillic `е` in
418
+ * `counterparty_public_key`, left 4.1's scope, took the `not_a_handoff_record`
419
+ * branch, and bought a case reading of `verified` with zero exceptions where
420
+ * the conformant spelling read `mixed` with four. proof#136 ratified that a
421
+ * respelling buys a DIFFERENT LINE rather than no line; at this layer it
422
+ * bought no line at all, and a strictly better outcome than honesty.
423
+ *
424
+ * It is not an accusation. `handoffs[]` MAY be empty and an unclaimed record
425
+ * may be entirely innocent. What it is, is the end of the silence: the reader
426
+ * cannot tell whether an unclaimed record is part of the hand-over the holder
427
+ * meant to give or one it is keeping out of its own account, and that is "I
428
+ * could not tell", which is why it carries `incomplete` and not `verified`.
429
+ *
430
+ * THE COST, stated rather than left to be found: a pack that declares records
431
+ * which are not hand-off sides at all, an ordinary log line among them, is
432
+ * named once per such record, so a case holding one cannot read `verified`.
433
+ * That is the price of a rule with no branch the holder selects, and the only
434
+ * narrower rule available keys on a value the holder writes. A surface that
435
+ * wants the two apart reads `CaseRecordReading.findings`, where
436
+ * `not_a_handoff_record` still says which is which.
437
+ */
438
+ export declare const RECORD_UNCLAIMED = "record_unclaimed_by_its_pack";
439
+ /**
440
+ * The pack supplied no `event_bytes` for a record, so this reader cannot see
441
+ * what the record IS in either direction.
442
+ *
443
+ * `event_bytes` is OPTIONAL (5.1, 5.3) and withholding it is minimum
444
+ * disclosure, which the corpus calls a customer's right and not a defect
445
+ * (proof#137, defect 3), so this is not a deviation and does not fail. It is
446
+ * still SAID, because a withheld record is exactly where a missing counterpart
447
+ * would hide, and a reader that dropped it silently would read a trimmed pack
448
+ * as an honest one.
449
+ */
450
+ export declare const RECORD_BYTES_WITHHELD = "record_bytes_withheld";
451
+ /**
452
+ * The pack SUPPLIED `event_bytes` this reader cannot read as
453
+ * `JCS(event record)` (5.4.2 rule 7's parser rule, one implementation in
454
+ * `packRecords`). Bytes the holder handed over that do not decode are a defect
455
+ * in what was handed over, not an absence, so this one is `malformed`.
456
+ */
457
+ export declare const RECORD_BYTES_UNREADABLE = "record_bytes_unreadable";
458
+ /**
459
+ * The pack deviates from its own declaration (5.4.2). A proven mismatch, so it
460
+ * fails: a case cannot be clean over a pack that is proven to be trimmed.
461
+ */
462
+ export declare const PACK_DECLARATION_DEVIATES = "pack_declaration_deviates";
463
+ /**
464
+ * The pack and the UNSIGNED content handed in beside it do not line up (5.4.2
465
+ * rules 3 and 4, proof#147, ratified by Phil 2026-09-22). A record the pack
466
+ * declares `disclosed` for which no bytes reached this reader, or one it
467
+ * declares `content_withheld` for which bytes reached this reader from the
468
+ * side-content map alone.
469
+ *
470
+ * `incomplete` and never `failed`, which is the whole point of the kind. 5.4
471
+ * grades a pack's declaration partly against content the READER was handed,
472
+ * and whoever assembles a case fills that map in. Under
473
+ * `PACK_DECLARATION_DEVIATES` an accuser could put an intact pack into a
474
+ * `failed` case WITH NO KEY AT ALL: supply bytes for a record its holder
475
+ * declared withheld, or drop the bytes of one it declared disclosed. The pack
476
+ * was intact, its wrapper verified, and its holder had done nothing. This is
477
+ * the mirror of proof#143, where a counterparty makes ITSELF look better by
478
+ * choosing what it hands over.
479
+ *
480
+ * It is emitted BESIDE `PACK_DECLARATION_DEVIATES` where a pack does both,
481
+ * because the two say different things about different records.
482
+ */
483
+ export declare const PACK_DECLARATION_CONTENT_DEPENDENT = "pack_declaration_content_dependent";
484
+ /**
485
+ * The pack carries a `declaration` this reader cannot read (5.4.3): an unknown
486
+ * `declaration_version`, an absent or `null` one, a non-object, or one that
487
+ * does not satisfy 5.4.1. `declaration_version` is chosen by the same party
488
+ * that assembles the pack and has no accept-set of its own, so this is UNKNOWN
489
+ * and never clean.
490
+ */
491
+ export declare const PACK_DECLARATION_UNREADABLE = "pack_declaration_unreadable";
492
+ /**
493
+ * The pack carries no `declaration` member at all, so it never said what it
494
+ * holds. `partial` is 5.4.3's own word for a claim nobody made: never
495
+ * `verified`, because there is nothing to check the pack against, and never
496
+ * `failed`, because nothing is wrong.
497
+ */
498
+ export declare const PACK_UNDECLARED = "pack_undeclared";
499
+ /**
500
+ * The pack's `body_type` is not one this reader knows to carry `events[]`
501
+ * (`CASE_BODY_TYPES`).
502
+ */
503
+ export declare const PACK_BODY_TYPE_UNKNOWN = "pack_body_type_unknown";
504
+ /**
505
+ * The pack carries a body this reader knows and no records in it. A pack that
506
+ * contributes nothing is named rather than passed over.
507
+ */
508
+ export declare const PACK_HOLDS_NO_RECORDS = "pack_holds_no_records";
509
+ /**
510
+ * The key the pack's wrapper names is not one this reader can read as a key
511
+ * (`normalizedSigningKey` returns nothing for it), so who signed the pack is
512
+ * UNKNOWN. The pack is not verified and none of its records are read, and
513
+ * every one-sided record whose named key signed no verified pack reads
514
+ * `COUNTERPART_ABSENT_SIGNER_UNKNOWN`.
515
+ *
516
+ * Found by constructing it, while enumerating this module's exits: the wrapper
517
+ * key is the ONE input that separates the two counterpart kinds, and a key this
518
+ * reader could not read used to drop out of that set in silence. The Python
519
+ * `verify_evidence_bundle` accepts a wrapper `public_key` with ASCII
520
+ * whitespace inside it or around it (`bytes.fromhex` skips it; this kernel's
521
+ * verifier rejects it), so a counterparty that signed its own pack under
522
+ * `" " + key` handed a Python reader a pack that verified, contributed no key,
523
+ * and moved the subject from `COUNTERPART_ABSENT_FROM_COUNTERPARTY_RECORDS` to
524
+ * the innocent `COUNTERPART_ABSENT`. The same repair 4.1 made for a RECORD
525
+ * whose envelope key it cannot read (proof#133, fourth hunt): a key a reader
526
+ * holds and cannot read is named, never treated as absent. It is `incomplete`
527
+ * rather than `malformed` because whether the spelling verifies is the envelope
528
+ * layer's call (proof#135), and this reader only knows it cannot read it.
529
+ */
530
+ export declare const PACK_KEY_UNREADABLE = "pack_key_unreadable";
531
+ /**
532
+ * The pack does not verify as a bundle: `verifyEvidenceBundle`, run here
533
+ * offline over the pack and its side content, returned `failed` or
534
+ * `malformed`. None of its records are read, because a record from it would
535
+ * reach 4.1 without the verified envelope `HeldRecord` requires, and the key
536
+ * its wrapper claims is never counted as a pack signer: an unverified wrapper
537
+ * claiming the counterparty's key is exactly what an accusing party would
538
+ * forge.
539
+ *
540
+ * `partial` is not this exception. `partial` means the wrapper signature and
541
+ * `body_hash` held and part of the body could not be determined, which is what
542
+ * an unknown body or withheld bytes produce, and those are named on their own.
543
+ *
544
+ * `failed`: a pack in the case is proven broken.
545
+ */
546
+ export declare const PACK_DOES_NOT_VERIFY = "pack_does_not_verify";
547
+ /**
548
+ * The pack's wrapper HOLDS and its own bundle verdict, from
549
+ * `verifyEvidenceBundle` run here offline over the pack and the content handed
550
+ * in beside it, is not `verified`. The exception carries that verdict
551
+ * (`status`) and the verifier's reasons (`reasons`: its bundle errors, then its
552
+ * stamp errors, then its checkpoint errors, each as the verifier wrote it), and
553
+ * it CONTRIBUTES that verdict, so the case verdict never reads above its
554
+ * weakest pack. Gate 3 on proof#140: two unwitnessed (`partial`) packs read as
555
+ * a `verified` case with no exception. It does not say who is at fault:
556
+ * content handed in beside a pack is not signed by its holder.
557
+ */
558
+ export declare const PACK_BUNDLE_NOT_VERIFIED = "pack_bundle_not_verified";
559
+ /**
560
+ * A record in a pack whose wrapper holds, whose OWN envelope does not verify
561
+ * (`EventResult.envelopeOk` is false). Not read by 4.1, and NAMED with the
562
+ * verifier's reasons. `failed`: a pack its holder signed carries a record that
563
+ * is proven not to verify. As a reason to drop the whole pack it let the
564
+ * counterparty add one junk event to its own pack, re-seal it, and strip a
565
+ * proven `message_hash` failure out of the case (gate 3 on proof#140).
566
+ */
567
+ export declare const RECORD_ENVELOPE_DOES_NOT_VERIFY = "record_envelope_does_not_verify";
568
+ /**
569
+ * A record whose envelope verifies and which fails another check the bundle
570
+ * verifier grades it on inside its pack (its inclusion proof, its join to a
571
+ * checkpoint or stamp, a retired field name). NAMED with the verifier's
572
+ * reasons, `failed`, and the record STAYS in the pile: a holder that could
573
+ * remove its own tampered receipt from pairing by breaking that receipt's
574
+ * inclusion proof would be choosing its own reading.
575
+ */
576
+ export declare const RECORD_DOES_NOT_VERIFY_IN_ITS_PACK = "record_does_not_verify_in_its_pack";
577
+ /**
578
+ * A record whose envelope verifies and whose disclosed content does not hash to
579
+ * the `content_hash` it commits to (`EventResult.contentOk` is false). `failed`,
580
+ * and the record STAYS in the pile: content beside a pack is unsigned and often
581
+ * supplied by whoever assembled the case, and gate 3 on proof#140 showed that
582
+ * party erasing an honest verified pair by swapping it.
583
+ */
584
+ export declare const RECORD_CONTENT_MISMATCH = "record_content_mismatch";
585
+ /**
586
+ * A record signed under a ratified profile that is under quarantine
587
+ * (`CASE_QUARANTINED_PROFILES`, pending DL-63). Not read, in any kernel.
588
+ * `incomplete`: whether it verifies is what the kernels disagree about.
589
+ */
590
+ export declare const RECORD_PROFILE_QUARANTINED = "record_profile_quarantined";
591
+ /** The reader was handed no packs at all. */
592
+ export declare const CASE_HOLDS_NO_PACKS = "case_holds_no_packs";
593
+ /**
594
+ * Every pack was read and not one hand-off record came out of any of them.
595
+ * There is no hand-off to have a verdict about, and saying `verified` over that
596
+ * would be the defect class in its purest form.
597
+ */
598
+ export declare const CASE_HOLDS_NO_HANDOFF_RECORDS = "case_holds_no_handoff_records";
599
+ /**
600
+ * Every exception this reader can report, as a closed set. A surface may render
601
+ * them and a test may assert over them; nothing outside this array is emitted.
602
+ */
603
+ 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"];
604
+ export type CaseExceptionKind = (typeof CASE_EXCEPTION_KINDS)[number];
605
+ /**
606
+ * One pack in a case, as a reader was handed it.
607
+ *
608
+ * `ref` is the reader's own name for the pack (a file name, an archive member)
609
+ * and is never read from the pack itself: a value inside the pack is written by
610
+ * the party that assembled it, and two packs are free to claim one name.
611
+ *
612
+ * A caller passes packs as it received them. `readCase` runs
613
+ * `verifyEvidenceBundle` over each one itself, so `HeldRecord`'s precondition
614
+ * (every envelope it compares has verified) is met by this module and cannot
615
+ * be skipped by a caller. A pack that does not verify is
616
+ * `PACK_DOES_NOT_VERIFY`.
617
+ */
618
+ export interface CasePack {
619
+ ref: string;
620
+ bundle: unknown;
621
+ content?: SideContentMap | null;
622
+ }
623
+ /** One named exception, as data. A surface renders its own prose or calls
624
+ * `renderException`. */
625
+ export interface CaseException {
626
+ kind: CaseExceptionKind;
627
+ /** Which pack this is about, by the reader's own name for it. */
628
+ packRef?: string;
629
+ /** Which record, by its envelope `event_id`. */
630
+ eventId?: string;
631
+ /**
632
+ * The counterparty key the record named, for the two counterpart kinds.
633
+ * Normalized (`normalizedSigningKey`), so a reader compares what this module
634
+ * compared.
635
+ */
636
+ counterpartyKey?: string | null;
637
+ /** Which checks a formed pair failed, in 4.1's fixed order. */
638
+ failedChecks?: string[];
639
+ /** How many of a thing the exception is about, where it is about a count. */
640
+ count?: number;
641
+ /** The pack's own bundle verdict, for `PACK_BUNDLE_NOT_VERIFIED`. */
642
+ status?: CaseVerdict;
643
+ /**
644
+ * What the bundle verifier said, as it said it, for the kinds that carry a
645
+ * verifier's grading: `PACK_BUNDLE_NOT_VERIFIED` and the three record kinds
646
+ * graded on the record's own envelope and content.
647
+ */
648
+ reasons?: string[];
649
+ }
650
+ /**
651
+ * 4.1's reading of one record the case holds, with the pack it came from.
652
+ *
653
+ * EVERY record every pack supplied and this reader could read gets one of
654
+ * these, in pack order and then in bundle order, including the ones that are
655
+ * not hand-off records at all (`findings` carries `not_a_handoff_record`). 4.1
656
+ * (v1.9, A14) requires that and names the reason: the one thing an attacker
657
+ * wants is for a record to be absent from the output.
658
+ */
659
+ export interface CaseRecordReading {
660
+ packRef: string;
661
+ eventId: string;
662
+ verdict: string | null;
663
+ findings: string[];
664
+ failedChecks: string[];
665
+ counterparts: string[];
666
+ }
667
+ export interface CaseReading {
668
+ verdict: CaseVerdict;
669
+ /**
670
+ * Every exception, in a fixed order: the pack-level ones for each pack in the
671
+ * order the reader was given the packs, then the record-level ones in pack
672
+ * order and bundle order.
673
+ *
674
+ * **An empty array is the only shape a reader may read as clean**, and it is
675
+ * reachable only together with `verdict === "verified"`.
676
+ */
677
+ exceptions: CaseException[];
678
+ /** One reading per record the case holds and this reader could read. */
679
+ records: CaseRecordReading[];
680
+ /**
681
+ * Every `[contributor, verdict]` the case verdict was computed from, in
682
+ * order. Carried so the verdict is auditable rather than asserted, and so two
683
+ * kernels compare the derivation and not only its answer.
684
+ */
685
+ contributions: [string, CaseVerdict][];
686
+ /** How many hand-off records the case held. Zero is its own exception. */
687
+ handoffRecords: number;
688
+ /**
689
+ * Whether any witness behind any pack in this case was re-fetched on chain.
690
+ * It is **false** in every reading this function produces, and it is here to
691
+ * be READ rather than inferred.
692
+ *
693
+ * Gate 4 on proof#140, finding 3. A pack is `partial` only when `anchor_ref`
694
+ * is ABSENT, so the honest omission is named and the unverifiable assertion
695
+ * is not: a holder that writes any `anchor_ref` at all reads `verified`,
696
+ * because this reader makes no network call. That is the standing lens
697
+ * inverted, and the answer is not to refuse the assertion, which no offline
698
+ * reader can grade. It is to say what was not checked, so nobody reads a
699
+ * clean case as "a witness held". Re-fetching on chain stays the caller's
700
+ * job, exactly as `evidence-bundle.md` 5.5's does-not-claim list says.
701
+ *
702
+ * Derived from each pack's own `EvidenceBundleResult.onChainChecked` rather
703
+ * than written as a constant, so it stays true of what was actually run:
704
+ * this reader passes no gateways, and it is the ALL of an empty list of
705
+ * checked packs, which is false.
706
+ */
707
+ onChainChecked: boolean;
708
+ }
709
+ /**
710
+ * Read a whole case: several packs in, one verdict and its exceptions out.
711
+ *
712
+ * The order of the work, and why it is this order:
713
+ *
714
+ * 1. **Each pack is read into records**, through `packRecords`, which is the
715
+ * one implementation of "what does this pack hold". A pack whose `body_type`
716
+ * this reader does not know, or which holds no records, is NAMED here and
717
+ * its records are not silently zero.
718
+ * 2. **Each pack is graded against its own declaration**, through
719
+ * `checkDeclaration`, which is the one implementation of 5.4. A pack proven
720
+ * to deviate from its own claim fails the case.
721
+ * 3. **Every record every pack supplied goes into ONE pile**, with no partition
722
+ * by pack, by issuer or by organization, and the pile is read by
723
+ * `readHandoffs`, which is the one implementation of 4.1. This is where
724
+ * matching happens, and it is on keys and hashes alone.
725
+ * 4. **Each reading becomes an exception or a clean contribution**, and the two
726
+ * counterpart kinds are separated by asking whether a pack SIGNED by the
727
+ * counterparty's key is in the case, a key read from every pack's wrapper
728
+ * before any exit can skip it.
729
+ *
730
+ * A record whose bytes the pack did not supply never reaches the pile, because
731
+ * there is nothing to put in it. That is exactly the shape this module has to
732
+ * refuse to pass over in silence, so it is named at step 1 and carries
733
+ * `incomplete`: the reader genuinely cannot tell whether the record it cannot
734
+ * see is the counterpart that is missing.
735
+ */
736
+ export declare function readCase(packs: readonly CasePack[]): Promise<CaseReading>;
737
+ export declare function renderException(exception: CaseException): string;
738
+ /**
739
+ * One line a surface can print for a whole case.
740
+ *
741
+ * Both kernels print this sentence, so a reader who runs the Python command and
742
+ * the TypeScript one over one case archive reads one sentence rather than two
743
+ * that differ by a word.
744
+ */
745
+ export declare function renderCaseSummary(reading: CaseReading): string;