@sanning/proof 0.10.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/agent-proof.js +8 -2
- package/dist/case.d.ts +745 -0
- package/dist/case.js +1318 -0
- package/dist/chain.js +20 -1
- package/dist/cli.js +181 -27
- package/dist/crypto.d.ts +1 -0
- package/dist/crypto.js +29 -0
- package/dist/declaration.d.ts +284 -0
- package/dist/declaration.js +674 -0
- package/dist/evidence.js +296 -73
- package/dist/grading.d.ts +65 -4
- package/dist/grading.js +112 -6
- package/dist/handoff.d.ts +209 -0
- package/dist/handoff.js +617 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +50 -0
- package/dist/message-parts.d.ts +283 -0
- package/dist/message-parts.js +903 -0
- package/dist/verifier.d.ts +36 -0
- package/dist/verifier.js +150 -3
- package/package.json +1 -1
- package/src/agent-proof.ts +8 -2
- package/src/case.ts +1573 -0
- package/src/chain.ts +20 -1
- package/src/cli.ts +213 -29
- package/src/crypto.ts +28 -0
- package/src/declaration.ts +1005 -0
- package/src/evidence.ts +306 -71
- package/src/grading.ts +140 -6
- package/src/handoff.ts +694 -0
- package/src/index.ts +127 -0
- package/src/message-parts.ts +998 -0
- package/src/verifier.ts +150 -3
- package/vectors/README.md +36 -1
- package/vectors/jcs-reader.json +166 -0
- package/vectors/pack-grading.json +131 -3
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { type HandoffRecord } from "./handoff.js";
|
|
2
|
+
import { type SideContentMap } from "./side-content.js";
|
|
3
|
+
/** The declaration schema this reader knows. */
|
|
4
|
+
export declare const DECLARATION_VERSION = "sanning.declaration/v1";
|
|
5
|
+
/** The three states a declaration check can reach, from the §3 vocabulary. */
|
|
6
|
+
export type DeclarationVerdict = "verified" | "failed" | "partial";
|
|
7
|
+
export type DeclarationDeviationKind =
|
|
8
|
+
/** The declaration is written to this version and is not readable as one. */
|
|
9
|
+
"declaration-malformed"
|
|
10
|
+
/** A record the declaration expects is not in the pack. */
|
|
11
|
+
| "declared-absent"
|
|
12
|
+
/** A record in the pack the declaration never expected. */
|
|
13
|
+
| "undeclared-present"
|
|
14
|
+
/**
|
|
15
|
+
* A record declared `content_withheld` whose bytes the pack supplies INSIDE
|
|
16
|
+
* its own signed body, in `events[].content`.
|
|
17
|
+
*/
|
|
18
|
+
| "withheld-but-disclosed"
|
|
19
|
+
/** The pack carries one `event_id` on two or more records. */
|
|
20
|
+
| "duplicate-event-id"
|
|
21
|
+
/** A claimed hand-off names a record the declaration does not declare. */
|
|
22
|
+
| "pair-record-undeclared"
|
|
23
|
+
/**
|
|
24
|
+
* A claimed hand-off names a record that is not a conformant hand-off
|
|
25
|
+
* record of the side it is claimed on (envelope-spec.md 4.1).
|
|
26
|
+
*/
|
|
27
|
+
| "pair-record-not-a-handoff";
|
|
28
|
+
/**
|
|
29
|
+
* Every way a pack and the UNSIGNED content handed in beside it fail to line
|
|
30
|
+
* up, as a closed set. Neither of these is a deviation, and neither of them
|
|
31
|
+
* fails (proof#147, ratified by Phil 2026-09-22).
|
|
32
|
+
*
|
|
33
|
+
* WHY THEY ARE NOT DEVIATIONS. 5.4 grades a pack's declaration partly against
|
|
34
|
+
* content the reader was HANDED, and that content is not signed by anybody.
|
|
35
|
+
* `events[].content` rides inside `body`, so the wrapper signature and
|
|
36
|
+
* `body_hash` cover it and what it says about the holder is the holder's own
|
|
37
|
+
* word. The side-content map is the other channel, and whoever assembles a
|
|
38
|
+
* case fills it in: an accuser could put an intact pack into
|
|
39
|
+
* `pack_declaration_deviates`, which contributed `failed`, WITH NO KEY AT
|
|
40
|
+
* ALL, by dropping the bytes of a record the pack declared `disclosed` or by
|
|
41
|
+
* adding bytes for one it declared `content_withheld`. The pack was intact,
|
|
42
|
+
* its wrapper verified, and its holder had done nothing.
|
|
43
|
+
*
|
|
44
|
+
* This is the mirror of proof#143. There a counterparty makes itself look
|
|
45
|
+
* better by choosing what it hands over; here an accuser makes a counterparty
|
|
46
|
+
* look worse the same way. Both now read as what the READER established, so
|
|
47
|
+
* `failed` stays for what a signature covers.
|
|
48
|
+
*
|
|
49
|
+
* WHAT A HOSTILE HOLDER GAINS FROM THE WEAKER WORD: nothing, because a
|
|
50
|
+
* cheaper move already dominates it. Declaring a record `content_withheld` is
|
|
51
|
+
* legal and grades `verified`, which is strictly better than the `incomplete`
|
|
52
|
+
* these carry.
|
|
53
|
+
*/
|
|
54
|
+
export type DeclarationContentKind =
|
|
55
|
+
/**
|
|
56
|
+
* A record declared `disclosed` whose bytes reached this reader from
|
|
57
|
+
* neither channel. ALWAYS content-dependent: 5.4.1 lets those bytes travel
|
|
58
|
+
* beside the pack as `logs/<event_id>.json`, so "no bytes arrived" is a
|
|
59
|
+
* fact about what the READER was handed and never a fact about what the
|
|
60
|
+
* holder supplied. No signature covers the absence of a file nobody signed.
|
|
61
|
+
*/
|
|
62
|
+
"content-dependent-withholding"
|
|
63
|
+
/**
|
|
64
|
+
* A record declared `content_withheld` whose bytes reached this reader ONLY
|
|
65
|
+
* from the unsigned side-content map. The pack's own body supplies none, so
|
|
66
|
+
* the holder committed to no such disclosure. The in-body half of the same
|
|
67
|
+
* rule is the `withheld-but-disclosed` DEVIATION and still fails.
|
|
68
|
+
*/
|
|
69
|
+
| "content-dependent-disclosure";
|
|
70
|
+
/**
|
|
71
|
+
* One way the pack and the unsigned content beside it do not line up. Named
|
|
72
|
+
* like a deviation and weighed like an absence.
|
|
73
|
+
*/
|
|
74
|
+
export interface DeclarationContentFinding {
|
|
75
|
+
kind: DeclarationContentKind;
|
|
76
|
+
/** The record this is about. Always one: both kinds are per-record. */
|
|
77
|
+
eventId?: string;
|
|
78
|
+
}
|
|
79
|
+
/** Everything this check reports that is NOT a deviation, as a closed set. */
|
|
80
|
+
export type DeclarationNoteKind =
|
|
81
|
+
/**
|
|
82
|
+
* Every record declared `disclosed` supplied no bytes, so nothing was
|
|
83
|
+
* selected: the bytes did not travel with the pack.
|
|
84
|
+
*/
|
|
85
|
+
"disclosure-did-not-travel"
|
|
86
|
+
/**
|
|
87
|
+
* A claimed hand-off side whose `event_bytes` the pack withholds. The reader
|
|
88
|
+
* cannot see what the record is either way, so it is neither a conformant
|
|
89
|
+
* hand-off record nor a non-conformant one.
|
|
90
|
+
*/
|
|
91
|
+
| "pair-record-withheld";
|
|
92
|
+
/**
|
|
93
|
+
* One named non-deviation finding, as data.
|
|
94
|
+
*
|
|
95
|
+
* A note never moves the declaration verdict. It exists so the one shape rule
|
|
96
|
+
* 3 deliberately does not fail is still SAID, rather than leaving a reader to
|
|
97
|
+
* notice nothing was reported.
|
|
98
|
+
*/
|
|
99
|
+
export interface DeclarationNote {
|
|
100
|
+
kind: DeclarationNoteKind;
|
|
101
|
+
/** How many records the note is about, where it is about a count. */
|
|
102
|
+
count?: number;
|
|
103
|
+
/** The record this is about, where it is about one. */
|
|
104
|
+
eventId?: string;
|
|
105
|
+
/** Which side of a claimed hand-off named it, for `pair-record-withheld`. */
|
|
106
|
+
side?: "sent" | "received";
|
|
107
|
+
/** Which claimed hand-off, by position, for `pair-record-withheld`. */
|
|
108
|
+
pairIndex?: number;
|
|
109
|
+
}
|
|
110
|
+
/** One deviation, as data. A surface renders its own prose or calls `renderDeviation`. */
|
|
111
|
+
export interface DeclarationDeviation {
|
|
112
|
+
kind: DeclarationDeviationKind;
|
|
113
|
+
/** The record this is about, where it is about one. */
|
|
114
|
+
eventId?: string;
|
|
115
|
+
/** Which side of a claimed hand-off named it, for the two `pair-record-` deviations. */
|
|
116
|
+
side?: "sent" | "received";
|
|
117
|
+
/** Which claimed hand-off, by position, for the two `pair-record-` deviations. */
|
|
118
|
+
pairIndex?: number;
|
|
119
|
+
/** What is wrong with the declaration itself, for `declaration-malformed`. */
|
|
120
|
+
reason?: string;
|
|
121
|
+
}
|
|
122
|
+
export interface DeclarationCheck {
|
|
123
|
+
verdict: DeclarationVerdict;
|
|
124
|
+
/** Every deviation, in a fixed order. Empty is only a pass when the verdict says so. */
|
|
125
|
+
deviations: DeclarationDeviation[];
|
|
126
|
+
/**
|
|
127
|
+
* Every way the pack and the unsigned content beside it do not line up, in
|
|
128
|
+
* a fixed order. These are NOT deviations: they carry the check to
|
|
129
|
+
* `partial` and the pack to `incomplete`, never to `failed`.
|
|
130
|
+
*/
|
|
131
|
+
contentFindings: DeclarationContentFinding[];
|
|
132
|
+
/** Named findings that are NOT deviations, in a fixed order. */
|
|
133
|
+
notes: DeclarationNote[];
|
|
134
|
+
/**
|
|
135
|
+
* Whether the body carried a `declaration` member at all, WHATEVER its
|
|
136
|
+
* shape. A string, an array or a `null` under that key is a claim the holder
|
|
137
|
+
* made and this reader cannot read, which is not the same thing as a pack
|
|
138
|
+
* that claimed nothing: `gradePack` grades the two differently.
|
|
139
|
+
*/
|
|
140
|
+
present: boolean;
|
|
141
|
+
/** `event_id`s the declaration expects, in declaration order. */
|
|
142
|
+
declared: string[];
|
|
143
|
+
/** Of those, the ones declared withheld on purpose. */
|
|
144
|
+
declaredWithheld: string[];
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* One of the pack's own records: the id, and whether the pack supplied its
|
|
148
|
+
* bytes.
|
|
149
|
+
*
|
|
150
|
+
* An event with no readable `event_id` is given a positional name, the same
|
|
151
|
+
* `#n` convention `accountDisclosure` uses, so it is still reported rather
|
|
152
|
+
* than dropped. An unnameable record cannot be declared, so it can only ever
|
|
153
|
+
* be surplus, which is a deviation and is named as one.
|
|
154
|
+
*/
|
|
155
|
+
export interface PackRecord {
|
|
156
|
+
eventId: string;
|
|
157
|
+
nameable: boolean;
|
|
158
|
+
/**
|
|
159
|
+
* Whether bytes for this record reached the reader at ALL, from either
|
|
160
|
+
* channel. This is what the disclosure account counts.
|
|
161
|
+
*/
|
|
162
|
+
disclosed: boolean;
|
|
163
|
+
/**
|
|
164
|
+
* Whether the bytes came from `events[].content`, INSIDE the body the
|
|
165
|
+
* wrapper signed and `body_hash` commits to. The two channels are kept
|
|
166
|
+
* apart because only this one is the holder's own word: the side-content
|
|
167
|
+
* map is filled in by whoever assembled the case (proof#147).
|
|
168
|
+
*/
|
|
169
|
+
disclosedInBody: boolean;
|
|
170
|
+
publicKey: string;
|
|
171
|
+
record: HandoffRecord | null;
|
|
172
|
+
/**
|
|
173
|
+
* Whether the pack SUPPLIED `event_bytes` for this record at all.
|
|
174
|
+
* `event_bytes` is OPTIONAL (evidence-bundle.md 5.1 and 5.3), and
|
|
175
|
+
* withholding it is minimum disclosure rather than a defect, so "the pack
|
|
176
|
+
* supplied bytes this reader cannot read" and "the pack supplied none" are
|
|
177
|
+
* two different readings and never one.
|
|
178
|
+
*/
|
|
179
|
+
bytesSupplied: boolean;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* The pack's own records, in bundle order. Exported so the CASE layer
|
|
183
|
+
* (`./case.ts`, anchor#198) reads a pack through the SAME parser that grades
|
|
184
|
+
* it here, rather than through a second one beside it. A validation copied
|
|
185
|
+
* next to the one that enforces it drifts, and a copy that is looser blesses
|
|
186
|
+
* a value the real one then rejects.
|
|
187
|
+
*/
|
|
188
|
+
export declare function packRecords(bundle: unknown, content: SideContentMap | null | undefined): PackRecord[];
|
|
189
|
+
/**
|
|
190
|
+
* Check a pack against the declaration inside its body.
|
|
191
|
+
*
|
|
192
|
+
* `content` is the same side-content map `verifyEvidenceBundle` takes, and is
|
|
193
|
+
* optional: a pack that discloses its bytes in the body needs no side input,
|
|
194
|
+
* and a pack that discloses them beside itself passes the map a consumer
|
|
195
|
+
* already built.
|
|
196
|
+
*
|
|
197
|
+
* The verdict is this check's own. It does not restate the bundle verdict and
|
|
198
|
+
* it does not read it: a declaration can match perfectly over a pack whose
|
|
199
|
+
* signatures are forged, and saying so is `gradePack`'s job, which takes both.
|
|
200
|
+
*/
|
|
201
|
+
export declare function checkDeclaration(bundle: unknown, content?: SideContentMap | null): DeclarationCheck;
|
|
202
|
+
/**
|
|
203
|
+
* The house wording for one deviation.
|
|
204
|
+
*
|
|
205
|
+
* Offered, not imposed, exactly as `renderCause` is: what must not vary
|
|
206
|
+
* between surfaces is the verdict and the set of deviations, and those come
|
|
207
|
+
* from `checkDeclaration`. Both kernels return this same text for the same
|
|
208
|
+
* deviation, and `cross-kernel/run_cli.sh` holds the two commands to it.
|
|
209
|
+
*/
|
|
210
|
+
/**
|
|
211
|
+
* One line a surface can print beside the pack verdict: what the pack claimed
|
|
212
|
+
* to hold, and whether it does.
|
|
213
|
+
*
|
|
214
|
+
* Both kernels' commands print this sentence, so a reader who runs
|
|
215
|
+
* `sanning-proof verify` and `npx @sanning/proof verify` over one pack reads
|
|
216
|
+
* one sentence rather than two that differ by a word.
|
|
217
|
+
*
|
|
218
|
+
* `partial` is spelled out rather than printed as a word, because "partial" on
|
|
219
|
+
* its own beside a green pack reads as a hedge. What it means is that nobody
|
|
220
|
+
* said what the pack was meant to contain, and a reader deciding whether this
|
|
221
|
+
* is a full hand-over needs that in words.
|
|
222
|
+
*/
|
|
223
|
+
/**
|
|
224
|
+
* What a pack's declaration CLAIMS, for a reader one layer out.
|
|
225
|
+
*
|
|
226
|
+
* `checkDeclaration` answers "does this pack agree with its own claim". A case
|
|
227
|
+
* reader (`./case.ts`, anchor#198) needs a different thing from the same
|
|
228
|
+
* object: which records the holder claimed as a hand-off side at all, so that
|
|
229
|
+
* a conformant hand-off record the holder claimed NOWHERE is a named finding
|
|
230
|
+
* rather than a silence. That is the door proof#137 ratified open at this
|
|
231
|
+
* layer: `handoffs[]` MAY be empty, so rule 2 cannot reach a declared record
|
|
232
|
+
* and rule 7 cannot reach an unclaimed one.
|
|
233
|
+
*
|
|
234
|
+
* It is read HERE, from the same two functions `checkDeclaration` reads,
|
|
235
|
+
* rather than restated in the case layer. A second reader of one object drifts
|
|
236
|
+
* from the first, and a looser copy blesses a claim the real one rejects.
|
|
237
|
+
*/
|
|
238
|
+
export interface DeclarationClaims {
|
|
239
|
+
/**
|
|
240
|
+
* Whether the body carried a `declaration` member at all, whatever its
|
|
241
|
+
* shape. The same field `DeclarationCheck` carries, and the same meaning.
|
|
242
|
+
*/
|
|
243
|
+
present: boolean;
|
|
244
|
+
/**
|
|
245
|
+
* Whether this reader could read the claim as `DECLARATION_VERSION` AND the
|
|
246
|
+
* object satisfies 5.4.1. `false` covers an absent declaration, an unknown
|
|
247
|
+
* version and a malformed object, and a case reader must not read any of the
|
|
248
|
+
* three as "the holder claimed nothing to check".
|
|
249
|
+
*/
|
|
250
|
+
readable: boolean;
|
|
251
|
+
/** The case the declaration names, where it names one. */
|
|
252
|
+
caseId: string | null;
|
|
253
|
+
/**
|
|
254
|
+
* Every `event_id` a `handoffs[]` entry names on either side, in declaration
|
|
255
|
+
* order, with no duplicates. Empty where the holder claimed no pair, which
|
|
256
|
+
* is legal and is exactly the shape a case reader has to name.
|
|
257
|
+
*/
|
|
258
|
+
claimedSides: string[];
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Read the claim a pack's declaration makes, without grading the pack.
|
|
262
|
+
*
|
|
263
|
+
* Deliberately total: a bundle this reader cannot read comes back with
|
|
264
|
+
* `readable: false` rather than throwing, because a case reader grades what it
|
|
265
|
+
* was handed and a reader that throws on a hostile pack cannot report that the
|
|
266
|
+
* pack is hostile.
|
|
267
|
+
*/
|
|
268
|
+
export declare function declarationClaims(bundle: unknown): DeclarationClaims;
|
|
269
|
+
export declare function renderDeclarationSummary(check: DeclarationCheck): string;
|
|
270
|
+
/**
|
|
271
|
+
* The house wording for one note. Offered, not imposed, as `renderDeviation`
|
|
272
|
+
* is.
|
|
273
|
+
*/
|
|
274
|
+
export declare function renderNote(note: DeclarationNote): string;
|
|
275
|
+
/**
|
|
276
|
+
* The house wording for one content-dependent finding.
|
|
277
|
+
*
|
|
278
|
+
* EVERY SENTENCE HERE NAMES THE CONTENT AND NOT THE HOLDER. That is the whole
|
|
279
|
+
* point of the kind: the bytes are unsigned and whoever assembled the case
|
|
280
|
+
* chose them, so a sentence that reads as an accusation would put the weaker
|
|
281
|
+
* word beside the stronger claim and an auditor would read the claim.
|
|
282
|
+
*/
|
|
283
|
+
export declare function renderContentFinding(finding: DeclarationContentFinding): string;
|
|
284
|
+
export declare function renderDeviation(deviation: DeclarationDeviation): string;
|