@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.
- package/README.md +30 -29
- package/dist/agent-record.d.ts +62 -0
- package/dist/agent-record.js +527 -0
- package/dist/agent-schemas.generated.d.ts +15 -0
- package/dist/agent-schemas.generated.js +5759 -0
- package/dist/case-file.d.ts +32 -89
- package/dist/case-file.js +41 -93
- package/dist/case.d.ts +339 -822
- package/dist/case.js +667 -1160
- package/dist/chain.d.ts +19 -47
- package/dist/chain.js +78 -205
- package/dist/claims.d.ts +211 -0
- package/dist/claims.js +1081 -0
- package/dist/cli.d.ts +45 -68
- package/dist/cli.js +618 -1022
- package/dist/compose.js +23 -65
- package/dist/crypto.d.ts +7 -8
- package/dist/crypto.js +60 -147
- package/dist/declaration.d.ts +68 -194
- package/dist/declaration.js +175 -518
- package/dist/environment.d.ts +9 -30
- package/dist/environment.js +17 -66
- package/dist/evidence.d.ts +119 -160
- package/dist/evidence.js +744 -1103
- package/dist/exit-codes.d.ts +11 -27
- package/dist/exit-codes.js +24 -59
- package/dist/file-match.d.ts +33 -79
- package/dist/file-match.js +33 -69
- package/dist/grading.d.ts +98 -135
- package/dist/grading.js +224 -380
- package/dist/handoff.d.ts +62 -160
- package/dist/handoff.js +153 -393
- package/dist/index.d.ts +14 -10
- package/dist/index.js +60 -167
- package/dist/json-schema.d.ts +29 -0
- package/dist/json-schema.js +298 -0
- package/dist/merkle.js +16 -28
- package/dist/message-parts.d.ts +56 -212
- package/dist/message-parts.js +142 -502
- package/dist/side-content.d.ts +11 -22
- package/dist/side-content.js +22 -65
- package/dist/types.d.ts +17 -0
- package/dist/types.js +19 -5
- package/dist/verifier.d.ts +40 -114
- package/dist/verifier.js +196 -535
- package/dist/witness-cache.d.ts +24 -45
- package/dist/witness-cache.js +38 -81
- package/dist/witness-lookup.d.ts +36 -57
- package/dist/witness-lookup.js +89 -139
- package/dist/zip-reader.d.ts +18 -33
- package/dist/zip-reader.js +19 -34
- package/package.json +11 -3
- package/src/agent-record.ts +608 -0
- package/src/agent-schemas.generated.ts +5764 -0
- package/src/case-file.ts +45 -109
- package/src/case.ts +814 -1328
- package/src/chain.ts +85 -224
- package/src/claims.ts +1253 -0
- package/src/cli.ts +714 -1176
- package/src/compose.ts +33 -79
- package/src/crypto.ts +60 -147
- package/src/declaration.ts +219 -656
- package/src/environment.ts +17 -65
- package/src/evidence.ts +958 -1381
- package/src/exit-codes.ts +24 -63
- package/src/file-match.ts +40 -89
- package/src/grading.ts +287 -421
- package/src/handoff.ts +175 -427
- package/src/index.ts +131 -177
- package/src/json-schema.ts +295 -0
- package/src/merkle.ts +16 -28
- package/src/message-parts.ts +155 -533
- package/src/side-content.ts +25 -68
- package/src/types.ts +55 -36
- package/src/verifier.ts +202 -526
- package/src/witness-cache.ts +45 -94
- package/src/witness-lookup.ts +107 -177
- package/src/zip-reader.ts +20 -38
- package/vectors/README.md +52 -34
- package/vectors/agent-record.json +2175 -0
- package/vectors/case-file-index.json +1 -1
- package/vectors/case-split.json +2 -2
- package/vectors/chain-reading.json +3 -3
- package/vectors/file-match.json +285 -148
- package/vectors/jcs-reader.json +6 -6
- package/vectors/pack-grading.json +102 -25
- package/vectors/sandbox-evidence.json +573 -581
- package/vectors/stamp-on-chain.json +18 -18
- package/vectors/witness-cache.json +49 -49
- package/dist/agent-proof.d.ts +0 -28
- package/dist/agent-proof.js +0 -226
- package/dist/run-adapters.d.ts +0 -10
- package/dist/run-adapters.js +0 -35
- package/dist/run.d.ts +0 -209
- package/dist/run.js +0 -1405
- package/src/agent-proof.ts +0 -264
- package/src/run-adapters.ts +0 -44
- package/src/run.ts +0 -1598
package/dist/merkle.js
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
// RFC 9162 binary Merkle tree
|
|
1
|
+
// RFC 9162 binary Merkle tree: build, audit paths, inclusion verification.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// duplicate-last-leaf variant, which produces different roots for
|
|
9
|
-
// non-power-of-two leaf counts.
|
|
10
|
-
//
|
|
11
|
-
// The empty tree (zero leaves) hashes to SHA-256("") — EMPTY_TREE_ROOT_HEX.
|
|
12
|
-
// Conformance is byte-for-byte against the 7 merkle-tree-* vectors of
|
|
13
|
-
// test-vectors-v1.0 (test/conformance.test.ts).
|
|
3
|
+
// SHA-256 with RFC 9162 §2.1 domain separation: leaves prefixed 0x00, interior
|
|
4
|
+
// nodes 0x01. A tree of n leaves splits into a left subtree of k leaves (the
|
|
5
|
+
// largest power of two < n) and a right subtree of n-k, not the Bitcoin
|
|
6
|
+
// duplicate-last-leaf variant. The empty tree hashes to SHA-256("")
|
|
7
|
+
// (EMPTY_TREE_ROOT_HEX).
|
|
14
8
|
import { sha256Bytes } from "./crypto.js";
|
|
15
9
|
export const EMPTY_TREE_ROOT_HEX = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
|
16
10
|
// SHA-256(0x00 || leafBytes) per RFC 9162 §2.1.
|
|
@@ -25,9 +19,8 @@ export async function nodeHash(left, right) {
|
|
|
25
19
|
buf.set(right, 1 + left.length);
|
|
26
20
|
return sha256Bytes(buf);
|
|
27
21
|
}
|
|
28
|
-
// The RFC 9162 Merkle Tree Hash of already-hashed leaves
|
|
29
|
-
//
|
|
30
|
-
// yields SHA-256("").
|
|
22
|
+
// The RFC 9162 Merkle Tree Hash of already-hashed leaves (the output of
|
|
23
|
+
// `leafHash`). Zero leaves yields SHA-256("").
|
|
31
24
|
export async function merkleRoot(leafHashes) {
|
|
32
25
|
const n = leafHashes.length;
|
|
33
26
|
if (n === 0)
|
|
@@ -37,9 +30,8 @@ export async function merkleRoot(leafHashes) {
|
|
|
37
30
|
const k = largestPow2LessThan(n);
|
|
38
31
|
return nodeHash(await merkleRoot(leafHashes.slice(0, k)), await merkleRoot(leafHashes.slice(k)));
|
|
39
32
|
}
|
|
40
|
-
// The inclusion proof for the leaf at index m
|
|
41
|
-
//
|
|
42
|
-
// root). Throws RangeError when m is out of range.
|
|
33
|
+
// The inclusion proof for the leaf at index m (RFC 9162 §2.1.3): sibling hashes
|
|
34
|
+
// bottom-up, empty for a single-leaf tree. Throws RangeError when m is out of range.
|
|
43
35
|
export async function auditPath(m, leafHashes) {
|
|
44
36
|
if (!Number.isInteger(m) || m < 0 || m >= leafHashes.length) {
|
|
45
37
|
throw new RangeError("merkle: leaf index out of range");
|
|
@@ -56,28 +48,24 @@ async function path(m, leafHashes) {
|
|
|
56
48
|
}
|
|
57
49
|
return [...(await path(m - k, leafHashes.slice(k))), await merkleRoot(leafHashes.slice(0, k))];
|
|
58
50
|
}
|
|
59
|
-
// Verify an RFC 9162 §2.1.3 inclusion proof. `leaf` is the leaf hash
|
|
60
|
-
//
|
|
61
|
-
// `expectedRoot` is the merkleRoot committed by the checkpoint envelope.
|
|
62
|
-
// True iff the path reconstructs the expected root. Never throws.
|
|
51
|
+
// Verify an RFC 9162 §2.1.3 inclusion proof. `leaf` is the leaf hash, `path` the
|
|
52
|
+
// audit path bottom-up, `expectedRoot` the root the checkpoint commits to. Never throws.
|
|
63
53
|
export async function verifyInclusion(leaf, leafIndex, totalLeaves, auditPath, expectedRoot) {
|
|
64
54
|
if (!Number.isInteger(leafIndex) || leafIndex < 0 || totalLeaves <= 0 || leafIndex >= totalLeaves) {
|
|
65
55
|
return false;
|
|
66
56
|
}
|
|
67
57
|
if (totalLeaves === 1)
|
|
68
58
|
return auditPath.length === 0 && bytesEqual(leaf, expectedRoot);
|
|
69
|
-
// Arithmetic, not bitwise:
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
// are exact for every safe integer, and the stated index and count reach
|
|
73
|
-
// this walk as safe integers.
|
|
59
|
+
// Arithmetic, not bitwise: `&` and `>>=` truncate to 32 bits, so a tree past
|
|
60
|
+
// 2**31 leaves would walk a different tree. `% 2` and halving floor are exact
|
|
61
|
+
// for every safe integer.
|
|
74
62
|
let fn = leafIndex;
|
|
75
63
|
let sn = totalLeaves - 1;
|
|
76
64
|
let r = leaf;
|
|
77
65
|
const half = (v) => Math.floor(v / 2);
|
|
78
66
|
for (const p of auditPath) {
|
|
79
67
|
if (sn === 0)
|
|
80
|
-
return false; // path longer than the tree depth
|
|
68
|
+
return false; // path longer than the tree depth
|
|
81
69
|
if (fn % 2 === 1 || fn === sn) {
|
|
82
70
|
r = await nodeHash(p, r);
|
|
83
71
|
if (fn % 2 === 0) {
|
package/dist/message-parts.d.ts
CHANGED
|
@@ -1,157 +1,71 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The result of a part whose supplied bytes hash to its `content_hash` AND
|
|
3
|
-
* count to its `content_length`.
|
|
4
|
-
*/
|
|
1
|
+
/** The supplied bytes match both `content_hash` and `content_length`. */
|
|
5
2
|
export declare const PART_MATCHED = "part_matched";
|
|
6
3
|
/**
|
|
7
|
-
* The
|
|
8
|
-
*
|
|
9
|
-
* applies to this part, and it replaces BOTH values with the held bytes' own.
|
|
4
|
+
* The supplied bytes miss either value, and no respelling explains the digest.
|
|
5
|
+
* The held message replaces both values with the held bytes' own.
|
|
10
6
|
*/
|
|
11
7
|
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
|
-
*/
|
|
8
|
+
/** Bytes not supplied, or the supply was refused. Not checked, and not clean. */
|
|
17
9
|
export declare const PART_UNCHECKED = "part_unchecked";
|
|
18
10
|
/**
|
|
19
|
-
* A
|
|
20
|
-
*
|
|
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.
|
|
11
|
+
* A file part with its content inside the message, committed by
|
|
12
|
+
* `message_hash` itself. Nothing is hashed or substituted.
|
|
26
13
|
*/
|
|
27
14
|
export declare const PART_INLINE_BYTES = "part_inline_bytes";
|
|
28
15
|
/**
|
|
29
|
-
* An inline part whose `content_hash
|
|
30
|
-
*
|
|
31
|
-
*
|
|
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.
|
|
16
|
+
* An inline part whose `content_hash` or `content_length` disagrees with its
|
|
17
|
+
* inline content. The raw UTF-8, base64 and hex readings are all tried, but one
|
|
18
|
+
* reading must satisfy every claim present. Nothing is substituted.
|
|
47
19
|
*/
|
|
48
20
|
export declare const PART_INLINE_MISMATCHED = "part_inline_mismatched";
|
|
49
21
|
/**
|
|
50
22
|
* `kind: "file"` without a conformant `content_hash` and `content_length`. A
|
|
51
|
-
* sender's defect
|
|
52
|
-
* the receiver did nothing wrong by holding it.
|
|
23
|
+
* sender's defect, so it is named rather than refused.
|
|
53
24
|
*/
|
|
54
25
|
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
|
-
*/
|
|
26
|
+
/** Any other part: ordinary JSON content, not checked by this rule. */
|
|
59
27
|
export declare const PART_NOT_A_FILE = "part_not_a_file";
|
|
60
28
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
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.
|
|
29
|
+
* Refusal of one supplied entry: it names no part, or a part this rule cannot
|
|
30
|
+
* check. Carries that part's index when the part exists, else only the entry
|
|
31
|
+
* position.
|
|
68
32
|
*/
|
|
69
33
|
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
|
-
*/
|
|
34
|
+
/** Refusal: two different byte strings were supplied for one part. */
|
|
74
35
|
export declare const CONFLICTING_PART_BYTES = "conflicting_part_bytes";
|
|
75
36
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
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.
|
|
37
|
+
* Refusal of one supplied entry: the bytes miss the descriptor, but a base64
|
|
38
|
+
* or hex respelling in either direction hashes to it. That is a receiver's
|
|
39
|
+
* coding defect (an undecoded supply, or a decoded PEM, DER or JWT the sender
|
|
40
|
+
* committed as text), not a sender's tamper, so it must not produce a tamper
|
|
41
|
+
* record.
|
|
89
42
|
*/
|
|
90
43
|
export declare const PART_ENCODING_MISMATCH = "part_encoding_mismatch";
|
|
91
44
|
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
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.
|
|
45
|
+
* Message-level: `parts` is present, not null and not an array (proto3 JSON
|
|
46
|
+
* encodes a map this way). Named, not refused. A `parts` of `null` is absent.
|
|
101
47
|
*/
|
|
102
48
|
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
|
-
*/
|
|
49
|
+
/** Note, not a refusal: one part supplied more than once with identical bytes. */
|
|
108
50
|
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
|
-
*/
|
|
51
|
+
/** The fixed order refusals are reported in. */
|
|
113
52
|
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
|
-
*/
|
|
53
|
+
/** The fixed order message-level results are reported in. */
|
|
118
54
|
export declare const MESSAGE_RESULT_ORDER: readonly ["parts_not_an_array"];
|
|
119
55
|
/**
|
|
120
|
-
* The
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* inline spelling either reference kernel has read from a real producer.
|
|
124
|
-
* `content` and `data` were added on inference rather than on an observed
|
|
125
|
-
* wire shape, and sanning-io/anchor's `filePart()` and `file_part()` SDK
|
|
126
|
-
* helpers, the only producer this plane ships, confirm the inference was
|
|
127
|
-
* wrong: both emit a descriptor part, `kind: "file"` plus `content_hash` and
|
|
128
|
-
* `content_length`, and neither ever writes a `content` or `data` wire key
|
|
129
|
-
* for inline bytes. None is A2A 1.0.0's either: its `Part` has no `kind` and
|
|
130
|
-
* carries a file as `raw` or `url`, so a literal A2A part reads
|
|
131
|
-
* `part_not_a_file` (envelope-spec §4.1, A19). The one key present with a
|
|
132
|
-
* value that is not `null` is the inline content.
|
|
133
|
-
*
|
|
134
|
-
* A key present with `null` is an ABSENT inline value, on A14's ruling that an
|
|
135
|
-
* absent value and one written as `null` name one thing. That matters: a
|
|
136
|
-
* proto3 JSON encoder can write an unset field as `"bytes": null`, and reading
|
|
137
|
-
* that as inline made a conformant DESCRIPTOR part uncheckable
|
|
138
|
-
* (proof#156 gate). Such a part falls through to the descriptor rule, which is
|
|
139
|
-
* what it is.
|
|
56
|
+
* The key that makes a part carry its content inline (envelope-spec §4.1).
|
|
57
|
+
* `bytes` is A2A's spelling. A key present with `null` is absent, so a proto3
|
|
58
|
+
* `"bytes": null` on a descriptor part still reads as a descriptor.
|
|
140
59
|
*/
|
|
141
60
|
export declare const INLINE_BYTES_KEYS: readonly ["bytes"];
|
|
142
61
|
/**
|
|
143
|
-
* The largest
|
|
144
|
-
*
|
|
145
|
-
* would read as two numbers.
|
|
62
|
+
* The largest exactly representable integer, the bound §4.1 puts on
|
|
63
|
+
* `event.sequence`. A larger `content_length` is malformed.
|
|
146
64
|
*/
|
|
147
65
|
export declare const MAX_SAFE_INTEGER: number;
|
|
148
66
|
/**
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* `part` is an INDEX into the message's `parts` array. It is an index rather
|
|
152
|
-
* than a content hash on purpose: keying the supply by the value the check is
|
|
153
|
-
* about to grade would mean a receiver could only supply bytes it had already
|
|
154
|
-
* agreed with, which is the whole defect stated backwards.
|
|
67
|
+
* A byte string the receiver holds, keyed by the index of its part. Keyed by
|
|
68
|
+
* index, not by hash, so a receiver can supply bytes that disagree.
|
|
155
69
|
*/
|
|
156
70
|
export interface SuppliedPart {
|
|
157
71
|
part: unknown;
|
|
@@ -163,13 +77,9 @@ export interface PartReading {
|
|
|
163
77
|
result: string;
|
|
164
78
|
}
|
|
165
79
|
/**
|
|
166
|
-
* A caller error.
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* and `entry` is the POSITION of the offending supplied entry in the sequence
|
|
170
|
-
* the caller passed. The position is recorded rather than the value the caller
|
|
171
|
-
* named, so a caller that names a part with a float or a string cannot make two
|
|
172
|
-
* kernels serialize one reading two ways.
|
|
80
|
+
* A caller error. `part` is the part index where there is one; `entry` is the
|
|
81
|
+
* position of the supplied entry, recorded rather than the caller's value so
|
|
82
|
+
* every implementation serializes it the same way.
|
|
173
83
|
*/
|
|
174
84
|
export interface Refusal {
|
|
175
85
|
name: string;
|
|
@@ -177,28 +87,17 @@ export interface Refusal {
|
|
|
177
87
|
entry: number | null;
|
|
178
88
|
}
|
|
179
89
|
/**
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
* note as a refusal by looking only at a name.
|
|
90
|
+
* Reported, but refuses nothing. Kept in its own list so a note is never read
|
|
91
|
+
* as a refusal by name alone.
|
|
183
92
|
*/
|
|
184
93
|
export type Note = Refusal;
|
|
185
94
|
/**
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
* `heldMessage` is ALWAYS the message the receiver commits to and is never
|
|
189
|
-
* null: a refusal is scoped to the supply it is about and never denies the
|
|
190
|
-
* receiver a record (proof#156 gate). The caller commits `event.message_hash`
|
|
191
|
-
* as SHA-256 over its JCS (RFC 8785) canonical bytes, which is the rule section
|
|
192
|
-
* 4.1 already states and the reason this module does not hash anything itself.
|
|
193
|
-
*
|
|
194
|
-
* `messageResults` carries the message-level named results, in
|
|
195
|
-
* `MESSAGE_RESULT_ORDER`. It is empty for every message whose shape the rule
|
|
196
|
-
* reads.
|
|
95
|
+
* What a receiver commits to, and what it found.
|
|
197
96
|
*
|
|
198
|
-
* `
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
* `outcome: "rejected"`.
|
|
97
|
+
* `heldMessage` is never null; the caller commits `event.message_hash` as
|
|
98
|
+
* SHA-256 over its JCS bytes. `messageResults` is in `MESSAGE_RESULT_ORDER`.
|
|
99
|
+
* `substituted` lists the parts replaced by held bytes; non-empty means the
|
|
100
|
+
* receiver writes `outcome: "rejected"`.
|
|
202
101
|
*/
|
|
203
102
|
export interface HeldMessage {
|
|
204
103
|
parts: PartReading[];
|
|
@@ -209,83 +108,28 @@ export interface HeldMessage {
|
|
|
209
108
|
substituted: number[];
|
|
210
109
|
}
|
|
211
110
|
/**
|
|
212
|
-
* Whether
|
|
213
|
-
*
|
|
214
|
-
* 4.1 states, in the order that keeps inline content out. An INLINE part is
|
|
215
|
-
* accepted and committed by the message itself (proof#157), so there is nothing
|
|
216
|
-
* here for supplied bytes to be checked against and this answers `false`.
|
|
111
|
+
* Whether bytes beside the message are checked against this part: true only
|
|
112
|
+
* for a conformant descriptor part, never for an inline one.
|
|
217
113
|
*/
|
|
218
114
|
export declare function isFilePart(part: unknown): boolean;
|
|
219
115
|
/**
|
|
220
116
|
* What a receiver commits to, given the message and the bytes it holds.
|
|
221
117
|
*
|
|
222
|
-
* `supplied`
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
* Where a part's bytes hash to something other than its `content_hash`, or
|
|
229
|
-
* where their count is not its `content_length`, the returned `heldMessage`
|
|
230
|
-
* carries that part's `content_hash` and `content_length` replaced by the held
|
|
231
|
-
* bytes' own. The receiver commits `event.message_hash` over THAT, so its
|
|
232
|
-
* record and the sender's disagree and the pair fails on `message_hash`.
|
|
233
|
-
*
|
|
234
|
-
* A refusal is scoped to the supplied entry it is about: the part it names is
|
|
235
|
-
* not checked and not substituted, every other part reads as it would have, and
|
|
236
|
-
* `heldMessage` is returned either way. Nothing a caller or a sender can do
|
|
237
|
-
* denies the receiver a record.
|
|
118
|
+
* `supplied` names each byte string by its part index. With none, every file
|
|
119
|
+
* part is `PART_UNCHECKED` and the held message is the message given. Where a
|
|
120
|
+
* part's bytes miss its hash or length, `heldMessage` carries the held bytes'
|
|
121
|
+
* own values for that part, so the pair fails on `message_hash`. A refusal is
|
|
122
|
+
* scoped to its entry; `heldMessage` is always returned.
|
|
238
123
|
*/
|
|
239
124
|
export declare function commitHeldMessage(message: unknown, supplied?: readonly SuppliedPart[]): Promise<HeldMessage>;
|
|
240
125
|
/**
|
|
241
|
-
* The parts the receiver
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
* is one of `HELD_UNCHECKED_RESULTS`: the receiver had bytes for that part,
|
|
247
|
-
* the rule declined to grade them, and NOTHING about that reaches the
|
|
248
|
-
* committed record.
|
|
249
|
-
*
|
|
250
|
-
* THE RULE, in one sentence, because a filter is not a rule. A part is in here
|
|
251
|
-
* where the receiver held bytes for it AND the message itself does not commit
|
|
252
|
-
* its content. `PART_UNCHECKED` is a refused supply;
|
|
253
|
-
* `PART_MALFORMED_DESCRIPTOR` is a part whose content travels BESIDE the
|
|
254
|
-
* message under a claim no receiver can use, so the receiver's bytes are
|
|
255
|
-
* exactly as ungraded and the record says as little. The first cut of this
|
|
256
|
-
* function omitted the second, which the third gate measured: a `kind: "file"`
|
|
257
|
-
* part with a non-conformant `content_hash` or `content_length`, bytes held,
|
|
258
|
-
* read `[]` here, so a caller following the rule this module prescribes wrote
|
|
259
|
-
* `accepted` over a part it held bytes for and never checked. That is the
|
|
260
|
-
* class the mechanism exists to close, and Phil named it in proof#162's
|
|
261
|
-
* ratification as the gap that decision inherits unless it is closed here.
|
|
262
|
-
*
|
|
263
|
-
* Three results are excluded ON PURPOSE. An inline part (`PART_INLINE_BYTES`,
|
|
264
|
-
* `PART_INLINE_MISMATCHED`) and a non-file part (`PART_NOT_A_FILE`) carry
|
|
265
|
-
* their content INSIDE the message, and `message_hash` commits it on both
|
|
266
|
-
* sides: bytes supplied beside one of those grade nothing and hide nothing,
|
|
267
|
-
* because a reader of either pack reads the content itself. Naming them would
|
|
268
|
-
* fire the caller's obligation on parts the swapped-bytes attack cannot reach
|
|
269
|
-
* at all.
|
|
270
|
-
*
|
|
271
|
-
* Why it is worth a function rather than a note. Delivering a tampered copy of
|
|
272
|
-
* a part alone is `part_mismatched`, is substituted, and fails the pair on
|
|
273
|
-
* `message_hash`. Delivering the tampered copy AND the true one is
|
|
274
|
-
* `conflicting_part_bytes`, is never substituted, and the pair VERIFIES, so
|
|
275
|
-
* adding data suppresses detection and the only trace is a refusal the
|
|
276
|
-
* receiving side publishes at its own discretion.
|
|
277
|
-
*
|
|
278
|
-
* NOT the same as "this part reads `PART_UNCHECKED`". A receiver that holds no
|
|
279
|
-
* bytes at all reads every file part that way, and that is the COMMON CASE,
|
|
280
|
-
* unchanged by A18 and not a reason to withhold `accepted`. Only a part whose
|
|
281
|
-
* bytes the receiver actually held and could not grade is in here.
|
|
126
|
+
* The parts the receiver held bytes for and did not check: a refusal names the
|
|
127
|
+
* part and its result is in `HELD_UNCHECKED_RESULTS`, or the part is a URL part
|
|
128
|
+
* that reads `PART_NOT_A_FILE` and the refusal is `BYTES_FOR_UNKNOWN_PART`. A
|
|
129
|
+
* caller writes `accepted` only when this and `substituted` are both empty, and
|
|
130
|
+
* records the positions and their count when it is not zero.
|
|
282
131
|
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
* record layer does instead (Phil, 2026-09-22): `accepted` keeps its meaning
|
|
286
|
-
* and the COUNT of these parts rides in an additive `context` key, REQUIRED
|
|
287
|
-
* when it is not zero, enforced against SDKs through conformance vectors. So a
|
|
288
|
-
* part missing from this list is a part no record built on that rule will ever
|
|
289
|
-
* mention.
|
|
132
|
+
* Not the same as "reads `PART_UNCHECKED`": a receiver holding no bytes reads
|
|
133
|
+
* every file part that way, which is the common case.
|
|
290
134
|
*/
|
|
291
135
|
export declare function uncheckedHeldParts(reading: HeldMessage): number[];
|