@sanning/proof 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -12
- package/dist/agent-proof.d.ts +2 -2
- package/dist/agent-proof.js +4 -4
- package/dist/case-file.d.ts +126 -0
- package/dist/case-file.js +349 -0
- package/dist/case.d.ts +173 -26
- package/dist/case.js +469 -123
- package/dist/cli.d.ts +38 -5
- package/dist/cli.js +557 -208
- package/dist/crypto.d.ts +1 -1
- package/dist/crypto.js +5 -1
- package/dist/declaration.d.ts +58 -52
- package/dist/declaration.js +256 -33
- package/dist/evidence.d.ts +2 -0
- package/dist/evidence.js +134 -19
- package/dist/exit-codes.d.ts +17 -0
- package/dist/exit-codes.js +50 -0
- package/dist/grading.d.ts +28 -13
- package/dist/grading.js +90 -31
- package/dist/index.d.ts +10 -7
- package/dist/index.js +13 -6
- package/dist/message-parts.d.ts +11 -9
- package/dist/message-parts.js +11 -9
- package/dist/types.d.ts +1 -0
- package/dist/verifier.d.ts +9 -2
- package/dist/verifier.js +28 -12
- package/dist/zip-reader.d.ts +50 -0
- package/dist/zip-reader.js +220 -0
- package/package.json +2 -2
- package/src/agent-proof.ts +4 -4
- package/src/case-file.ts +419 -0
- package/src/case.ts +586 -132
- package/src/cli.ts +651 -206
- package/src/crypto.ts +5 -1
- package/src/declaration.ts +248 -52
- package/src/evidence.ts +142 -21
- package/src/exit-codes.ts +54 -0
- package/src/grading.ts +109 -52
- package/src/index.ts +58 -2
- package/src/message-parts.ts +11 -9
- package/src/types.ts +5 -0
- package/src/verifier.ts +35 -13
- package/src/zip-reader.ts +229 -0
- package/vectors/README.md +23 -5
- package/vectors/case-file-index.json +668 -0
- package/vectors/case-split.json +218 -9
- package/vectors/pack-grading.json +68 -4
- package/vectors/stamp-on-chain.json +3 -3
package/dist/declaration.js
CHANGED
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
// last-write-wins in both languages, so a second record under a declared id
|
|
120
120
|
// was graded by neither rule 2 nor rule 7 and was never reported as surplus.
|
|
121
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 `
|
|
122
|
+
// record after it under the same id is a `duplicate_event_id` deviation.
|
|
123
123
|
//
|
|
124
124
|
// MINIMUM DISCLOSURE IS NOT A DEVIATION. A claimed side whose `event_bytes`
|
|
125
125
|
// the pack withholds was graded "not a conformant hand-off record", so an
|
|
@@ -132,10 +132,130 @@
|
|
|
132
132
|
// `src/sanning_proof/canonicalize.py` is the twin (proof#140 gate 5).
|
|
133
133
|
import { hexToBytes } from "./crypto.js";
|
|
134
134
|
import { jcsParse } from "./verifier.js";
|
|
135
|
-
import { isConformantHandoffRecord } from "./handoff.js";
|
|
135
|
+
import { isConformantHandoffRecord, isHandoffNamespace } from "./handoff.js";
|
|
136
136
|
import { hasSideContent, inBodyContentOffered } from "./side-content.js";
|
|
137
|
-
/** The declaration schema
|
|
137
|
+
/** The first declaration schema, which every earlier reader knows. */
|
|
138
138
|
export const DECLARATION_VERSION = "sanning.declaration/v1";
|
|
139
|
+
/**
|
|
140
|
+
* v1 plus a signed `records[].role` on every record (evidence-bundle.md
|
|
141
|
+
* 5.4.1 v1.20, A21). A reader that knows only v1 reads a v2 declaration as one
|
|
142
|
+
* it cannot read, which is the safe direction.
|
|
143
|
+
*/
|
|
144
|
+
export const DECLARATION_VERSION_V2 = "sanning.declaration/v2";
|
|
145
|
+
/** Every declaration schema this reader knows, in the order they were added. */
|
|
146
|
+
export const DECLARATION_VERSIONS = [DECLARATION_VERSION, DECLARATION_VERSION_V2];
|
|
147
|
+
/**
|
|
148
|
+
* What a v2 declaration may sign a record IS, compared exactly: `handoff`, a
|
|
149
|
+
* record 4.1 governs that the pack claims as a side in `handoffs[]`, or
|
|
150
|
+
* `step`, any other record.
|
|
151
|
+
*/
|
|
152
|
+
export const DECLARATION_ROLES = ["handoff", "step"];
|
|
153
|
+
/** The three states a declaration check can reach, from the §3 vocabulary. */
|
|
154
|
+
export const DECLARATION_VERDICTS = ["verified", "failed", "partial"];
|
|
155
|
+
export const DECLARATION_DEVIATION_KINDS = [
|
|
156
|
+
/** The declaration is written to this version and is not readable as one. */
|
|
157
|
+
"declaration_malformed",
|
|
158
|
+
/** A record the declaration expects is not in the pack. */
|
|
159
|
+
"declared_absent",
|
|
160
|
+
/** A record in the pack the declaration never expected. */
|
|
161
|
+
"undeclared_present",
|
|
162
|
+
/**
|
|
163
|
+
* A record declared `content_withheld` whose bytes the pack supplies INSIDE
|
|
164
|
+
* its own signed body, in `events[].content`.
|
|
165
|
+
*/
|
|
166
|
+
"withheld_but_disclosed",
|
|
167
|
+
/** The pack carries one `event_id` on two or more records. */
|
|
168
|
+
"duplicate_event_id",
|
|
169
|
+
/** A claimed hand-off names a record the declaration does not declare. */
|
|
170
|
+
"pair_record_undeclared",
|
|
171
|
+
/**
|
|
172
|
+
* A claimed hand-off names a record that is not a conformant hand-off
|
|
173
|
+
* record of the side it is claimed on (envelope-spec.md 4.1).
|
|
174
|
+
*/
|
|
175
|
+
"pair_record_not_a_handoff",
|
|
176
|
+
/**
|
|
177
|
+
* Rule 9 (v1.20, A21), `sanning.declaration/v2` only. A record declared
|
|
178
|
+
* `handoff` that no `handoffs[]` entry names as a side.
|
|
179
|
+
*/
|
|
180
|
+
"role_handoff_unclaimed",
|
|
181
|
+
/** A record a `handoffs[]` entry names as a side, declared `step`. */
|
|
182
|
+
"role_step_claimed",
|
|
183
|
+
/**
|
|
184
|
+
* A record declared `step` that 4.1's scope test puts in scope: its `event`
|
|
185
|
+
* carries `counterparty_public_key`, or its folded `event_type` has a
|
|
186
|
+
* `handoff` segment. The holder signed that a hand-off record is not one.
|
|
187
|
+
*/
|
|
188
|
+
"role_step_governed",
|
|
189
|
+
/**
|
|
190
|
+
* A record declared `step` whose supplied `event_bytes` this reader cannot
|
|
191
|
+
* read under rule 7's parser rule.
|
|
192
|
+
*/
|
|
193
|
+
"role_step_unreadable",
|
|
194
|
+
];
|
|
195
|
+
/**
|
|
196
|
+
* Every way a pack and the UNSIGNED content handed in beside it fail to line
|
|
197
|
+
* up, as a closed set. Neither of these is a deviation, and neither of them
|
|
198
|
+
* fails (proof#147, ratified by Phil 2026-09-22).
|
|
199
|
+
*
|
|
200
|
+
* WHY THEY ARE NOT DEVIATIONS. 5.4 grades a pack's declaration partly against
|
|
201
|
+
* content the reader was HANDED, and that content is not signed by anybody.
|
|
202
|
+
* `events[].content` rides inside `body`, so the wrapper signature and
|
|
203
|
+
* `body_hash` cover it and what it says about the holder is the holder's own
|
|
204
|
+
* word. The side-content map is the other channel, and whoever assembles a
|
|
205
|
+
* case fills it in: an accuser could put an intact pack into
|
|
206
|
+
* `pack_declaration_deviates`, which contributed `failed`, WITH NO KEY AT
|
|
207
|
+
* ALL, by dropping the bytes of a record the pack declared `disclosed` or by
|
|
208
|
+
* adding bytes for one it declared `content_withheld`. The pack was intact,
|
|
209
|
+
* its wrapper verified, and its holder had done nothing.
|
|
210
|
+
*
|
|
211
|
+
* This is the mirror of proof#143. There a counterparty makes itself look
|
|
212
|
+
* better by choosing what it hands over; here an accuser makes a counterparty
|
|
213
|
+
* look worse the same way. Both now read as what the READER established, so
|
|
214
|
+
* `failed` stays for what a signature covers.
|
|
215
|
+
*
|
|
216
|
+
* WHAT A HOSTILE HOLDER GAINS FROM THE WEAKER WORD: nothing, because a
|
|
217
|
+
* cheaper move already dominates it. Declaring a record `content_withheld` is
|
|
218
|
+
* legal and grades `verified`, which is strictly better than the `incomplete`
|
|
219
|
+
* these carry.
|
|
220
|
+
*/
|
|
221
|
+
export const DECLARATION_CONTENT_KINDS = [
|
|
222
|
+
/**
|
|
223
|
+
* A record declared `disclosed` whose bytes reached this reader from
|
|
224
|
+
* neither channel. ALWAYS content-dependent: 5.4.1 lets those bytes travel
|
|
225
|
+
* beside the pack as `logs/<event_id>.json`, so "no bytes arrived" is a
|
|
226
|
+
* fact about what the READER was handed and never a fact about what the
|
|
227
|
+
* holder supplied. No signature covers the absence of a file nobody signed.
|
|
228
|
+
*/
|
|
229
|
+
"content_dependent_withholding",
|
|
230
|
+
/**
|
|
231
|
+
* A record declared `content_withheld` whose bytes reached this reader ONLY
|
|
232
|
+
* from the unsigned side-content map. The pack's own body supplies none, so
|
|
233
|
+
* the holder committed to no such disclosure. The in-body half of the same
|
|
234
|
+
* rule is the `withheld_but_disclosed` DEVIATION and still fails.
|
|
235
|
+
*/
|
|
236
|
+
"content_dependent_disclosure",
|
|
237
|
+
];
|
|
238
|
+
/** Everything this check reports that is NOT a deviation, as a closed set. */
|
|
239
|
+
export const DECLARATION_NOTE_KINDS = [
|
|
240
|
+
/**
|
|
241
|
+
* Every record declared `disclosed` supplied no bytes, so nothing was
|
|
242
|
+
* selected: the bytes did not travel with the pack.
|
|
243
|
+
*/
|
|
244
|
+
"disclosure_did_not_travel",
|
|
245
|
+
/**
|
|
246
|
+
* A claimed hand-off side whose `event_bytes` the pack withholds. The reader
|
|
247
|
+
* cannot see what the record is either way, so it is neither a conformant
|
|
248
|
+
* hand-off record nor a non-conformant one.
|
|
249
|
+
*/
|
|
250
|
+
"pair_record_withheld",
|
|
251
|
+
/**
|
|
252
|
+
* A record a v2 declaration signs `step` whose `event_bytes` the pack
|
|
253
|
+
* withholds (rule 9, v1.20). Its role cannot be checked in either direction,
|
|
254
|
+
* so it is a note; the pack grade still holds the pack at `incomplete`,
|
|
255
|
+
* because its payload binding is undetermined.
|
|
256
|
+
*/
|
|
257
|
+
"role_step_withheld",
|
|
258
|
+
];
|
|
139
259
|
/**
|
|
140
260
|
* The record committed inside one `events[]` entry, or `null`.
|
|
141
261
|
*
|
|
@@ -250,8 +370,13 @@ const isId = (v) => typeof v === "string" && v.length > 0;
|
|
|
250
370
|
* and a reader that names one defect in a claim with four sends its holder
|
|
251
371
|
* round the loop four times.
|
|
252
372
|
*/
|
|
253
|
-
function readDeclaration(declaration) {
|
|
373
|
+
function readDeclaration(declaration, version = DECLARATION_VERSION) {
|
|
254
374
|
const reasons = [];
|
|
375
|
+
// Under v2 every `records[]` entry must also carry `role`, exactly `handoff`
|
|
376
|
+
// or `step` (5.4.1 v1.20, A21); under v1 the member is not read at all, so a
|
|
377
|
+
// v1 declaration carrying `role` reads exactly as the same object without it.
|
|
378
|
+
const roles = [];
|
|
379
|
+
const v2 = version === DECLARATION_VERSION_V2;
|
|
255
380
|
const note = (r) => reasons.push(r);
|
|
256
381
|
if (!isId(declaration["case_id"]))
|
|
257
382
|
note("case_id is not a non-empty string");
|
|
@@ -289,12 +414,21 @@ function readDeclaration(declaration) {
|
|
|
289
414
|
note(`records[${i}] (${eventId}) has no disclosure of "disclosed" or "content_withheld"`);
|
|
290
415
|
continue;
|
|
291
416
|
}
|
|
417
|
+
// Compared exactly: `Handoff`, `hand-off` and `step ` are other values,
|
|
418
|
+
// and a non-string never equals one.
|
|
419
|
+
const role = r["role"];
|
|
420
|
+
if (v2 && !(role === "handoff" || role === "step")) {
|
|
421
|
+
note(`records[${i}] (${eventId}) has no role of "handoff" or "step"`);
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
292
424
|
if (seen.has(eventId)) {
|
|
293
425
|
note(`records[${i}] declares ${eventId} a second time`);
|
|
294
426
|
continue;
|
|
295
427
|
}
|
|
296
428
|
seen.add(eventId);
|
|
297
429
|
records.push({ eventId, withheld: disclosure === "content_withheld" });
|
|
430
|
+
if (v2)
|
|
431
|
+
roles.push({ eventId, role: role });
|
|
298
432
|
}
|
|
299
433
|
}
|
|
300
434
|
const pairs = [];
|
|
@@ -321,7 +455,7 @@ function readDeclaration(declaration) {
|
|
|
321
455
|
pairs.push({ sent: sent, received: received });
|
|
322
456
|
}
|
|
323
457
|
}
|
|
324
|
-
return reasons.length > 0 ? { ok: false, reasons } : { ok: true, read: { records, pairs } };
|
|
458
|
+
return reasons.length > 0 ? { ok: false, reasons } : { ok: true, read: { records, pairs, roles } };
|
|
325
459
|
}
|
|
326
460
|
// ---- The check ---------------------------------------------------------------
|
|
327
461
|
const noDeclaration = () => ({
|
|
@@ -332,7 +466,40 @@ const noDeclaration = () => ({
|
|
|
332
466
|
present: false,
|
|
333
467
|
declared: [],
|
|
334
468
|
declaredWithheld: [],
|
|
469
|
+
version: null,
|
|
470
|
+
roles: [],
|
|
335
471
|
});
|
|
472
|
+
/**
|
|
473
|
+
* The `declaration_version` of an object this reader knows, or `null`: a
|
|
474
|
+
* declaration that is not an object, an absent or `null` version and a version
|
|
475
|
+
* outside `DECLARATION_VERSIONS` are each a claim this reader cannot read.
|
|
476
|
+
*/
|
|
477
|
+
function knownVersion(declaration) {
|
|
478
|
+
if (!isObject(declaration))
|
|
479
|
+
return null;
|
|
480
|
+
const version = declaration["declaration_version"];
|
|
481
|
+
return version === DECLARATION_VERSION || version === DECLARATION_VERSION_V2 ? version : null;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Rule 9 over one record the pack holds: the deviation or note it earns, or
|
|
485
|
+
* `null` where the signed role agrees with the pack and the record. The first
|
|
486
|
+
* of the four deviations that applies, in the order 5.4.2 lists them. Whether
|
|
487
|
+
* 4.1 governs the record is `isHandoffNamespace`, CALLED, never restated.
|
|
488
|
+
*/
|
|
489
|
+
function roleReading(p, role, claimed) {
|
|
490
|
+
if (role === "handoff")
|
|
491
|
+
return claimed ? null : "role_handoff_unclaimed";
|
|
492
|
+
if (claimed)
|
|
493
|
+
return "role_step_claimed";
|
|
494
|
+
if (!p.bytesSupplied)
|
|
495
|
+
return "role_step_withheld";
|
|
496
|
+
if (p.record === null)
|
|
497
|
+
return "role_step_unreadable";
|
|
498
|
+
if (isHandoffNamespace({ publicKey: p.publicKey, record: p.record, ref: p.eventId })) {
|
|
499
|
+
return "role_step_governed";
|
|
500
|
+
}
|
|
501
|
+
return null;
|
|
502
|
+
}
|
|
336
503
|
/**
|
|
337
504
|
* Check a pack against the declaration inside its body.
|
|
338
505
|
*
|
|
@@ -368,22 +535,29 @@ export function checkDeclaration(bundle, content) {
|
|
|
368
535
|
// a deleted one, a null one, and a `declaration` that is a string, an array
|
|
369
536
|
// or null. Each of those hides the evidence of a trim inside a signed body,
|
|
370
537
|
// so none of them may reach a clean pack verdict.
|
|
371
|
-
|
|
538
|
+
const version = knownVersion(declaration);
|
|
539
|
+
if (version === null) {
|
|
372
540
|
return { ...noDeclaration(), present: true };
|
|
373
541
|
}
|
|
374
|
-
const read = readDeclaration(declaration);
|
|
542
|
+
const read = readDeclaration(declaration, version);
|
|
375
543
|
if (!read.ok) {
|
|
376
544
|
return {
|
|
377
545
|
verdict: "failed",
|
|
378
|
-
deviations: read.reasons.map((reason) => ({
|
|
546
|
+
deviations: read.reasons.map((reason) => ({
|
|
547
|
+
kind: "declaration_malformed",
|
|
548
|
+
reason,
|
|
549
|
+
version,
|
|
550
|
+
})),
|
|
379
551
|
contentFindings: [],
|
|
380
552
|
notes: [],
|
|
381
553
|
present: true,
|
|
382
554
|
declared: [],
|
|
383
555
|
declaredWithheld: [],
|
|
556
|
+
version,
|
|
557
|
+
roles: [],
|
|
384
558
|
};
|
|
385
559
|
}
|
|
386
|
-
const { records, pairs } = read.read;
|
|
560
|
+
const { records, pairs, roles } = read.read;
|
|
387
561
|
const declared = records.map((r) => r.eventId);
|
|
388
562
|
const declaredSet = new Set(declared);
|
|
389
563
|
const pack = packRecords(bundle, content);
|
|
@@ -404,7 +578,7 @@ export function checkDeclaration(bundle, content) {
|
|
|
404
578
|
// declaration exists: nothing else in the pack can see it.
|
|
405
579
|
for (const r of records) {
|
|
406
580
|
if (!byId.has(r.eventId))
|
|
407
|
-
deviations.push({ kind: "
|
|
581
|
+
deviations.push({ kind: "declared_absent", eventId: r.eventId });
|
|
408
582
|
}
|
|
409
583
|
// Present and undeclared, and the pack carrying one id twice.
|
|
410
584
|
//
|
|
@@ -421,13 +595,13 @@ export function checkDeclaration(bundle, content) {
|
|
|
421
595
|
const seenInPack = new Set();
|
|
422
596
|
for (const p of pack) {
|
|
423
597
|
if (p.nameable && seenInPack.has(p.eventId)) {
|
|
424
|
-
deviations.push({ kind: "
|
|
598
|
+
deviations.push({ kind: "duplicate_event_id", eventId: p.eventId });
|
|
425
599
|
continue;
|
|
426
600
|
}
|
|
427
601
|
if (p.nameable)
|
|
428
602
|
seenInPack.add(p.eventId);
|
|
429
603
|
if (!p.nameable || !declaredSet.has(p.eventId)) {
|
|
430
|
-
deviations.push({ kind: "
|
|
604
|
+
deviations.push({ kind: "undeclared_present", eventId: p.eventId });
|
|
431
605
|
}
|
|
432
606
|
}
|
|
433
607
|
// The bytes that did not travel. Over the records declared `disclosed` that
|
|
@@ -441,7 +615,7 @@ export function checkDeclaration(bundle, content) {
|
|
|
441
615
|
.filter((p) => p !== undefined);
|
|
442
616
|
const nothingTravelled = disclosedHeld.length > 0 && !disclosedHeld.some((p) => p.disclosed);
|
|
443
617
|
const notes = nothingTravelled
|
|
444
|
-
? [{ kind: "
|
|
618
|
+
? [{ kind: "disclosure_did_not_travel", count: disclosedHeld.length }]
|
|
445
619
|
: [];
|
|
446
620
|
// The two disclosure rules, in declaration order, over the records the pack
|
|
447
621
|
// actually holds: an absent record is already named above, and naming it
|
|
@@ -467,14 +641,14 @@ export function checkDeclaration(bundle, content) {
|
|
|
467
641
|
// the pack, so no pack can prove it supplied them and no reader can
|
|
468
642
|
// prove it did not: an accuser holding a twenty-file drop reaches this
|
|
469
643
|
// reading by deleting one file.
|
|
470
|
-
contentFindings.push({ kind: "
|
|
644
|
+
contentFindings.push({ kind: "content_dependent_withholding", eventId: r.eventId });
|
|
471
645
|
}
|
|
472
646
|
else if (r.withheld && p.disclosed) {
|
|
473
647
|
if (p.disclosedInBody) {
|
|
474
|
-
deviations.push({ kind: "
|
|
648
|
+
deviations.push({ kind: "withheld_but_disclosed", eventId: r.eventId });
|
|
475
649
|
}
|
|
476
650
|
else {
|
|
477
|
-
contentFindings.push({ kind: "
|
|
651
|
+
contentFindings.push({ kind: "content_dependent_disclosure", eventId: r.eventId });
|
|
478
652
|
}
|
|
479
653
|
}
|
|
480
654
|
}
|
|
@@ -504,7 +678,7 @@ export function checkDeclaration(bundle, content) {
|
|
|
504
678
|
if (id === null)
|
|
505
679
|
continue;
|
|
506
680
|
if (!declaredSet.has(id)) {
|
|
507
|
-
deviations.push({ kind: "
|
|
681
|
+
deviations.push({ kind: "pair_record_undeclared", eventId: id, side, pairIndex: i });
|
|
508
682
|
continue;
|
|
509
683
|
}
|
|
510
684
|
const p = byId.get(id);
|
|
@@ -512,13 +686,39 @@ export function checkDeclaration(bundle, content) {
|
|
|
512
686
|
continue;
|
|
513
687
|
const reading = sideReading(p, side);
|
|
514
688
|
if (reading === "withheld") {
|
|
515
|
-
notes.push({ kind: "
|
|
689
|
+
notes.push({ kind: "pair_record_withheld", eventId: id, side, pairIndex: i });
|
|
516
690
|
}
|
|
517
691
|
else if (reading === "not-a-handoff") {
|
|
518
|
-
deviations.push({ kind: "
|
|
692
|
+
deviations.push({ kind: "pair_record_not_a_handoff", eventId: id, side, pairIndex: i });
|
|
519
693
|
}
|
|
520
694
|
}
|
|
521
695
|
}
|
|
696
|
+
// Rule 9 (5.4.2 v1.20, A21), `sanning.declaration/v2` only: a record's
|
|
697
|
+
// signed role against the pack and against the record. Its deviations follow
|
|
698
|
+
// every deviation rules 1 to 8 report, and its note follows every other
|
|
699
|
+
// note, both in `records[]` order. It reads the signed body alone and NEVER
|
|
700
|
+
// the side-content map, so no party that signed nothing can create, move or
|
|
701
|
+
// remove one of its findings.
|
|
702
|
+
if (roles.length > 0) {
|
|
703
|
+
const claimed = new Set();
|
|
704
|
+
for (const pair of pairs) {
|
|
705
|
+
if (pair.sent !== null)
|
|
706
|
+
claimed.add(pair.sent);
|
|
707
|
+
if (pair.received !== null)
|
|
708
|
+
claimed.add(pair.received);
|
|
709
|
+
}
|
|
710
|
+
for (const { eventId, role } of roles) {
|
|
711
|
+
const p = byId.get(eventId);
|
|
712
|
+
// Declared and absent: rule 1 named it, and one defect is one finding.
|
|
713
|
+
if (p === undefined)
|
|
714
|
+
continue;
|
|
715
|
+
const kind = roleReading(p, role, claimed.has(eventId));
|
|
716
|
+
if (kind === "role_step_withheld")
|
|
717
|
+
notes.push({ kind, eventId });
|
|
718
|
+
else if (kind !== null)
|
|
719
|
+
deviations.push({ kind, eventId });
|
|
720
|
+
}
|
|
721
|
+
}
|
|
522
722
|
// A proven deviation wins: it is a mismatch between what the pack claimed
|
|
523
723
|
// and what its own signed body carries, and a failing check always wins.
|
|
524
724
|
// With no deviation and a content-dependent finding, the pack lines up with
|
|
@@ -534,6 +734,8 @@ export function checkDeclaration(bundle, content) {
|
|
|
534
734
|
present: true,
|
|
535
735
|
declared,
|
|
536
736
|
declaredWithheld: records.filter((r) => r.withheld).map((r) => r.eventId),
|
|
737
|
+
version,
|
|
738
|
+
roles,
|
|
537
739
|
};
|
|
538
740
|
}
|
|
539
741
|
/**
|
|
@@ -550,15 +752,18 @@ export function declarationClaims(bundle) {
|
|
|
550
752
|
readable: false,
|
|
551
753
|
caseId: null,
|
|
552
754
|
claimedSides: [],
|
|
755
|
+
version: null,
|
|
756
|
+
roles: [],
|
|
553
757
|
};
|
|
554
758
|
const body = bodyOf(bundle);
|
|
555
759
|
if (!isObject(body) || !("declaration" in body))
|
|
556
760
|
return none;
|
|
557
761
|
const declaration = body["declaration"];
|
|
558
|
-
|
|
762
|
+
const version = knownVersion(declaration);
|
|
763
|
+
if (version === null || !isObject(declaration)) {
|
|
559
764
|
return { ...none, present: true };
|
|
560
765
|
}
|
|
561
|
-
const read = readDeclaration(declaration);
|
|
766
|
+
const read = readDeclaration(declaration, version);
|
|
562
767
|
if (!read.ok)
|
|
563
768
|
return { ...none, present: true };
|
|
564
769
|
const claimedSides = [];
|
|
@@ -574,6 +779,8 @@ export function declarationClaims(bundle) {
|
|
|
574
779
|
readable: true,
|
|
575
780
|
caseId: typeof caseId === "string" ? caseId : null,
|
|
576
781
|
claimedSides,
|
|
782
|
+
version,
|
|
783
|
+
roles: read.read.roles,
|
|
577
784
|
};
|
|
578
785
|
}
|
|
579
786
|
export function renderDeclarationSummary(check) {
|
|
@@ -621,13 +828,16 @@ export function renderDeclarationSummary(check) {
|
|
|
621
828
|
*/
|
|
622
829
|
export function renderNote(note) {
|
|
623
830
|
switch (note.kind) {
|
|
624
|
-
case "
|
|
831
|
+
case "disclosure_did_not_travel":
|
|
625
832
|
return (`this pack declares ${note.count} record(s) disclosed and supplies no bytes for ` +
|
|
626
833
|
"any of them: the bytes did not travel with the pack, so their content is UNKNOWN");
|
|
627
|
-
case "
|
|
834
|
+
case "pair_record_withheld":
|
|
628
835
|
return (`the claimed hand-off at position ${note.pairIndex} names ${note.eventId} on its ` +
|
|
629
836
|
`${note.side} side, whose record bytes this pack withholds, so what that record ` +
|
|
630
837
|
"IS is UNKNOWN");
|
|
838
|
+
case "role_step_withheld":
|
|
839
|
+
return (`${note.eventId} is signed as a step and this pack withholds its ` +
|
|
840
|
+
"record bytes, so whether it is one is UNKNOWN");
|
|
631
841
|
}
|
|
632
842
|
}
|
|
633
843
|
/**
|
|
@@ -640,11 +850,11 @@ export function renderNote(note) {
|
|
|
640
850
|
*/
|
|
641
851
|
export function renderContentFinding(finding) {
|
|
642
852
|
switch (finding.kind) {
|
|
643
|
-
case "
|
|
853
|
+
case "content_dependent_withholding":
|
|
644
854
|
return (`${finding.eventId} is declared disclosed and no bytes for it reached this ` +
|
|
645
855
|
"verifier, from the pack or from beside it, so whether the holder supplied " +
|
|
646
856
|
"them is UNKNOWN");
|
|
647
|
-
case "
|
|
857
|
+
case "content_dependent_disclosure":
|
|
648
858
|
return (`${finding.eventId} is declared withheld and bytes for it were handed in ` +
|
|
649
859
|
"beside this pack, which the pack does not carry and nobody signed, so " +
|
|
650
860
|
"whether the holder disclosed them is UNKNOWN");
|
|
@@ -652,23 +862,36 @@ export function renderContentFinding(finding) {
|
|
|
652
862
|
}
|
|
653
863
|
export function renderDeviation(deviation) {
|
|
654
864
|
switch (deviation.kind) {
|
|
655
|
-
case "
|
|
656
|
-
return
|
|
657
|
-
|
|
865
|
+
case "declaration_malformed":
|
|
866
|
+
return ("the declaration is not readable as " +
|
|
867
|
+
`${deviation.version || DECLARATION_VERSION}: ${deviation.reason}`);
|
|
868
|
+
case "declared_absent":
|
|
658
869
|
return `${deviation.eventId} is declared and is NOT in this pack`;
|
|
659
|
-
case "
|
|
870
|
+
case "undeclared_present":
|
|
660
871
|
return `${deviation.eventId} is in this pack and is NOT declared`;
|
|
661
|
-
case "
|
|
872
|
+
case "withheld_but_disclosed":
|
|
662
873
|
return (`${deviation.eventId} is declared withheld and this pack carries its bytes ` +
|
|
663
874
|
"in its own signed body");
|
|
664
|
-
case "
|
|
875
|
+
case "duplicate_event_id":
|
|
665
876
|
return `this pack carries ${deviation.eventId} more than once`;
|
|
666
|
-
case "
|
|
877
|
+
case "pair_record_undeclared":
|
|
667
878
|
return (`the claimed hand-off at position ${deviation.pairIndex} names ` +
|
|
668
879
|
`${deviation.eventId} on its ${deviation.side} side, which the declaration does not declare`);
|
|
669
|
-
case "
|
|
880
|
+
case "pair_record_not_a_handoff":
|
|
670
881
|
return (`the claimed hand-off at position ${deviation.pairIndex} names ` +
|
|
671
882
|
`${deviation.eventId} on its ${deviation.side} side, which is not a conformant ` +
|
|
672
883
|
`${SIDE_EVENT_TYPES[deviation.side ?? "sent"]} record`);
|
|
884
|
+
case "role_handoff_unclaimed":
|
|
885
|
+
return (`${deviation.eventId} is signed as a hand-off record and no claimed ` +
|
|
886
|
+
"hand-off names it");
|
|
887
|
+
case "role_step_claimed":
|
|
888
|
+
return (`${deviation.eventId} is signed as a step and a claimed hand-off ` +
|
|
889
|
+
"names it as a side");
|
|
890
|
+
case "role_step_governed":
|
|
891
|
+
return (`${deviation.eventId} is signed as a step and is a record the ` +
|
|
892
|
+
"hand-off rules govern");
|
|
893
|
+
case "role_step_unreadable":
|
|
894
|
+
return (`${deviation.eventId} is signed as a step and this pack carries ` +
|
|
895
|
+
"record bytes for it that this verifier cannot read");
|
|
673
896
|
}
|
|
674
897
|
}
|
package/dist/evidence.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { Envelope } from "./types.js";
|
|
2
|
+
/** The accepted `sanning.evidence/v1` wrapper majors; exported in both kernels (P-01, 0.13.0). */
|
|
3
|
+
export declare const ACCEPTED_EVIDENCE_MAJORS: readonly string[];
|
|
2
4
|
export declare const ANCHOR_TRACE_BODY_TYPE = "sanning.anchor.trace/v3";
|
|
3
5
|
/**
|
|
4
6
|
* The name `event_bytes` replaced. Under the new majors a body entry that still
|