@sanning/proof 0.19.1 → 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 +339 -822
  9. package/dist/case.js +667 -1160
  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 +814 -1328
  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 +131 -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 +573 -581
  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
@@ -1,152 +1,47 @@
1
- // The declaration: what a pack SAYS it holds, checked against what it does.
1
+ // The declaration: what a pack says it holds, checked against what it does
2
+ // (evidence-bundle.md §5.4).
2
3
  //
3
- // WHY THIS EXISTS.
4
- // A pack is a selection out of an interval. Every per-record check in this
5
- // kernel is a check on a record that is PRESENT: signatures, payload bindings,
6
- // inclusion proofs, content hashes. Drop half the records and every survivor
7
- // still verifies perfectly, so an auditor holding a pack cannot tell a full
8
- // hand-over from a trimmed one. `completenessNotes` in `./grading.ts` closes
9
- // one narrow case of that, where a checkpoint's own signed record commits to a
10
- // leaf count. Nothing closes it for a hand-over pack built out of a stamp, and
11
- // nothing at all says which records were withheld ON PURPOSE.
4
+ // Every per-record check verifies a record that is present, so a trimmed pack
5
+ // verifies as well as a full one. The declaration is the pack's own signed
6
+ // statement of what it was meant to contain. It rides inside `body`, so the
7
+ // bundle signature and `body_hash` cover it.
12
8
  //
13
- // The declaration is the pack's own signed statement of what it was meant to
14
- // contain: which agents, which period, which case, the records expected, which
15
- // of those are disclosed and which are withheld deliberately, and the hand-off
16
- // pairs it claims. It rides INSIDE `body`, so the bundle's signature covers it
17
- // and `body_hash` commits to it: the claim has an author, and re-declaring a
18
- // different selection later means signing a different pack.
9
+ // verified the pack holds exactly what the declaration says. A record
10
+ // declared `content_withheld` is checked for its signature and
11
+ // commitment only.
12
+ // failed any deviation, and every deviation is named. A deviation never
13
+ // surfaces as an absence a reader is left to interpret.
14
+ // incomplete no declaration, or one this reader cannot read. Nothing to
15
+ // check against, and nothing proven wrong.
19
16
  //
20
- // ── the rule ────────────────────────────────────────────────────────────────
17
+ // When no record declared `disclosed` supplied bytes, nothing was selected:
18
+ // the bytes travelled separately. That is a note, not a deviation, and the
19
+ // missing bytes still carry the pack to `incomplete`. Partial disclosure is a
20
+ // selection and fails.
21
21
  //
22
- // verified the pack holds exactly what the declaration says it holds. A
23
- // record declared `content_withheld` is in scope for its
24
- // signature and its commitment only, and its absent bytes are
25
- // not a deviation, because the declaration said so in advance.
26
- // failed ANY deviation, and EVERY deviation is named. Declared and
27
- // absent, present and undeclared, the pack carrying one
28
- // `event_id` twice, withheld without being declared, disclosed
29
- // after being declared withheld, or a claimed hand-off naming a
30
- // record the declaration never declared.
31
- // partial no declaration, or one the reader cannot interpret as this
32
- // version. The pack makes no claim this reader can check, so
33
- // what it was meant to contain is UNKNOWN. Never `verified`,
34
- // because there is nothing to check against, and never `failed`,
35
- // because nothing is wrong: absence is not failure.
22
+ // Pure: data in, data out. "The holder supplied bytes" is defined once, in
23
+ // `./side-content.ts`. This check does not verify that a claimed hand-off pair
24
+ // is a real pair (the case verdict does), but it does require each claimed side
25
+ // to be a conformant hand-off record (`./handoff.ts`).
36
26
  //
37
- // A deviation is a FAILURE and is NAMED. It must never surface as an absence
38
- // a reader is left to interpret. That is the shape the hand-off record
39
- // contract caught in review (anchor#189): a check made a condition of pairing
40
- // turns a deviation into two records that did not pair for no stated reason,
41
- // and a reader reads a missing thing as an innocent one.
42
- //
43
- // ── the bytes that did not travel ────────────────────────────────────
44
- //
45
- // One case inside rule 3 is not a selection and is not graded as one. When NO
46
- // record declared `disclosed` supplied bytes, nothing was selected: either the
47
- // bytes travelled separately or the reader was handed the JSON alone. A
48
- // hostile holder gains nothing from that shape, because declaring those
49
- // records `content_withheld` grades `verified` today and is cheaper, so the
50
- // only party who reaches it is the honest one who forgot `--logs`. This check
51
- // emits a NAMED note instead of a deviation, and the pack's missing bytes
52
- // carry it to `incomplete` through the disclosure account that already counts
53
- // them.
54
- //
55
- // PARTIAL disclosure is a selection and stays a deviation: nineteen of twenty
56
- // declared-disclosed records supplying bytes and one not is a choice about
57
- // which record, and it fails.
58
- //
59
- // ── the seam ────────────────────────────────────────────────────────────────
60
- //
61
- // PURE, like `./grading.ts`: data in, data out, no I/O and no key lifecycle.
62
- // It reads the bundle it is handed and, optionally, the same side-content map
63
- // `verifyEvidenceBundle` takes, because "the holder supplied bytes for this
64
- // record" has exactly one definition in this kernel (`./side-content.ts`) and
65
- // this module calls it rather than restating it.
66
- //
67
- // ── what this check does NOT claim ──────────────────────────────────────────
68
- //
69
- // It compares the declaration to the pack. It does not read a record's
70
- // internals and cross-check `agents`, `period` or `case_id` against them, and
71
- // it does not verify that a claimed hand-off PAIR is a real pair: the hashes
72
- // match, the sequence holds, the two sides name each other's keys. Those are
73
- // the CASE verdict, across packs and across organizations, and they are
74
- // anchor#198. Doing half of that here would put two implementations of one
75
- // rule in the stack, which is the defect `./grading.ts` was brought into the
76
- // kernel to end.
77
- //
78
- // It does now check that each SIDE of a claimed hand-off is a conformant
79
- // hand-off record (proof#136, ratified by Phil 2026-09-22), and it asks
80
- // `./handoff.ts` rather than restating that rule. `handoffs[]` carried the
81
- // claim and checked nothing about it, and that is what made an unreadable
82
- // record useful: a receiving side respelled one code point of `event_type`
83
- // and one of `event.counterparty_public_key`, which put the record outside
84
- // envelope-spec.md 4.1 by both of its scope clauses, then declared it and
85
- // claimed it as the received half. The record layer said nothing and the pack
86
- // layer said `verified`. A record cannot be unreadable now and usable later.
87
- //
88
- // What that does NOT close (proof#137, ratified by Phil 2026-09-22). The
89
- // sentence deleted from the spec beside this one said the other door was
90
- // closed too: omit the record from `handoffs[]` and it is a
91
- // present-and-undeclared record. It is not. `handoffs[]` MAY be empty, so a
92
- // holder declares the record in `records[]` and claims it nowhere, and then
93
- // rule 2 cannot reach it because it is declared and rule 7 cannot reach it
94
- // because it is unclaimed: this check reads `verified`, exactly as it does
95
- // over the honest pack. The corpus pins that pack as EXPECTED
96
- // (`declaration/declaration-unclaimed-record-01`). A pack a party assembles
97
- // about itself is not self-proving about what that party LEFT OUT, because
98
- // the omission happens before any reader runs; the closure is the case
99
- // verdict across BOTH parties' packs, anchor#198.
100
- //
101
- // ── the three defects the sixth hunt landed on this check (proof#137) ───────
102
- //
103
- // Three. The fourth door the hunt found, whether a pack a party assembles
104
- // about itself can prove what it LEFT OUT, is a founder call and is not
105
- // answered here.
106
- //
107
- // ONE PARSER, NOT TWO BUILT-INS. The two kernels read one `event_bytes`
108
- // differently, three of the four ways verdict-level on identical bytes:
109
- // Python's `json.loads` accepts `NaN`, `Infinity` and `-Infinity` and this
110
- // kernel's `JSON.parse` rejects them, and Python's `.decode("utf-8")` keeps
111
- // a leading byte-order mark where `TextDecoder` eats it. One pack read
112
- // `verified` here and `failed[pair-record-not-a-handoff]` there. The rule is
113
- // now stated rather than delegated: bytes that are not RFC 8785 JSON are not
114
- // a conformant record, so a leading BOM and each of the three non-finite
115
- // literals are rejected in BOTH kernels, spelled out in each, exactly as the
116
- // hex charset already was.
117
- //
118
- // ONE `event_id`, TWO RECORDS. Both kernels built a map by id, which is
119
- // last-write-wins in both languages, so a second record under a declared id
120
- // was graded by neither rule 2 nor rule 7 and was never reported as surplus.
121
- // FIRST in bundle order is now the record every other rule grades, and every
122
- // record after it under the same id is a `duplicate_event_id` deviation.
123
- //
124
- // MINIMUM DISCLOSURE IS NOT A DEVIATION. A claimed side whose `event_bytes`
125
- // the pack withholds was graded "not a conformant hand-off record", so an
126
- // honest holder exercising the disclosure posture this product sells proved
127
- // a deviation, and the cheapest escape from that was to stop claiming pairs
128
- // at all. A withheld side now gets its OWN reading, distinct from both
129
- // conformant and non-conformant, and is a NAMED NOTE.
27
+ // A pack cannot prove what its holder left out: a record declared in
28
+ // `records[]` and claimed in no `handoffs[]` entry reads `verified` here. The
29
+ // case verdict across both parties' packs closes that.
130
30
  import { hexToBytes, utf8 } from "./crypto.js";
