@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.
@@ -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;