@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
package/dist/index.js
CHANGED
|
@@ -51,6 +51,40 @@ export { ANCHOR_TRACE_BODY_TYPE, EXPORT_BODY_TYPE, STAMP_BODY_VERSION, STAMP_TRA
|
|
|
51
51
|
// derivation to it, and diff its copy against ours. Additive — no existing
|
|
52
52
|
// export changes.
|
|
53
53
|
export { accountDisclosure, completenessNotes, gradeCauses, gradePack, renderCause, signedLeafCount, } from "./grading.js";
|
|
54
|
+
// The declaration (src/declaration.ts): what a pack SAYS it holds, checked
|
|
55
|
+
// against what it does (evidence-bundle.md §5.4).
|
|
56
|
+
//
|
|
57
|
+
// Every other check in this kernel checks a record that is PRESENT, so a pack
|
|
58
|
+
// with half its records dropped verifies perfectly and an auditor cannot tell
|
|
59
|
+
// a full hand-over from a trimmed one. The declaration is the pack's own
|
|
60
|
+
// statement of what it was meant to contain, inside `body` and therefore under
|
|
61
|
+
// the bundle's signature. Any deviation FAILS and every deviation is NAMED; a
|
|
62
|
+
// pack that carries no declaration is `partial`, never `verified`; and a
|
|
63
|
+
// reading that rests on the UNSIGNED content handed in beside the pack is
|
|
64
|
+
// named on its own and carries the weaker word, because whoever assembles a
|
|
65
|
+
// case chose those bytes (proof#147).
|
|
66
|
+
//
|
|
67
|
+
// Pure: data in, data out, no I/O. `gradePack` takes the result as an
|
|
68
|
+
// optional fourth argument, so a surface gets one verdict; omitting it is the
|
|
69
|
+
// pre-§5.4 behavior, unchanged. Additive: no existing export changes.
|
|
70
|
+
export { DECLARATION_VERSION, checkDeclaration, declarationClaims, packRecords, renderContentFinding, renderDeclarationSummary, renderDeviation, renderNote, } from "./declaration.js";
|
|
71
|
+
// The case (src/case.ts): one verdict over SEVERAL packs, across organizations
|
|
72
|
+
// (anchor#198). The layer above the declaration, and the closure for what a
|
|
73
|
+
// declaration cannot reach.
|
|
74
|
+
//
|
|
75
|
+
// A pack a party assembles about itself is not self-proving about what that
|
|
76
|
+
// party LEFT OUT (envelope-spec.md §4.1 A15, evidence-bundle.md §5.4.2 rule 7),
|
|
77
|
+
// because the omission happens before any reader runs. Reading several parties'
|
|
78
|
+
// packs at once is what closes it: a `handoff.sent` with no counterpart is
|
|
79
|
+
// always named, and where a pack signed by the key it names is in the case, the
|
|
80
|
+
// reading says that pack holds no counterpart. Whether a party that presented
|
|
81
|
+
// no such pack recorded anything is not established (proof#143). `readCase`
|
|
82
|
+
// verifies every pack itself, offline, so it is async. Matching is on keys and hashes alone and NEVER on tenancy, so a pair from two
|
|
83
|
+
// organizations verifies (DL-60).
|
|
84
|
+
//
|
|
85
|
+
// Pure: data in, data out, no I/O. Additive: a new module, no existing export
|
|
86
|
+
// changes, and no new verdict word anywhere.
|
|
87
|
+
export { CASE_BODY_TYPES, CASE_EXCEPTION_KINDS, CASE_QUARANTINED_PROFILES, CASE_RECORD_PROFILES, CASE_VERDICTS, readCase, renderCaseSummary, renderException, } from "./case.js";
|
|
54
88
|
// Chain reading (src/chain.ts): following the pointer every sanning.events/v2
|
|
55
89
|
// record carries to the record before it, over the records ONE bundle presents.
|
|
56
90
|
//
|
|
@@ -81,3 +115,19 @@ export { attestationSignatureToHex, composeExport } from "./compose.js";
|
|
|
81
115
|
// sanning.agent.proof/v1 bundle. The CLI sniffs spec_version and routes here so
|
|
82
116
|
// one verifier covers both agent and anchor bundles.
|
|
83
117
|
export { AGENT_PROOF_SPEC_PREFIX, isAgentProofSpec, verifyAgentProofBundle } from "./agent-proof.js";
|
|
118
|
+
// The hand-off pairing rule (src/handoff.ts): which records are candidates to
|
|
119
|
+
// be the counterpart of a hand-off record, how a candidate set of more than one
|
|
120
|
+
// is ordered, and what a formed pair grades to (specs/envelope-spec.md §4.1
|
|
121
|
+
// v1.9, proof#132 and proof#136). The Python half is src/sanning_proof/handoff.py and the two
|
|
122
|
+
// answer identically on identical bytes. Additive: a new module, and no
|
|
123
|
+
// existing export changes.
|
|
124
|
+
export { CANDIDATE_NAMESPACE, DEFINED_EVENT_TYPES, NOT_A_HANDOFF_FINDING, isConformantHandoffRecord, readHandoff, readHandoffs, recordIsMalformed, } from "./handoff.js";
|
|
125
|
+
// A file part's BYTES (src/message-parts.ts): what a file part is, and what a
|
|
126
|
+
// receiver that HOLDS a part's bytes commits to (envelope-spec.md section 4.1
|
|
127
|
+
// v1.13, A18, proof#145). A receiver that holds the bytes hashes them and, on a
|
|
128
|
+
// mismatch, commits to the message AS IT HELD IT, so the pair fails on
|
|
129
|
+
// `message_hash` rather than verifying over bytes nobody checked. The Python
|
|
130
|
+
// half is src/sanning_proof/message_parts.py and the two are held together by
|
|
131
|
+
// cross-kernel/run_parts.sh. Pure: it neither canonicalizes nor hashes the
|
|
132
|
+
// message.
|
|
133
|
+
export { BYTES_FOR_UNKNOWN_PART, CONFLICTING_PART_BYTES, INLINE_BYTES_KEYS, MESSAGE_RESULT_ORDER, PARTS_NOT_AN_ARRAY, PART_ENCODING_MISMATCH, PART_INLINE_BYTES, PART_INLINE_MISMATCHED, PART_MALFORMED_DESCRIPTOR, PART_MATCHED, PART_MISMATCHED, PART_NOT_A_FILE, PART_SUPPLIED_TWICE, PART_UNCHECKED, REFUSAL_ORDER, commitHeldMessage, isFilePart, uncheckedHeldParts, } from "./message-parts.js";
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The result of a part whose supplied bytes hash to its `content_hash` AND
|
|
3
|
+
* count to its `content_length`.
|
|
4
|
+
*/
|
|
5
|
+
export declare const PART_MATCHED = "part_matched";
|
|
6
|
+
/**
|
|
7
|
+
* The result of a part whose supplied bytes miss either of those, with no
|
|
8
|
+
* respelling of them that explains the digest. The held-message substitution
|
|
9
|
+
* applies to this part, and it replaces BOTH values with the held bytes' own.
|
|
10
|
+
*/
|
|
11
|
+
export declare const PART_MISMATCHED = "part_mismatched";
|
|
12
|
+
/**
|
|
13
|
+
* A file part whose bytes were not supplied, or whose supply was REFUSED. It is
|
|
14
|
+
* not checked and it is not clean: section 4.1 forbids a reader treating it as
|
|
15
|
+
* checked.
|
|
16
|
+
*/
|
|
17
|
+
export declare const PART_UNCHECKED = "part_unchecked";
|
|
18
|
+
/**
|
|
19
|
+
* A `kind: "file"` part carrying its content inside the message, and ACCEPTED
|
|
20
|
+
* (proof#157, ratified by Phil 2026-09-22). It is committed by the MESSAGE
|
|
21
|
+
* ITSELF: `message_hash` is SHA-256 over the canonical bytes of the message
|
|
22
|
+
* object, so altering that content changes the message, changes the hash, and
|
|
23
|
+
* fails the pair. Nothing is hashed against it here and nothing is substituted,
|
|
24
|
+
* because there is nothing travelling beside the message for the message to
|
|
25
|
+
* disagree with. That is why the swapped-bytes attack cannot reach this shape.
|
|
26
|
+
*/
|
|
27
|
+
export declare const PART_INLINE_BYTES = "part_inline_bytes";
|
|
28
|
+
/**
|
|
29
|
+
* An inline part whose `content_hash`, `content_length` or both DISAGREE with
|
|
30
|
+
* its inline content: no single reading of that content satisfies every claim
|
|
31
|
+
* the part writes beside it. The message's own commitment still governs and
|
|
32
|
+
* nothing is substituted; this names the SECOND claim that disagrees with it,
|
|
33
|
+
* which is a finding in its own right (proof#157).
|
|
34
|
+
*
|
|
35
|
+
* ONE name for both claims, exactly as `PART_MISMATCHED` is one name for both
|
|
36
|
+
* on a descriptor part. It was `part_inline_hash_mismatch` for one round of
|
|
37
|
+
* this unmerged PR, when only the hash was compared; a name that says "hash"
|
|
38
|
+
* about a result that also covers a length asserts more than its body does, so
|
|
39
|
+
* the name was re-cut with the rule (proof#156 second gate, F4).
|
|
40
|
+
*
|
|
41
|
+
* The check is deliberately permissive ACROSS readings: the raw UTF-8 of the
|
|
42
|
+
* value, its base64 decoding and its hex decoding are all tried, so this fires
|
|
43
|
+
* only where no reading agrees at all. It is not permissive BETWEEN claims: one
|
|
44
|
+
* reading has to satisfy all of them, because a hash that agrees under the
|
|
45
|
+
* base64 decoding beside a length that agrees under the raw text describes no
|
|
46
|
+
* blob that exists.
|
|
47
|
+
*/
|
|
48
|
+
export declare const PART_INLINE_MISMATCHED = "part_inline_mismatched";
|
|
49
|
+
/**
|
|
50
|
+
* `kind: "file"` without a conformant `content_hash` and `content_length`. A
|
|
51
|
+
* sender's defect rather than a receiver's, so it is named rather than refused:
|
|
52
|
+
* the receiver did nothing wrong by holding it.
|
|
53
|
+
*/
|
|
54
|
+
export declare const PART_MALFORMED_DESCRIPTOR = "part_malformed_descriptor";
|
|
55
|
+
/**
|
|
56
|
+
* Any other part. It is committed as ordinary JSON content and this rule checks
|
|
57
|
+
* nothing about it.
|
|
58
|
+
*/
|
|
59
|
+
export declare const PART_NOT_A_FILE = "part_not_a_file";
|
|
60
|
+
/**
|
|
61
|
+
* REFUSAL of ONE supplied entry. Bytes were supplied for no part of the
|
|
62
|
+
* message, or for a part this rule cannot check. The caller and the message
|
|
63
|
+
* disagree about what was sent, and hashing bytes against a part that is not
|
|
64
|
+
* there establishes nothing. Where the entry names a part that EXISTS but is
|
|
65
|
+
* not checkable, the refusal carries that part's index and the part keeps the
|
|
66
|
+
* result it classified to; where it names no part at all, the refusal carries
|
|
67
|
+
* the entry position and no part. Neither stops the record being written.
|
|
68
|
+
*/
|
|
69
|
+
export declare const BYTES_FOR_UNKNOWN_PART = "bytes_for_unknown_part";
|
|
70
|
+
/**
|
|
71
|
+
* REFUSAL. Two DIFFERENT byte strings were supplied for one part. A receiver
|
|
72
|
+
* holding two answers has not established which one it held.
|
|
73
|
+
*/
|
|
74
|
+
export declare const CONFLICTING_PART_BYTES = "conflicting_part_bytes";
|
|
75
|
+
/**
|
|
76
|
+
* REFUSAL of ONE supplied entry. The supplied bytes miss the descriptor, but a
|
|
77
|
+
* base64 or hex RESPELLING of them in EITHER direction hashes to it: either the
|
|
78
|
+
* bytes DECODE to the part (the receiver's stack never decoded what it was
|
|
79
|
+
* handed) or they ENCODE to it (the receiver's stack decoded bytes the sender
|
|
80
|
+
* committed in their encoded spelling, which is what a single-line DER
|
|
81
|
+
* attachment, a PEM body and a JWT all are). That is a coding defect in the
|
|
82
|
+
* receiver, not a tamper by the sender: a proto3 JSON transcoder re-encodes
|
|
83
|
+
* base64 URL-safe and unpadded while behaving honestly, and under the silence
|
|
84
|
+
* this amendment removes it produced a permanent tamper record against a sender
|
|
85
|
+
* who did nothing. Recording evidence out of a receiver's own bug is the one
|
|
86
|
+
* outcome worse than recording nothing, so BOTH directions are guarded
|
|
87
|
+
* (proof#156 gate): guarding only the decoding direction left the other half of
|
|
88
|
+
* one honest stack's behavior reading as a tamper.
|
|
89
|
+
*/
|
|
90
|
+
export declare const PART_ENCODING_MISMATCH = "part_encoding_mismatch";
|
|
91
|
+
/**
|
|
92
|
+
* A MESSAGE-LEVEL result. The message carries a `parts` key whose value is
|
|
93
|
+
* present and is not an array, which is how proto3 JSON encodes a map and so is
|
|
94
|
+
* reachable from the same transport family the motivating example comes from.
|
|
95
|
+
* Section 4.1 asks for one result per part; a message shaped like this has
|
|
96
|
+
* part-shaped content and no parts, and before proof#156's gate it produced
|
|
97
|
+
* zero readings and an accepted record. It is named rather than refused: the
|
|
98
|
+
* receiver did nothing wrong by holding it, and nothing in it is checked. A
|
|
99
|
+
* `parts` written as `null` is an ABSENT `parts` and is not this, on A14's
|
|
100
|
+
* ruling that an absent value and one written as `null` name one thing.
|
|
101
|
+
*/
|
|
102
|
+
export declare const PARTS_NOT_AN_ARRAY = "parts_not_an_array";
|
|
103
|
+
/**
|
|
104
|
+
* NOTE, not a refusal. One part supplied more than once with IDENTICAL bytes is
|
|
105
|
+
* one supply stated twice, and the part is checked normally. Different bytes for
|
|
106
|
+
* one part are `CONFLICTING_PART_BYTES`.
|
|
107
|
+
*/
|
|
108
|
+
export declare const PART_SUPPLIED_TWICE = "part_supplied_twice";
|
|
109
|
+
/**
|
|
110
|
+
* The order refusals are reported in, fixed so two kernels report one list
|
|
111
|
+
* rather than two orderings of it.
|
|
112
|
+
*/
|
|
113
|
+
export declare const REFUSAL_ORDER: readonly ["bytes_for_unknown_part", "conflicting_part_bytes", "part_encoding_mismatch"];
|
|
114
|
+
/**
|
|
115
|
+
* The order message-level results are reported in, for the same reason as
|
|
116
|
+
* `REFUSAL_ORDER`.
|
|
117
|
+
*/
|
|
118
|
+
export declare const MESSAGE_RESULT_ORDER: readonly ["parts_not_an_array"];
|
|
119
|
+
/**
|
|
120
|
+
* The keys that make a part carry its content INLINE, in the order they are
|
|
121
|
+
* read. Named rather than inferred: `bytes` is A2A's spelling of an inline file
|
|
122
|
+
* part, and `data` and `content` are the two other spellings in the wild. The
|
|
123
|
+
* FIRST of them present with a value that is not `null` is the inline content,
|
|
124
|
+
* so a part spelling it twice reads one way in both kernels.
|
|
125
|
+
*
|
|
126
|
+
* A key present with `null` is an ABSENT inline value, on A14's ruling that an
|
|
127
|
+
* absent value and one written as `null` name one thing. That matters: an A2A
|
|
128
|
+
* `FilePart` with `uri` set carries `"bytes": null`, and so does proto3 JSON,
|
|
129
|
+
* and reading those as inline made a conformant DESCRIPTOR part uncheckable
|
|
130
|
+
* (proof#156 gate). They fall through to the descriptor rule, which is what
|
|
131
|
+
* they are.
|
|
132
|
+
*/
|
|
133
|
+
export declare const INLINE_BYTES_KEYS: readonly ["bytes", "content", "data"];
|
|
134
|
+
/**
|
|
135
|
+
* The largest integer a double carries exactly, the same bound section 4.1 puts
|
|
136
|
+
* on `event.sequence`: a `content_length` outside it is a value the two kernels
|
|
137
|
+
* would read as two numbers.
|
|
138
|
+
*/
|
|
139
|
+
export declare const MAX_SAFE_INTEGER: number;
|
|
140
|
+
/**
|
|
141
|
+
* One byte string a receiver holds, and the part it holds it for.
|
|
142
|
+
*
|
|
143
|
+
* `part` is an INDEX into the message's `parts` array. It is an index rather
|
|
144
|
+
* than a content hash on purpose: keying the supply by the value the check is
|
|
145
|
+
* about to grade would mean a receiver could only supply bytes it had already
|
|
146
|
+
* agreed with, which is the whole defect stated backwards.
|
|
147
|
+
*/
|
|
148
|
+
export interface SuppliedPart {
|
|
149
|
+
part: unknown;
|
|
150
|
+
data: Uint8Array;
|
|
151
|
+
}
|
|
152
|
+
/** What the receiver reports for one part of the message, one per part. */
|
|
153
|
+
export interface PartReading {
|
|
154
|
+
index: number;
|
|
155
|
+
result: string;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* A caller error. Nothing is written, nothing is signed, no record made.
|
|
159
|
+
*
|
|
160
|
+
* `part` is the part index where the refusal is about a part of the message,
|
|
161
|
+
* and `entry` is the POSITION of the offending supplied entry in the sequence
|
|
162
|
+
* the caller passed. The position is recorded rather than the value the caller
|
|
163
|
+
* named, so a caller that names a part with a float or a string cannot make two
|
|
164
|
+
* kernels serialize one reading two ways.
|
|
165
|
+
*/
|
|
166
|
+
export interface Refusal {
|
|
167
|
+
name: string;
|
|
168
|
+
part: number | null;
|
|
169
|
+
entry: number | null;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Something worth reporting that refuses nothing and checks nothing out. Same
|
|
173
|
+
* shape as a `Refusal` and carried in its own list, so no caller can read a
|
|
174
|
+
* note as a refusal by looking only at a name.
|
|
175
|
+
*/
|
|
176
|
+
export type Note = Refusal;
|
|
177
|
+
/**
|
|
178
|
+
* The message a receiver commits to, and everything it found on the way.
|
|
179
|
+
*
|
|
180
|
+
* `heldMessage` is ALWAYS the message the receiver commits to and is never
|
|
181
|
+
* null: a refusal is scoped to the supply it is about and never denies the
|
|
182
|
+
* receiver a record (proof#156 gate). The caller commits `event.message_hash`
|
|
183
|
+
* as SHA-256 over its JCS (RFC 8785) canonical bytes, which is the rule section
|
|
184
|
+
* 4.1 already states and the reason this module does not hash anything itself.
|
|
185
|
+
*
|
|
186
|
+
* `messageResults` carries the message-level named results, in
|
|
187
|
+
* `MESSAGE_RESULT_ORDER`. It is empty for every message whose shape the rule
|
|
188
|
+
* reads.
|
|
189
|
+
*
|
|
190
|
+
* `substituted` carries the indexes of the parts whose descriptor the receiver
|
|
191
|
+
* replaced with the held bytes' own hash and length. It is empty for an honest
|
|
192
|
+
* hand-off, and where it is not empty the receiving side writes
|
|
193
|
+
* `outcome: "rejected"`.
|
|
194
|
+
*/
|
|
195
|
+
export interface HeldMessage {
|
|
196
|
+
parts: PartReading[];
|
|
197
|
+
messageResults: string[];
|
|
198
|
+
refusals: Refusal[];
|
|
199
|
+
notes: Note[];
|
|
200
|
+
heldMessage: unknown | null;
|
|
201
|
+
substituted: number[];
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Whether this part is one this rule CHECKS against bytes travelling BESIDE the
|
|
205
|
+
* message: a conformant descriptor part. Exactly the three conditions section
|
|
206
|
+
* 4.1 states, in the order that keeps inline content out. An INLINE part is
|
|
207
|
+
* accepted and committed by the message itself (proof#157), so there is nothing
|
|
208
|
+
* here for supplied bytes to be checked against and this answers `false`.
|
|
209
|
+
*/
|
|
210
|
+
export declare function isFilePart(part: unknown): boolean;
|
|
211
|
+
/**
|
|
212
|
+
* What a receiver commits to, given the message and the bytes it holds.
|
|
213
|
+
*
|
|
214
|
+
* `supplied` is the bytes the receiver actually holds, each named by the index
|
|
215
|
+
* of the part it belongs to. An empty list is the common case, a receiver that
|
|
216
|
+
* holds no part's bytes, and it reads exactly as section 4.1 read before this
|
|
217
|
+
* amendment: every file part is `PART_UNCHECKED` and the message a receiver
|
|
218
|
+
* commits to is the message it was given.
|
|
219
|
+
*
|
|
220
|
+
* Where a part's bytes hash to something other than its `content_hash`, or
|
|
221
|
+
* where their count is not its `content_length`, the returned `heldMessage`
|
|
222
|
+
* carries that part's `content_hash` and `content_length` replaced by the held
|
|
223
|
+
* bytes' own. The receiver commits `event.message_hash` over THAT, so its
|
|
224
|
+
* record and the sender's disagree and the pair fails on `message_hash`.
|
|
225
|
+
*
|
|
226
|
+
* A refusal is scoped to the supplied entry it is about: the part it names is
|
|
227
|
+
* not checked and not substituted, every other part reads as it would have, and
|
|
228
|
+
* `heldMessage` is returned either way. Nothing a caller or a sender can do
|
|
229
|
+
* denies the receiver a record.
|
|
230
|
+
*/
|
|
231
|
+
export declare function commitHeldMessage(message: unknown, supplied?: readonly SuppliedPart[]): Promise<HeldMessage>;
|
|
232
|
+
/**
|
|
233
|
+
* The parts the receiver HELD bytes for and did NOT check.
|
|
234
|
+
*
|
|
235
|
+
* The gap between "nothing was substituted" and "every part is clean", named
|
|
236
|
+
* so a caller does not have to re-derive it from the refusal list (proof#156
|
|
237
|
+
* second gate, F5). A part is in here where a refusal NAMES it and its reading
|
|
238
|
+
* is one of `HELD_UNCHECKED_RESULTS`: the receiver had bytes for that part,
|
|
239
|
+
* the rule declined to grade them, and NOTHING about that reaches the
|
|
240
|
+
* committed record.
|
|
241
|
+
*
|
|
242
|
+
* THE RULE, in one sentence, because a filter is not a rule. A part is in here
|
|
243
|
+
* where the receiver held bytes for it AND the message itself does not commit
|
|
244
|
+
* its content. `PART_UNCHECKED` is a refused supply;
|
|
245
|
+
* `PART_MALFORMED_DESCRIPTOR` is a part whose content travels BESIDE the
|
|
246
|
+
* message under a claim no receiver can use, so the receiver's bytes are
|
|
247
|
+
* exactly as ungraded and the record says as little. The first cut of this
|
|
248
|
+
* function omitted the second, which the third gate measured: a `kind: "file"`
|
|
249
|
+
* part with a non-conformant `content_hash` or `content_length`, bytes held,
|
|
250
|
+
* read `[]` here, so a caller following the rule this module prescribes wrote
|
|
251
|
+
* `accepted` over a part it held bytes for and never checked. That is the
|
|
252
|
+
* class the mechanism exists to close, and Phil named it in proof#162's
|
|
253
|
+
* ratification as the gap that decision inherits unless it is closed here.
|
|
254
|
+
*
|
|
255
|
+
* Three results are excluded ON PURPOSE. An inline part (`PART_INLINE_BYTES`,
|
|
256
|
+
* `PART_INLINE_MISMATCHED`) and a non-file part (`PART_NOT_A_FILE`) carry
|
|
257
|
+
* their content INSIDE the message, and `message_hash` commits it on both
|
|
258
|
+
* sides: bytes supplied beside one of those grade nothing and hide nothing,
|
|
259
|
+
* because a reader of either pack reads the content itself. Naming them would
|
|
260
|
+
* fire the caller's obligation on parts the swapped-bytes attack cannot reach
|
|
261
|
+
* at all.
|
|
262
|
+
*
|
|
263
|
+
* Why it is worth a function rather than a note. Delivering a tampered copy of
|
|
264
|
+
* a part alone is `part_mismatched`, is substituted, and fails the pair on
|
|
265
|
+
* `message_hash`. Delivering the tampered copy AND the true one is
|
|
266
|
+
* `conflicting_part_bytes`, is never substituted, and the pair VERIFIES, so
|
|
267
|
+
* adding data suppresses detection and the only trace is a refusal the
|
|
268
|
+
* receiving side publishes at its own discretion.
|
|
269
|
+
*
|
|
270
|
+
* NOT the same as "this part reads `PART_UNCHECKED`". A receiver that holds no
|
|
271
|
+
* bytes at all reads every file part that way, and that is the COMMON CASE,
|
|
272
|
+
* unchanged by A18 and not a reason to withhold `accepted`. Only a part whose
|
|
273
|
+
* bytes the receiver actually held and could not grade is in here.
|
|
274
|
+
*
|
|
275
|
+
* What this does NOT do: it cannot stop a caller writing `accepted` anyway.
|
|
276
|
+
* This module reads and does not write the record. proof#162 ratified what the
|
|
277
|
+
* record layer does instead (Phil, 2026-09-22): `accepted` keeps its meaning
|
|
278
|
+
* and the COUNT of these parts rides in an additive `context` key, REQUIRED
|
|
279
|
+
* when it is not zero, enforced against SDKs through conformance vectors. So a
|
|
280
|
+
* part missing from this list is a part no record built on that rule will ever
|
|
281
|
+
* mention.
|
|
282
|
+
*/
|
|
283
|
+
export declare function uncheckedHeldParts(reading: HeldMessage): number[];
|