131
31
  import { jcs, jcsParse } from "./verifier.js";
132
32
  import { isConformantHandoffRecord, isHandoffNamespace } from "./handoff.js";
133
33
  import { hasSideContent, inBodyContentOffered, sideContentEntry, } from "./side-content.js";
134
- /** The first declaration schema, which every earlier reader knows. */
34
+ /** The first declaration schema. */
135
35
  export const DECLARATION_VERSION = "sanning.declaration/v1";
136
- /**
137
- * v1 plus a signed `records[].role` on every record (evidence-bundle.md
138
- * 5.4.1 v1.20, A21). A reader that knows only v1 reads a v2 declaration as one
139
- * it cannot read, which is the safe direction.
140
- */
36
+ /** v1 plus a signed `records[].role` on every record (§5.4.1). */
141
37
  export const DECLARATION_VERSION_V2 = "sanning.declaration/v2";
142
38
  /**
143
39
  * v2 plus a signed `chains[]`: the `chain_key`s the holder claims are complete
144
- * and in order (evidence-bundle.md 5.4.1 v1.36, A45). A reader that knows only
145
- * v1 and v2 reads a v3 declaration as one it cannot read, which is the safe
40
+ * and in order (§5.4.1). An older reader cannot read it, which is the safe
146
41
  * direction.
147
42
  */
148
43
  export const DECLARATION_VERSION_V3 = "sanning.declaration/v3";
149
- /** Every declaration schema this reader knows, in the order they were added. */
44
+ /** Every declaration schema this reader knows, oldest first. */
150
45
  export const DECLARATION_VERSIONS = [
151
46
  DECLARATION_VERSION,
152
47
  DECLARATION_VERSION_V2,
@@ -155,13 +50,11 @@ export const DECLARATION_VERSIONS = [
155
50
  /** The versions whose every `records[]` entry carries a signed `role`. */
156
51
  const ROLE_VERSIONS = [DECLARATION_VERSION_V2, DECLARATION_VERSION_V3];
157
52
  /**
158
- * What a v2 declaration may sign a record IS, compared exactly: `handoff`, a
159
- * record 4.1 governs that the pack claims as a side in `handoffs[]`, or
160
- * `step`, any other record.
53
+ * The roles a v2 declaration can sign, compared exactly: `handoff` for a
54
+ * record §4.1 governs that the pack claims in `handoffs[]`, `step` otherwise.
161
55
  */
162
56
  export const DECLARATION_ROLES = ["handoff", "step"];
163
- /** The three states a declaration check can reach, from the §3 vocabulary. */
164
- export const DECLARATION_VERDICTS = ["verified", "failed", "partial"];
57
+ export const DECLARATION_VERDICTS = ["verified", "failed", "incomplete"];
165
58
  export const DECLARATION_DEVIATION_KINDS = [
166
59
  /** The declaration is written to this version and is not readable as one. */
167
60
  "declaration_malformed",
@@ -169,160 +62,86 @@ export const DECLARATION_DEVIATION_KINDS = [
169
62
  "declared_absent",
170
63
  /** A record in the pack the declaration never expected. */
171
64
  "undeclared_present",
172
- /**
173
- * A record declared `content_withheld` whose bytes the pack supplies INSIDE
174
- * its own signed body, in `events[].content`.
175
- */
65
+ /** A record declared `content_withheld` whose bytes the pack's own body supplies. */
176
66
  "withheld_but_disclosed",
177
67
  /** The pack carries one `event_id` on two or more records. */
178
68
  "duplicate_event_id",
179
69
  /** A claimed hand-off names a record the declaration does not declare. */
180
70
  "pair_record_undeclared",
181
- /**
182
- * A claimed hand-off names a record that is not a conformant hand-off
183
- * record of the side it is claimed on (envelope-spec.md 4.1).
184
- */
71
+ /** A claimed hand-off side that is not a conformant hand-off record of that side (§4.1). */
185
72
  "pair_record_not_a_handoff",
186
- /**
187
- * Rule 9 (v1.20, A21), `sanning.declaration/v2` only. A record declared
188
- * `handoff` that no `handoffs[]` entry names as a side.
189
- */
73
+ /** v2 only: a record declared `handoff` that no `handoffs[]` entry claims. */
190
74
  "role_handoff_unclaimed",
191
75
  /** A record a `handoffs[]` entry names as a side, declared `step`. */
192
76
  "role_step_claimed",
193
- /**
194
- * A record declared `step` that 4.1's scope test puts in scope: its `event`
195
- * carries `counterparty_public_key`, or its folded `event_type` has a
196
- * `handoff` segment. The holder signed that a hand-off record is not one.
197
- */
77
+ /** A record declared `step` that §4.1's scope test puts in scope. */
198
78
  "role_step_governed",
199
- /**
200
- * A record declared `step` whose supplied `event_bytes` this reader cannot
201
- * read under rule 7's parser rule.
202
- */
79
+ /** A record declared `step` whose supplied `event_bytes` cannot be read. */
203
80
  "role_step_unreadable",
204
- /**
205
- * Rule 11 (v1.36, A45), `sanning.declaration/v3` only. The holder signed
206
- * that a chain is complete and in order, and a second record under that key,
207
- * under any producing key, begins it.
208
- */
81
+ /** v3 only: a second record beginning a claimed chain, under any producing key. */
209
82
  "chain_second_root",
210
83
  /** A record of a claimed chain is the second successor of one predecessor. */
211
84
  "chain_fork",
212
- /** A record of a claimed chain points at a predecessor its chain does not present in the pack. */
85
+ /** A claimed chain's record points at a predecessor the pack does not present. */
213
86
  "chain_link_dangling",
214
87
  /** A claimed chain of which the pack presents no record at all. */
215
88
  "chain_claimed_absent",
216
89
  ];
217
90
  /**
218
- * Every way a pack and the UNSIGNED content handed in beside it fail to line
219
- * up, as a closed set. Neither of these is a deviation, and neither of them
220
- * fails (proof#147, ratified by Phil 2026-09-22).
221
- *
222
- * WHY THEY ARE NOT DEVIATIONS. 5.4 grades a pack's declaration partly against
223
- * content the reader was HANDED, and that content is not signed by anybody.
224
- * `events[].content` rides inside `body`, so the wrapper signature and
225
- * `body_hash` cover it and what it says about the holder is the holder's own
226
- * word. The side-content map is the other channel, and whoever assembles a
227
- * case fills it in: an accuser could put an intact pack into
228
- * `pack_declaration_deviates`, which contributed `failed`, WITH NO KEY AT
229
- * ALL, by dropping the bytes of a record the pack declared `disclosed` or by
230
- * adding bytes for one it declared `content_withheld`. The pack was intact,
231
- * its wrapper verified, and its holder had done nothing.
232
- *
233
- * This is the mirror of proof#143. There a counterparty makes itself look
234
- * better by choosing what it hands over; here an accuser makes a counterparty
235
- * look worse the same way. Both now read as what the READER established, so
236
- * `failed` stays for what a signature covers.
237
- *
238
- * WHAT A HOSTILE HOLDER GAINS FROM THE WEAKER WORD: nothing, because a
239
- * cheaper move already dominates it. Declaring a record `content_withheld` is
240
- * legal and grades `verified`, which is strictly better than the `incomplete`
241
- * these carry.
91
+ * Every way a pack and the unsigned content handed in beside it fail to line
92
+ * up. Not deviations, and never `failed`: whoever assembles a case fills the
93
+ * side-content map, so an accuser could otherwise fail an intact pack with no
94
+ * key at all. `failed` stays for what a signature covers.
242
95
  */
243
96
  export const DECLARATION_CONTENT_KINDS = [
244
97
  /**
245
- * A record declared `disclosed` whose bytes reached this reader from
246
- * neither channel. ALWAYS content-dependent: 5.4.1 lets those bytes travel
247
- * beside the pack as `logs/<event_id>.json`, so "no bytes arrived" is a
248
- * fact about what the READER was handed and never a fact about what the
249
- * holder supplied. No signature covers the absence of a file nobody signed.
98
+ * A record declared `disclosed` whose bytes arrived from neither channel.
99
+ * Always content-dependent: the bytes may travel beside the pack, so this is
100
+ * a fact about what the reader was handed.
250
101
  */
251
102
  "content_dependent_withholding",
252
103
  /**
253
- * A record declared `content_withheld` whose bytes reached this reader ONLY
254
- * from the unsigned side-content map. The pack's own body supplies none, so
255
- * the holder committed to no such disclosure. The in-body half of the same
256
- * rule is the `withheld_but_disclosed` DEVIATION and still fails.
104
+ * A record declared `content_withheld` whose bytes arrived only through the
105
+ * side-content map. The in-body case is the `withheld_but_disclosed`
106
+ * deviation.
257
107
  */
258
108
  "content_dependent_disclosure",
259
109
  ];
260
- /** Everything this check reports that is NOT a deviation, as a closed set. */
110
+ /** Everything this check reports that is not a deviation. */
261
111
  export const DECLARATION_NOTE_KINDS = [
262
- /**
263
- * Every record declared `disclosed` supplied no bytes, so nothing was
264
- * selected: the bytes did not travel with the pack.
265
- */
112
+ /** No record declared `disclosed` supplied bytes: they did not travel with the pack. */
266
113
  "disclosure_did_not_travel",
267
114
  /**
268
- * A claimed hand-off side whose `event_bytes` the pack withholds. The reader
269
- * cannot see what the record is either way, so it is neither a conformant
270
- * hand-off record nor a non-conformant one.
115
+ * A claimed hand-off side whose `event_bytes` the pack withholds. Withholding
116
+ * is minimum disclosure, so it is neither conformant nor non-conformant.
271
117
  */
272
118
  "pair_record_withheld",
273
119
  /**
274
- * A record a v2 declaration signs `step` whose `event_bytes` the pack
275
- * withholds (rule 9, v1.20). Its role cannot be checked in either direction,
276
- * so it is a note; the pack grade still holds the pack at `incomplete`,
277
- * because its payload binding is undetermined.
120
+ * A record a v2 declaration signs `step` whose `event_bytes` are withheld, so
121
+ * its role cannot be checked. The undetermined binding keeps the pack at
122
+ * `incomplete`.
278
123
  */
279
124
  "role_step_withheld",
280
125
  /**
281
- * Rule 10 (v1.29, A36). A record whose signed `event_type` is `declaration`
282
- * and whose content HANDED IN to this reader is exactly `JCS(object)` of a
283
- * declaration object of a version this reader knows, carrying exactly that
284
- * version's keys, and not this pack's own. The note names the content handed
285
- * in, never the record: whether those bytes bind to the record's
286
- * `content_hash` is the bundle verifier's check, and an accuser can hand in
287
- * declaration-shaped bytes for an honest record. What the pack holds is what
288
- * the declaration in its body says, so this moves no verdict.
126
+ * A record signed as `event_type` `declaration` whose handed-in content is
127
+ * `JCS` of a known declaration other than this pack's own. Names the content,
128
+ * not the record: an accuser can hand in such bytes for an honest record.
129
+ * Moves no verdict.
289
130
  */
290
131
  "other_declaration_held",
291
- /**
292
- * Rule 10. A record whose signed `event_type` is `declaration` and whose
293
- * content did not reach this reader from either channel, so whether it is a
294
- * declaration other than this pack's own is UNKNOWN.
295
- */
132
+ /** A record signed `declaration` whose content did not arrive, so what it declares is unknown. */
296
133
  "other_declaration_withheld",
297
134
  /**
298
- * Rule 11 (v1.36, A45). A claimed chain has a dangling link, and a record
299
- * signed by the same producing key has an undetermined payload binding. That
300
- * record takes no part in a chain, so whether the missing link is it is
301
- * UNKNOWN. Moves nothing: the undetermined binding holds the pack at
302
- * `incomplete`.
135
+ * A claimed chain has a dangling link, and a same-key record with an
136
+ * undetermined payload binding could be the missing link. Moves nothing.
303
137
  */
304
138
  "chain_claim_withheld",
305
139
  ];
306
140
  /**
307
- * The record committed inside one `events[]` entry, or `null`.
308
- *
309
- * `null` is "this reader cannot read the record", which is not the same as
310
- * "the record is fine": a claimed hand-off side whose bytes the pack supplied
311
- * and this reader cannot read is a deviation, because the claim is that it IS
312
- * a hand-off record and nothing here supports that. It is not the same as "the
313
- * pack supplied no bytes" either: see `bytesSupplied`.
314
- *
315
- * ONE RULE, SPELLED OUT, BECAUSE TWO BUILT-INS ARE NOT ONE RULE. The bytes are
316
- * `JCS(event record)` (RFC 8785) or they are not a conformant record. Three
317
- * checks carry that: lowercase-or-uppercase hex in pairs, no leading byte-order
318
- * mark, and no non-finite literal. The hex is `hexToBytes`, because it and
319
- * `bytes.fromhex` disagree about embedded whitespace (proof#135); the rest is
320
- * `jcsParse`, which every site in either kernel that reads signed bytes now
321
- * shares (proof#140 gate 5). Both halves were a hand-rolled copy in this module
322
- * until proof#146, and both are the shared reader now: the answer on every
323
- * input is the same, and there is one fewer place for the rule to drift. The
324
- * cross-kernel declaration leg and `declaration.test.ts` hold both kernels to
325
- * the same answer on the same bytes rather than to either built-in.
141
+ * The record committed inside one `events[]` entry, or `null` where this
142
+ * reader cannot read it. Bytes that are not `JCS(event record)` are not a
143
+ * conformant record; parsing goes through the shared `hexToBytes` and
144
+ * `jcsParse` so both kernels agree on every input.
326
145
  */
327
146
  function committedRecord(raw) {
328
147
  let parsed;
@@ -334,20 +153,11 @@ function committedRecord(raw) {
334
153
  }
335
154
  return isObject(parsed) ? parsed : null;
336
155
  }
337
- // Which record type each side of a claimed hand-off has to be. A pack that
338
- // names its receipt as the sent side has mislabeled a record, and the reading
339
- // layer grades the two types differently.
340
- const SIDE_EVENT_TYPES = { sent: "handoff.sent", received: "handoff.received" };
156
+ // A pack that names its receipt as the sent side has mislabelled a record.
157
+ const SIDE_EVENT_TYPES = { sent: "agent.handoff.sent", received: "agent.handoff.received" };
341
158
  /**
342
- * How a claimed side reads: conformant, withheld, or not a hand-off record.
343
- *
344
- * The definition of conformant is 4.1's own (`isConformantHandoffRecord`),
345
- * read once and never restated here, plus the record type the side names.
346
- *
347
- * WITHHELD IS ITS OWN READING. The pack supplied no `event_bytes`, so this
348
- * reader cannot see what the record is in either direction. Saying it is not a
349
- * hand-off record is a claim the reader cannot support, and it punishes the
350
- * disclosure posture the product sells.
159
+ * How a claimed side reads. Conformant is §4.1's own rule
160
+ * (`isConformantHandoffRecord`) plus the record type the side names.
351
161
  */
352
162
  function sideReading(p, side) {
353
163
  if (!p.bytesSupplied)
@@ -361,11 +171,8 @@ function sideReading(p, side) {
361
171
  }
362
172
  const bodyOf = (bundle) => bundle?.body;
363
173
  /**
364
- * The pack's own records, in bundle order. Exported so the CASE layer
365
- * (`./case.ts`, anchor#198) reads a pack through the SAME parser that grades
366
- * it here, rather than through a second one beside it. A validation copied
367
- * next to the one that enforces it drifts, and a copy that is looser blesses
368
- * a value the real one then rejects.
174
+ * The pack's own records, in bundle order. Exported so `./case.ts` reads a
175
+ * pack through the same parser that grades it.
369
176
  */
370
177
  export function packRecords(bundle, content) {
371
178
  const events = bodyOf(bundle)?.events;
@@ -375,19 +182,13 @@ export function packRecords(bundle, content) {
375
182
  const raw = ev?.envelope?.event_id;
376
183
  const nameable = typeof raw === "string" && raw.length > 0;
377
184
  const eventId = nameable ? raw : `#${i + 1}`;
378
- // One definition of "the holder supplied bytes", in both of the two
379
- // shapes a pack can supply them: in the body, or beside it. Both are
380
- // `./side-content.ts`, never restated here.
381
185
  const disclosedInBody = inBodyContentOffered(ev?.content);
382
186
  const disclosed = disclosedInBody || (nameable && hasSideContent(content, eventId));
383
- // The key comes from the ENVELOPE and never from inside the record, which
384
- // is 4.1's whole point: a value that names no key makes the record
385
- // `malformed` there, so a claimed side carrying one is a deviation here.
187
+ // From the envelope, never the record: an unparseable key makes the record
188
+ // `malformed` under §4.1.
386
189
  const key = ev?.envelope?.public_key;
387
- // The family convention for "the holder supplied this hex field": a
388
- // non-empty string. Anything else, an absent key, a `null` or a number, is
389
- // the field not travelling, which is minimum disclosure and not a record
390
- // this reader was handed and could not read.
190
+ // Only a non-empty string counts as supplied; anything else is the field
191
+ // not travelling.
391
192
  const rawBytes = ev?.event_bytes;
392
193
  return {
393
194
  eventId,
@@ -402,28 +203,18 @@ export function packRecords(bundle, content) {
402
203
  };
403
204
  });
404
205
  }
405
- // ---- Reading the declaration -------------------------------------------------
406
206
  const DISCLOSURE_STATES = new Set(["disclosed", "content_withheld"]);
407
- // RFC 3339 `date-time` with an uppercase `Z`, the family's convention for an
408
- // instant. Format only: this is not an ordering check and not a calendar
409
- // check, and it deliberately does not restate the `signed_at` rule of
410
- // envelope-spec §2, which grades a different field on a different layer.
207
+ // RFC 3339 `date-time` with an uppercase `Z`. Format only, not ordering.
411
208
  const INSTANT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/;
412
209
  const isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
413
210
  const isId = (v) => typeof v === "string" && v.length > 0;
414
211
  /**
415
- * Read a declaration written to the version this reader knows, or say what is
416
- * wrong with it.
417
- *
418
- * Every malformation is collected, not the first: a declaration is a claim,
419
- * and a reader that names one defect in a claim with four sends its holder
420
- * round the loop four times.
212
+ * Read a declaration of a known version, or collect every malformation in it
213
+ * (all of them, so the holder fixes them in one pass).
421
214
  */
422
215
  function readDeclaration(declaration, version = DECLARATION_VERSION) {
423
216
  const reasons = [];
424
- // Under v2 every `records[]` entry must also carry `role`, exactly `handoff`
425
- // or `step` (5.4.1 v1.20, A21); under v1 the member is not read at all, so a
426
- // v1 declaration carrying `role` reads exactly as the same object without it.
217
+ // v2 and v3 require `role` on every entry; v1 does not read the member.
427
218
  const roles = [];
428
219
  const v2 = ROLE_VERSIONS.includes(version);
429
220
  const note = (r) => reasons.push(r);
@@ -463,8 +254,7 @@ function readDeclaration(declaration, version = DECLARATION_VERSION) {
463
254
  note(`records[${i}] (${eventId}) has no disclosure of "disclosed" or "content_withheld"`);
464
255
  continue;
465
256
  }
466
- // Compared exactly: `Handoff`, `hand-off` and `step ` are other values,
467
- // and a non-string never equals one.
257
+ // Exact: `Handoff`, `hand-off` and `step ` are other values.
468
258
  const role = r["role"];
469
259
  if (v2 && !(role === "handoff" || role === "step")) {
470
260
  note(`records[${i}] (${eventId}) has no role of "handoff" or "step"`);
@@ -504,8 +294,7 @@ function readDeclaration(declaration, version = DECLARATION_VERSION) {
504
294
  pairs.push({ sent: sent, received: received });
505
295
  }
506
296
  }
507
- // v3 is v2 plus a required `chains[]` (5.4.1 v1.36, A45); v1 and v2 do not
508
- // read the member.
297
+ // v3 requires `chains[]`; v1 and v2 do not read the member.
509
298
  const chains = [];
510
299
  if (version === DECLARATION_VERSION_V3) {
511
300
  const rawChains = declaration["chains"];
@@ -530,9 +319,8 @@ function readDeclaration(declaration, version = DECLARATION_VERSION) {
530
319
  ? { ok: false, reasons }
531
320
  : { ok: true, read: { records, pairs, roles, chains } };
532
321
  }
533
- // ---- The check ---------------------------------------------------------------
534
322
  const noDeclaration = () => ({
535
- verdict: "partial",
323
+ verdict: "incomplete",
536
324
  deviations: [],
537
325
  contentFindings: [],
538
326
  notes: [],
@@ -544,9 +332,7 @@ const noDeclaration = () => ({
544
332
  chains: [],
545
333
  });
546
334
  /**
547
- * The `declaration_version` of an object this reader knows, or `null`: a
548
- * declaration that is not an object, an absent or `null` version and a version
549
- * outside `DECLARATION_VERSIONS` are each a claim this reader cannot read.
335
+ * The `declaration_version` of a declaration this reader knows, or `null`.
550
336
  */
551
337
  function knownVersion(declaration) {
552
338
  if (!isObject(declaration))
@@ -557,10 +343,8 @@ function knownVersion(declaration) {
557
343
  : null;
558
344
  }
559
345
  /**
560
- * Rule 9 over one record the pack holds: the deviation or note it earns, or
561
- * `null` where the signed role agrees with the pack and the record. The first
562
- * of the four deviations that applies, in the order 5.4.2 lists them. Whether
563
- * 4.1 governs the record is `isHandoffNamespace`, CALLED, never restated.
346
+ * Rule 9 for one held record: the first deviation that applies in §5.4.2
347
+ * order, a note, or `null` where the signed role agrees.
564
348
  */
565
349
  function roleReading(p, role, claimed) {
566
350
  if (role === "handoff")
@@ -576,16 +360,9 @@ function roleReading(p, role, claimed) {
576
360
  }
577
361
  return null;
578
362
  }
579
- /**
580
- * The `event_type` a declaration record is signed under: a reserved
581
- * single-segment name, and the one `@sanning/anchor`'s `bundle` anchors a
582
- * declaration as.
583
- */
363
+ /** The reserved `event_type` a declaration record is signed under. */
584
364
  const DECLARATION_EVENT_TYPE = "declaration";
585
- /**
586
- * The keys a declaration object carries, under every version this reader
587
- * knows: exactly these, no more and no fewer (5.4.1).
588
- */
365
+ /** The exact keys of a declaration object, under every known version (§5.4.1). */
589
366
  const DECLARATION_KEYS = ["declaration_version", "case_id", "agents", "period", "records", "handoffs"];
590
367
  function hasExactKeys(value, keys) {
591
368
  if (!isObject(value))
@@ -594,14 +371,9 @@ function hasExactKeys(value, keys) {
594
371
  return have.length === keys.length && keys.every((k) => Object.prototype.hasOwnProperty.call(value, k));
595
372
  }
596
373
  /**
597
- * Whether `obj` is shaped like a declaration this reader knows: one of
598
- * `DECLARATION_VERSIONS` compared exactly, exactly the keys that version
599
- * defines, and the value types this module reads.
600
- *
601
- * It is the shape test `@sanning/anchor`'s `isDeclarationShape` makes, and it
602
- * checks shape only, not the 5.4.1 rules `readDeclaration` grades: a stale
603
- * declaration is somebody else's claim, and this reader does not grade it, it
604
- * names it.
374
+ * Whether `obj` has the shape of a known declaration: a known version, exactly
375
+ * its keys, and the value types read here. Shape only: another holder's stale
376
+ * declaration is named, not graded.
605
377
  */
606
378
  function isDeclarationShape(obj) {
607
379
  if (!isObject(obj))
@@ -644,9 +416,8 @@ function isDeclarationShape(obj) {
644
416
  chains.every((c) => hasExactKeys(c, ["chain_key"]) && typeof c["chain_key"] === "string"));
645
417
  }
646
418
  /**
647
- * The content bytes disclosed for one record, or `null` where none are usable:
648
- * `events[].content` first, then the side-content map, chosen the way the
649
- * bundle verifier chooses the bytes it binds.
419
+ * The content bytes disclosed for one record, or `null`: `events[].content`
420
+ * first, then the side-content map, as the bundle verifier chooses.
650
421
  */
651
422
  function disclosedBytes(ev, p, content) {
652
423
  const inBody = isObject(ev) ? ev["content"] : undefined;
@@ -682,9 +453,8 @@ function sameBytes(a, b) {
682
453
  return true;
683
454
  }
684
455
  /**
685
- * Whether `raw` is exactly `JCS(object)` of a declaration-shaped object.
686
- * Canonical bytes, not merely parseable ones: a declaration is anchored as
687
- * `JCS(declaration)`, and nothing an agent writes by accident.
456
+ * Whether `raw` is exactly `JCS` of a declaration-shaped object. Canonical
457
+ * bytes only, so an agent does not write one by accident.
688
458
  */
689
459
  function isDeclarationBytes(raw) {
690
460
  try {
@@ -697,13 +467,8 @@ function isDeclarationBytes(raw) {
697
467
  }
698
468
  /**
699
469
  * Rule 10: every record that is a declaration other than the pack's own, in
700
- * bundle order.
701
- *
702
- * The record's `event_type` is read from its signed `event_bytes`, so a record
703
- * whose bytes the pack withholds is not named: the reader cannot see what it
704
- * was signed as. Its content is read from either channel; a record whose
705
- * content is byte for byte `JCS` of the pack's own declaration is the pack's
706
- * own and is never named.
470
+ * bundle order. The `event_type` comes from the signed `event_bytes`, so a
471
+ * record with withheld bytes is not named.
707
472
  */
708
473
  function otherDeclarationNotes(bundle, pack, declaration, content) {
709
474
  const events = pack.length > 0 ? (bodyOf(bundle)?.events ?? []) : [];
@@ -720,9 +485,8 @@ function otherDeclarationNotes(bundle, pack, declaration, content) {
720
485
  return;
721
486
  const raw = disclosedBytes(events[i], p, content);
722
487
  if (raw === null) {
723
- // Offered and unusable is not withheld: something arrived, it is not a
724
- // declaration, and the bundle verifier names what is wrong with it.
725
- // Nothing arrived is the reading the reader cannot settle.
488
+ // Unusable content is the bundle verifier's finding; only content that
489
+ // never arrived is unknown here.
726
490
  if (!p.disclosed)
727
491
  notes.push({ kind: "other_declaration_withheld", eventId: p.eventId });
728
492
  return;
@@ -737,46 +501,23 @@ function otherDeclarationNotes(bundle, pack, declaration, content) {
737
501
  /**
738
502
  * Check a pack against the declaration inside its body.
739
503
  *
740
- * `content` is the same side-content map `verifyEvidenceBundle` takes, and is
741
- * optional: a pack that discloses its bytes in the body needs no side input,
742
- * and a pack that discloses them beside itself passes the map a consumer
743
- * already built.
744
- *
745
- * `chains` is `readChains` over the SAME bundle. Only a v3 declaration that
746
- * claims a chain reads it (rule 11). It is an argument rather than computed
747
- * here because `readChains` is asynchronous and this check is not, and Python's
748
- * `check_declaration` takes it the same way so the two kernels share one
749
- * contract. A v3 declaration that claims a chain, checked without it, reads as
750
- * a reader that does not know v3 reads it: `partial`, nothing named, and the
751
- * pack `incomplete`. Leaving it out can never read `verified`.
504
+ * @param content - The side-content map `verifyEvidenceBundle` takes.
505
+ * @param chains - `readChains` over the same bundle, read only for a v3 chain
506
+ * claim. Omitted, such a claim reads `incomplete`, never `verified`.
752
507
  *
753
- * The verdict is this check's own. It does not restate the bundle verdict and
754
- * it does not read it: a declaration can match perfectly over a pack whose
755
- * signatures are forged, and saying so is `gradePack`'s job, which takes both.
508
+ * The verdict does not read the bundle verdict: a declaration can match over
509
+ * forged signatures, and `gradePack` combines the two.
756
510
  */
757
511
  export function checkDeclaration(bundle, content, links) {
758
- // `body.declaration` as WRITTEN, not as hoped. The key being absent and the
759
- // key holding a string, an array or `null` are graded differently and must
760
- // not collapse into one another: a body with no `declaration` key claimed
761
- // nothing, which every pack built before §5.4 did, while a body whose
762
- // `declaration` will not read DID claim something. Reading the second as the
763
- // first is how a one-field edit to a re-sealed pack graded clean.
512
+ // An absent `declaration` claimed nothing; one present but unreadable did
513
+ // claim something. The two must not collapse into one.
764
514
  const body = bodyOf(bundle);
765
515
  if (!isObject(body) || !("declaration" in body))
766
516
  return noDeclaration();
767
517
  const declaration = body["declaration"];
768
- // A declaration this reader cannot interpret as `sanning.declaration/v1` is
769
- // UNKNOWN and not an accusation: the same posture §4 step 1 takes for an
770
- // unknown `body_type`, one layer down. `present` says a declaration was
771
- // THERE, so a surface can tell "nothing was claimed" from "something was
772
- // claimed in a language I do not speak", and `gradePack` carries the second
773
- // to `incomplete`.
774
- //
775
- // Every shape that lands here is one field away from a valid declared pack
776
- // the holder can re-seal with its own key: an unknown `declaration_version`,
777
- // a deleted one, a null one, and a `declaration` that is a string, an array
778
- // or null. Each of those hides the evidence of a trim inside a signed body,
779
- // so none of them may reach a clean pack verdict.
518
+ // An unreadable declaration is unknown, not an accusation, but each such
519
+ // shape is one field from a valid re-sealed pack, so none may reach a clean
520
+ // pack verdict.
780
521
  const version = knownVersion(declaration);
781
522
  if (version === null) {
782
523
  return { ...noDeclaration(), present: true };
@@ -804,37 +545,21 @@ export function checkDeclaration(bundle, content, links) {
804
545
  const declared = records.map((r) => r.eventId);
805
546
  const declaredSet = new Set(declared);
806
547
  const pack = packRecords(bundle, content);
807
- // FIRST WINS, and the second record under one id is named below. A map built
808
- // by id is last-write-wins in both languages, so a pack carrying one
809
- // `event_id` twice had its second record graded by rule 7 and its first by
810
- // nothing, or the reverse once the order flipped, and neither record was
811
- // ever reported as surplus (proof#137, defect 2). First in bundle order is
812
- // the record every other rule grades, and it does not depend on how the
813
- // holder ordered `events[]`.
548
+ // First in bundle order wins; later records under the same id are named.
549
+ // A map by id would be last-write-wins and grade one of them by nothing.
814
550
  const byId = new Map();
815
551
  for (const p of pack) {
816
552
  if (p.nameable && !byId.has(p.eventId))
817
553
  byId.set(p.eventId, p);
818
554
  }
819
555
  const deviations = [];
820
- // Declared and absent. The trimmed hand-over, which is the whole reason a
821
- // declaration exists: nothing else in the pack can see it.
556
+ // Declared and absent: the trimmed hand-over.
822
557
  for (const r of records) {
823
558
  if (!byId.has(r.eventId))
824
559
  deviations.push({ kind: "declared_absent", eventId: r.eventId });
825
560
  }
826
- // Present and undeclared, and the pack carrying one id twice.
827
- //
828
- // An unnameable record is present-and-undeclared, because a record the
829
- // declaration could not have named is surplus by construction.
830
- //
831
- // A record under an id the pack already carries is named SEPARATELY, in
832
- // bundle order, and is not also reported as surplus: one defect, one
833
- // finding. 5.4.1 says declaring an id twice is a malformed declaration and
834
- // said nothing about the PACK carrying one twice, so a second record under a
835
- // declared id was graded by no rule at all and was never reported
836
- // (proof#137, defect 2). A declaration names records by id, so an id that
837
- // names two records names neither.
561
+ // Present and undeclared. An unnameable record is surplus by construction.
562
+ // A repeated id is named once as `duplicate_event_id`, not also as surplus.
838
563
  const seenInPack = new Set();
839
564
  for (const p of pack) {
840
565
  if (p.nameable && seenInPack.has(p.eventId)) {
@@ -847,11 +572,8 @@ export function checkDeclaration(bundle, content, links) {
847
572
  deviations.push({ kind: "undeclared_present", eventId: p.eventId });
848
573
  }
849
574
  }
850
- // The bytes that did not travel. Over the records declared `disclosed` that
851
- // the pack actually holds: if NONE of them supplied bytes, no record was
852
- // selected over another and the honest reading is that the bytes travelled
853
- // separately, so this is a note and not a deviation. One of them supplying
854
- // bytes and another not IS a selection, and every shortfall is named.
575
+ // If no held `disclosed` record supplied bytes, nothing was selected: a note.
576
+ // Some supplying and some not is a selection, and every shortfall is named.
855
577
  const disclosedHeld = records
856
578
  .filter((r) => !r.withheld)
857
579
  .map((r) => byId.get(r.eventId))
@@ -860,18 +582,9 @@ export function checkDeclaration(bundle, content, links) {
860
582
  const notes = nothingTravelled
861
583
  ? [{ kind: "disclosure_did_not_travel", count: disclosedHeld.length }]
862
584
  : [];
863
- // The two disclosure rules, in declaration order, over the records the pack
864
- // actually holds: an absent record is already named above, and naming it
865
- // twice would report one defect as two.
866
- //
867
- // EACH OF THEM SPLITS ON WHICH CHANNEL THE BYTES CAME FROM (proof#147,
868
- // ratified by Phil 2026-09-22). `events[].content` is inside the signed
869
- // body, so what it says is the holder's own word and a mismatch with the
870
- // holder's own declaration is a proven deviation. The side-content map is
871
- // unsigned and is filled in by whoever assembled the case, so a reading
872
- // that rests on it says what the READER established and not what the holder
873
- // did. The weaker readings are named, and they carry the check to `partial`
874
- // and the pack to `incomplete`.
585
+ // The disclosure rules, over held records only. Each splits on the channel:
586
+ // in-body content is the holder's signed word and a mismatch is a deviation;
587
+ // side content is unsigned and yields a content finding.
875
588
  const contentFindings = [];
876
589
  for (const r of records) {
877
590
  const p = byId.get(r.eventId);
@@ -880,10 +593,7 @@ export function checkDeclaration(bundle, content, links) {
880
593
  if (!r.withheld && !p.disclosed) {
881
594
  if (nothingTravelled)
882
595
  continue;
883
- // ALWAYS content-dependent. 5.4.1 lets a record's bytes travel beside
884
- // the pack, so no pack can prove it supplied them and no reader can
885
- // prove it did not: an accuser holding a twenty-file drop reaches this
886
- // reading by deleting one file.
596
+ // Always content-dependent: deleting one file beside the pack reaches it.
887
597
  contentFindings.push({ kind: "content_dependent_withholding", eventId: r.eventId });
888
598
  }
889
599
  else if (r.withheld && p.disclosed) {
@@ -895,26 +605,9 @@ export function checkDeclaration(bundle, content, links) {
895
605
  }
896
606
  }
897
607
  }
898
- // A claimed hand-off that names a record the declaration does not declare,
899
- // or names one that is not a conformant hand-off record of that side.
900
- //
901
- // The PAIR itself, that the hashes match and the sequence holds, is the case
902
- // verdict (anchor#198) and is deliberately not checked here. Each SIDE is
903
- // checked, and that is the belt for 4.1's own repair: a record respelled out
904
- // of 4.1's scope was unreadable at the record layer and still usable here,
905
- // because this array carried the claim and checked nothing about it
906
- // (proof#136). A record declared and absent is already named above and is
907
- // not named twice.
908
- //
909
- // A claimed side whose `event_bytes` the pack WITHHELD is a NAMED NOTE and
910
- // not a deviation (proof#137, defect 3). `event_bytes` is OPTIONAL, and a
911
- // holder that withholds it is exercising minimum disclosure, which this
912
- // corpus calls a customer's right and not a defect. Grading it "not a
913
- // conformant hand-off record" made an honest pack prove a deviation over a
914
- // record the reader cannot see in either direction, and the cheapest escape
915
- // from that was to stop claiming pairs, which is the door proof#137 is
916
- // about. A withheld side is still not a clean claim: the pack's own
917
- // disclosure account already carries those absent bytes to `incomplete`.
608
+ // Each claimed side must be declared and be a conformant hand-off record of
609
+ // that side. A withheld side is a note, not a deviation. The pair itself is
610
+ // the case verdict's to check.
918
611
  for (const [i, pair] of pairs.entries()) {
919
612
  for (const side of ["sent", "received"]) {
920
613
  const id = pair[side];
@@ -936,12 +629,8 @@ export function checkDeclaration(bundle, content, links) {
936
629
  }
937
630
  }
938
631
  }
939
- // Rule 9 (5.4.2 v1.20, A21), `sanning.declaration/v2` only: a record's
940
- // signed role against the pack and against the record. Its deviations follow
941
- // every deviation rules 1 to 8 report, and its note follows every other
942
- // note, both in `records[]` order. It reads the signed body alone and NEVER
943
- // the side-content map, so no party that signed nothing can create, move or
944
- // remove one of its findings.
632
+ // Rule 9, v2 and v3: reads the signed body alone, never the side-content
633
+ // map, so no party that signed nothing can move its findings.
945
634
  if (roles.length > 0) {
946
635
  const claimed = new Set();
947
636
  for (const pair of pairs) {
@@ -952,7 +641,7 @@ export function checkDeclaration(bundle, content, links) {
952
641
  }
953
642
  for (const { eventId, role } of roles) {
954
643
  const p = byId.get(eventId);
955
- // Declared and absent: rule 1 named it, and one defect is one finding.
644
+ // Already named by rule 1.
956
645
  if (p === undefined)
957
646
  continue;
958
647
  const kind = roleReading(p, role, claimed.has(eventId));
@@ -962,33 +651,22 @@ export function checkDeclaration(bundle, content, links) {
962
651
  deviations.push({ kind, eventId });
963
652
  }
964
653
  }
965
- // Rule 11 (5.4.2 v1.36, A45), `sanning.declaration/v3` only: a chain the
966
- // holder signed as complete and in order, against the chain reading of the
967
- // same pack. Its deviations follow rule 9's, and its notes follow rule 9's
968
- // and precede rule 10's, both in `chains[]` order.
969
- // A caller that passed no links leaves the claim UNREAD: rules 1 to 10 still
970
- // run, and the verdict below is never `verified`.
654
+ // Rule 11, v3 only. Without chain links the claim is unread, and the verdict
655
+ // is never `verified`.
971
656
  const unread = claimedChains.length > 0 && (links === undefined || links === null);
972
657
  if (claimedChains.length > 0 && links !== undefined && links !== null) {
973
658
  const found = chainClaimFindings(claimedChains, links, pack);
974
659
  deviations.push(...found.deviations);
975
660
  notes.push(...found.notes);
976
661
  }
977
- // Rule 10 (5.4.2 v1.29, A36): content handed in for a record signed
978
- // `declaration` that is a declaration other than this pack's own. Notes only, in bundle order, after every other note, and
979
- // never a deviation: the pack holds what its own declaration says whatever
980
- // one of its records says.
662
+ // Rule 10: notes only, after every other note.
981
663
  notes.push(...otherDeclarationNotes(bundle, pack, declaration, content));
982
- // A proven deviation wins: it is a mismatch between what the pack claimed
983
- // and what its own signed body carries, and a failing check always wins.
984
- // With no deviation and a content-dependent finding, the pack lines up with
985
- // its claim everywhere a signature reaches and does not line up somewhere
986
- // only unsigned bytes could settle, which is UNDETERMINED: `partial` is
987
- // 3's word for that, and it adds none.
664
+ // A deviation always wins. Otherwise a content finding is undetermined:
665
+ // `incomplete`.
988
666
  const verdict = deviations.length > 0
989
667
  ? "failed"
990
668
  : contentFindings.length > 0 || unread
991
- ? "partial"
669
+ ? "incomplete"
992
670
  : "verified";
993
671
  return {
994
672
  verdict,
@@ -1004,22 +682,17 @@ export function checkDeclaration(bundle, content, links) {
1004
682
  };
1005
683
  }
1006
684
  /**
1007
- * Rule 11 over every claimed `chain_key`, in `chains[]` order. The twin of
1008
- * Python's `_chain_claim_findings`, which states the rule in full.
685
+ * Rule 11 over every claimed `chain_key`, in `chains[]` order. Python's twin is
686
+ * `_chain_claim_findings`.
1009
687
  *
1010
- * A claim covers every record whose OWN signed linkage names the key
1011
- * (`source === "record"`), under every producing key; an adapter's metadata
1012
- * linkage is never read for a claim. Under a claim, in bundle order: every
1013
- * record after the first that begins the chain is a `chain_second_root`; a
1014
- * record whose predecessor already has an earlier successor in its chain is a
1015
- * `chain_fork`; a record whose pointer resolves to no record of its own chain
1016
- * (one producing key, one `chain_key`) is a `chain_link_dangling`, with no
1017
- * window allowance; a claimed key with no such record is
1018
- * `chain_claimed_absent`. Links are read from the record each envelope binds,
1019
- * `event_bytes` or an inline `payload`. A dangling link is the note
1020
- * `chain_claim_withheld` instead, once per key, only where its pointer equals
1021
- * the signed `payload_hash` of a same-key record whose binding is
1022
- * undetermined: exactly the missing link. Nothing else is excused.
688
+ * A claim covers every record whose own signed linkage names the key, under
689
+ * every producing key; adapter metadata linkage is never read. In bundle order:
690
+ * a record after the first root is `chain_second_root`; a second successor of
691
+ * one predecessor is `chain_fork`; a pointer to no record of its own chain is
692
+ * `chain_link_dangling`; a claimed key with no record is `chain_claimed_absent`.
693
+ * A dangling link is excused as the note `chain_claim_withheld` only where its
694
+ * pointer equals the `payload_hash` of a same-key record with an undetermined
695
+ * binding.
1023
696
  */
1024
697
  function chainClaimFindings(claimed, links, pack) {
1025
698
  const deviations = [];
@@ -1068,12 +741,8 @@ function chainClaimFindings(claimed, links, pack) {
1068
741
  return { deviations, notes };
1069
742
  }
1070
743
  /**
1071
- * Read the claim a pack's declaration makes, without grading the pack.
1072
- *
1073
- * Deliberately total: a bundle this reader cannot read comes back with
1074
- * `readable: false` rather than throwing, because a case reader grades what it
1075
- * was handed and a reader that throws on a hostile pack cannot report that the
1076
- * pack is hostile.
744
+ * Read the claim a pack's declaration makes, without grading the pack. Total:
745
+ * never throws, so a hostile pack is reported rather than crashing the reader.
1077
746
  */
1078
747
  export function declarationClaims(bundle) {
1079
748
  const none = {
@@ -1112,68 +781,57 @@ export function declarationClaims(bundle) {
1112
781
  roles: read.read.roles,
1113
782
  };
1114
783
  }
784
+ /**
785
+ * One line a surface can print beside the pack verdict. Both kernels print the
786
+ * same sentence.
787
+ */
1115
788
  export function renderDeclarationSummary(check) {
1116
789
  if (!check.present) {
1117
- return "this pack does not say what it holds, so whether it is complete is UNKNOWN";
790
+ return "this pack does not say what it holds, so whether it is complete is unknown";
1118
791
  }
1119
792
  if (check.verdict === "failed") {
1120
- // READ BEFORE THE CONTENT-FINDINGS BRANCH BELOW. A failing check always
1121
- // wins (section 5.4.3: a pack with one deviation and one content-dependent
1122
- // finding is `failed`, and the finding is still named), so a pack that
1123
- // deviates gets the deviating sentence even when it also carries findings.
1124
- // Ordered the other way round, a pack holding both printed "this pack
1125
- // agrees with its own declaration everywhere its signature reaches" with
1126
- // its own named deviations listed underneath, which is the reading this
1127
- // section exists to prevent: a deviation surfacing as something a reader
1128
- // is left to interpret.
793
+ // Before the content-findings branch: a deviation always wins.
1129
794
  if (check.contentFindings.length > 0) {
1130
- // BOTH counts, because the two facts are about different records and a
1131
- // reader shown one of them would look in the wrong place.
1132
- return (`this pack DEVIATES from its own declaration, in ${check.deviations.length} way(s), ` +
795
+ // Both counts: the two facts are about different records.
796
+ return (`this pack deviates from its own declaration, in ${check.deviations.length} way(s), ` +
1133
797
  `and in ${check.contentFindings.length} further way(s) it does not line up with ` +
1134
798
  "the unsigned content handed in beside it, which anyone could have supplied");
1135
799
  }
1136
- return `this pack DEVIATES from its own declaration, in ${check.deviations.length} way(s)`;
800
+ return `this pack deviates from its own declaration, in ${check.deviations.length} way(s)`;
1137
801
  }
1138
802
  if (check.contentFindings.length > 0) {
1139
- // Read before the `partial` branch below, because both reach this verdict
1140
- // and they are two different sentences. This one says what the reader
1141
- // could not establish; the one below says the reader could not read the
1142
- // claim at all.
803
+ // Before the `incomplete` branch below: this says what the reader could not
804
+ // establish, that one says the claim could not be read.
1143
805
  return ("this pack agrees with its own declaration everywhere its signature reaches, " +
1144
806
  `and in ${check.contentFindings.length} way(s) it does not line up with the ` +
1145
807
  "unsigned content handed in beside it, which anyone could have supplied");
1146
808
  }
1147
- if (check.verdict === "partial" && (check.chains ?? []).length > 0) {
1148
- // Read before the branch below: the declaration WAS read, and its chain
1149
- // claim was not checked because the caller gave no reading.
809
+ if (check.verdict === "incomplete" && (check.chains ?? []).length > 0) {
810
+ // The declaration was read; its chain claim was not checked.
1150
811
  return ("this pack holds what it declares, and says its chains are complete " +
1151
812
  "and in order, which this verifier did not check, so whether they " +
1152
- "are is UNKNOWN");
813
+ "are is unknown");
1153
814
  }
1154
- if (check.verdict === "partial") {
815
+ if (check.verdict === "incomplete") {
1155
816
  return ("this pack says what it holds in a declaration this verifier cannot read, " +
1156
- "so whether it is complete is UNKNOWN");
817
+ "so whether it is complete is unknown");
1157
818
  }
1158
819
  return (`this pack holds exactly what it declares: ${check.declared.length} record(s), ` +
1159
820
  `${check.declaredWithheld.length} withheld on purpose`);
1160
821
  }
1161
- /**
1162
- * The house wording for one note. Offered, not imposed, as `renderDeviation`
1163
- * is.
1164
- */
822
+ /** The house wording for one note. */
1165
823
  export function renderNote(note) {
1166
824
  switch (note.kind) {
1167
825
  case "disclosure_did_not_travel":
1168
- return (`this pack declares ${note.count} record(s) disclosed and supplies no bytes for ` +
1169
- "any of them: the bytes did not travel with the pack, so their content is UNKNOWN");
826
+ return (`this pack declares ${note.count} ${note.count === 1 ? "record" : "records"} disclosed and supplies no bytes for ` +
827
+ "any of them: the bytes did not travel with the pack, so their content is unknown");
1170
828
  case "pair_record_withheld":
1171
829
  return (`the claimed hand-off at position ${note.pairIndex} names ${note.eventId} on its ` +
1172
830
  `${note.side} side, whose record bytes this pack withholds, so what that record ` +
1173
- "IS is UNKNOWN");
831
+ "is, is unknown");
1174
832
  case "role_step_withheld":
1175
833
  return (`${note.eventId} is signed as a step and this pack withholds its ` +
1176
- "record bytes, so whether it is one is UNKNOWN");
834
+ "record bytes, so whether it is one is unknown");
1177
835
  case "other_declaration_held":
1178
836
  return (`the content handed in for ${note.eventId} is a declaration that is ` +
1179
837
  "not this pack's own: it says what some pack holds, and what this " +
@@ -1181,7 +839,7 @@ export function renderNote(note) {
1181
839
  case "other_declaration_withheld":
1182
840
  return (`${note.eventId} is signed as a declaration and no content for it ` +
1183
841
  "reached this verifier, so whether it is a declaration other than " +
1184
- "this pack's own is UNKNOWN");
842
+ "this pack's own is unknown");
1185
843
  case "chain_claim_withheld":
1186
844
  return (`this pack declares chain ${note.chainKey} complete and in order, ` +
1187
845
  "and a link in it points at a record its producer signed and this " +
@@ -1190,34 +848,33 @@ export function renderNote(note) {
1190
848
  }
1191
849
  }
1192
850
  /**
1193
- * The house wording for one content-dependent finding.
1194
- *
1195
- * EVERY SENTENCE HERE NAMES THE CONTENT AND NOT THE HOLDER. That is the whole
1196
- * point of the kind: the bytes are unsigned and whoever assembled the case
1197
- * chose them, so a sentence that reads as an accusation would put the weaker
1198
- * word beside the stronger claim and an auditor would read the claim.
851
+ * The house wording for one content finding. Names the content, never the
852
+ * holder: the bytes are unsigned.
1199
853
  */
1200
854
  export function renderContentFinding(finding) {
1201
855
  switch (finding.kind) {
1202
856
  case "content_dependent_withholding":
1203
857
  return (`${finding.eventId} is declared disclosed and no bytes for it reached this ` +
1204
858
  "verifier, from the pack or from beside it, so whether the holder supplied " +
1205
- "them is UNKNOWN");
859
+ "them is unknown");
1206
860
  case "content_dependent_disclosure":
1207
861
  return (`${finding.eventId} is declared withheld and bytes for it were handed in ` +
1208
862
  "beside this pack, which the pack does not carry and nobody signed, so " +
1209
- "whether the holder disclosed them is UNKNOWN");
863
+ "whether the holder disclosed them is unknown");
1210
864
  }
1211
865
  }
866
+ /**
867
+ * The house wording for one deviation. Both kernels return the same text.
868
+ */
1212
869
  export function renderDeviation(deviation) {
1213
870
  switch (deviation.kind) {
1214
871
  case "declaration_malformed":
1215
872
  return ("the declaration is not readable as " +
1216
873
  `${deviation.version || DECLARATION_VERSION}: ${deviation.reason}`);
1217
874
  case "declared_absent":
1218
- return `${deviation.eventId} is declared and is NOT in this pack`;
875
+ return `${deviation.eventId} is declared and is not in this pack`;
1219
876
  case "undeclared_present":
1220
- return `${deviation.eventId} is in this pack and is NOT declared`;
877
+ return `${deviation.eventId} is in this pack and is not declared`;
1221
878
  case "withheld_but_disclosed":
1222
879
  return (`${deviation.eventId} is declared withheld and this pack carries its bytes ` +
1223
880
  "in its own signed body");