@sanning/proof 0.9.2 → 0.11.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.
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "rule": "pack-grading",
3
- "ruleVersion": 2,
4
- "statement": "What a verification surface may CLAIM about a pack it was handed. verified is reachable ONLY when every record's own bytes bound to its own commitment and nothing is missing, unreadable, surplus or omitted. Absent or unreadable evidence is incomplete (UNKNOWN), never failed and never verified. A hard kernel verdict wins: malformed > failed > incomplete > the kernel's own headline.",
3
+ "ruleVersion": 5,
4
+ "statement": "What a verification surface may CLAIM about a pack it was handed. verified is reachable ONLY when every record's own bytes bound to its own commitment and nothing is missing, unreadable, surplus or omitted. Absent or unreadable evidence is incomplete (UNKNOWN), never failed and never verified. A hard kernel verdict wins: malformed > failed > incomplete > the kernel's own headline. A pack that DEVIATES from its own signed declaration (evidence-bundle.md \u00a75.4) is failed, and every deviation is a cause: a declared record that is absent, a present record nobody declared, or a record the pack declared withheld whose bytes its own SIGNED BODY supplies. A reading that rests instead on the UNSIGNED content handed in beside a pack is not a deviation and never reaches failed (proof#147): each content-dependent finding is its own cause and carries the pack to incomplete, because whoever assembles a case fills that map in, so an accuser holding an intact pack could otherwise reach failed with no key at all. A deviation and a content-dependent finding together are still failed, because the deviation reads off the signed body and a failing check wins. A declaration check that came back partial therefore splits in THREE, and only two of the three turn on the declaration's readability: no declaration member in the body at all moves nothing, because every pack built before \u00a75.4 is that case; a declaration the reader CANNOT READ makes the pack incomplete, because declaration_version is chosen by the party that assembles the pack and has no accept-set, so an unreadable claim must never reach a clean verdict; and a declaration the reader read perfectly well whose disclosure only unsigned bytes could settle makes the pack incomplete as well, named as what the reader could not establish rather than as what the holder did. The grade carries the declaration check verdict in its own output field, so a consumer that keeps only the grade can still tell a declared pack from an undeclared one.",
5
5
  "home": "@sanning/proof — ts/vectors/pack-grading.json. This file is the RULE, not a test fixture of one implementation. The kernel implements it in ts/src/grading.ts and is held to it by ts/test/grading.test.ts.",
6
6
  "consumers": "A surface that grades packs SHOULD call the kernel's gradePack rather than derive the rule again. A surface that cannot yet (an older pinned kernel, another language) vendors this file byte-for-byte, asserts its own derivation against it, and runs a drift check that compares its copy against the copy shipped in the @sanning/proof tarball at vectors/pack-grading.json. A vendored copy that is not byte-identical to this one is a defect, not a variant.",
7
- "howToUse": "Adapt a kernel verification result and a disclosure account into the shapes below, call gradePack, and assert the expectations. `kernel` gives the per-event outcome classes the kernel returns; `logs` gives the account of which records disclosed bytes; `omissions` gives the signed-leaf_count completeness notes. Nothing here depends on how the bytes were transported — a directory read and a browser drop produce the same account and must produce the same verdict.",
7
+ "howToUse": "Adapt a kernel verification result and a disclosure account into the shapes below, call gradePack, and assert the expectations. `kernel` gives the per-event outcome classes the kernel returns; `logs` gives the account of which records disclosed bytes; `omissions` gives the signed-leaf_count completeness notes. Nothing here depends on how the bytes were transported: a directory read and a browser drop produce the same account and must produce the same verdict. `declaration`, where a case carries one, gives the declaration check's verdict, whether the body carried a declaration member at all, and how many deviations it found; pass a check of that shape as gradePack's optional fourth argument. A case with no `declaration` key calls gradePack with three arguments, which is the pre-§5.4 behavior and must not move, and its `expect.declaration` is absent.",
8
8
  "kernelEventClasses": {
9
9
  "okBound": "{ ok: true, contentOk: true } — the record's own bytes hashed to its own commitment",
10
10
  "okUnknown": "{ ok: true, contentOk: null } — structurally fine, but no bytes were disclosed",
@@ -52,6 +52,14 @@
52
52
  "omissions": [],
53
53
  "expect": { "verdict": "incomplete", "recordsVerified": 0, "recordsFailed": 0, "recordsUndetermined": 26, "causeCount": 1 }
54
54
  },
55
+ {
56
+ "id": "withheld-record-clean-account",
57
+ "why": "the case the terminal got wrong (proof#110). A bundle the kernel grades `verified` under evidence-bundle.md §3, holding one record the producer WITHHELD. The disclosure account is clean, and correctly so: no bytes were ever expected for a record that was never offered, so nothing is missing, unreadable, surplus or omitted and there is no cause to report. The record is still unhashed. The PACK is `incomplete` while the BUNDLE is `verified`: the two enums are distinct, and an undetermined record ON ITS OWN is enough to downgrade, with no cause beside it. Every other incomplete case here carries a cause, so a surface could satisfy all of them by keying off `causes` alone and still print a green verdict over a record nobody checked, which is exactly what `proof verify` did.",
58
+ "kernel": { "status": "verified", "okBound": 2, "okUnknown": 1, "mismatched": 0, "brokenNoContent": 0 },
59
+ "logs": { "expected": 2, "present": 2, "missing": 0, "unreadable": 0, "extra": 0 },
60
+ "omissions": [],
61
+ "expect": { "verdict": "incomplete", "recordsVerified": 2, "recordsFailed": 0, "recordsUndetermined": 1, "causeCount": 0 }
62
+ },
55
63
  {
56
64
  "id": "one-log-unreadable",
57
65
  "why": "a file we could not read is our fault, not the record's — UNKNOWN",
@@ -131,6 +139,126 @@
131
139
  "logs": { "expected": 26, "present": 26, "missing": 0, "unreadable": 0, "extra": 0 },
132
140
  "omissions": [],
133
141
  "expect": { "verdict": "incomplete", "recordsVerified": 26, "recordsFailed": 0, "recordsUndetermined": 0, "causeCount": 0 }
142
+ },
143
+ {
144
+ "id": "declaration-matches",
145
+ "why": "a pack that holds exactly what its declaration says it holds: the declaration adds no cause and moves no verdict",
146
+ "kernel": { "status": "verified", "okBound": 26, "okUnknown": 0, "mismatched": 0, "brokenNoContent": 0 },
147
+ "logs": { "expected": 26, "present": 26, "missing": 0, "unreadable": 0, "extra": 0 },
148
+ "omissions": [],
149
+ "declaration": { "verdict": "verified", "present": true, "deviations": 0 },
150
+ "expect": { "verdict": "verified", "recordsVerified": 26, "recordsFailed": 0, "recordsUndetermined": 0, "causeCount": 0, "declaration": "verified" }
151
+ },
152
+ {
153
+ "id": "declaration-deviates",
154
+ "why": "the trimmed hand-over. Every record present verifies and nothing is missing from the disclosure account, so every other check is green; the pack deviates from its own declaration and that is a FAILURE, never an absence",
155
+ "kernel": { "status": "verified", "okBound": 26, "okUnknown": 0, "mismatched": 0, "brokenNoContent": 0 },
156
+ "logs": { "expected": 26, "present": 26, "missing": 0, "unreadable": 0, "extra": 0 },
157
+ "omissions": [],
158
+ "declaration": { "verdict": "failed", "present": true, "deviations": 1 },
159
+ "expect": { "verdict": "failed", "recordsVerified": 26, "recordsFailed": 0, "recordsUndetermined": 0, "causeCount": 1, "declaration": "failed" }
160
+ },
161
+ {
162
+ "id": "declaration-content-dependent",
163
+ "why": "the pack agrees with its own claim everywhere its signature reaches, and does not line up with the UNSIGNED content handed in beside it. A NAMED cause and incomplete, never failed: an accuser holding an intact pack reaches this by dropping one file or adding one, with no key at all (proof#147)",
164
+ "kernel": {
165
+ "status": "verified",
166
+ "okBound": 26,
167
+ "okUnknown": 0,
168
+ "mismatched": 0,
169
+ "brokenNoContent": 0
170
+ },
171
+ "logs": {
172
+ "expected": 26,
173
+ "present": 26,
174
+ "missing": 0,
175
+ "unreadable": 0,
176
+ "extra": 0
177
+ },
178
+ "omissions": [],
179
+ "declaration": {
180
+ "verdict": "partial",
181
+ "present": true,
182
+ "deviations": 0,
183
+ "contentFindings": 1
184
+ },
185
+ "expect": {
186
+ "verdict": "incomplete",
187
+ "recordsVerified": 26,
188
+ "recordsFailed": 0,
189
+ "recordsUndetermined": 0,
190
+ "causeCount": 1,
191
+ "declaration": "partial"
192
+ }
193
+ },
194
+ {
195
+ "id": "declaration-deviation-beats-a-content-finding",
196
+ "why": "one of each, over different records. The deviation reads off the pack's own signed body so the pack FAILS, and the content-dependent finding is still its own cause: two classes, both reported, neither folded into the other",
197
+ "kernel": {
198
+ "status": "verified",
199
+ "okBound": 26,
200
+ "okUnknown": 0,
201
+ "mismatched": 0,
202
+ "brokenNoContent": 0
203
+ },
204
+ "logs": {
205
+ "expected": 26,
206
+ "present": 26,
207
+ "missing": 0,
208
+ "unreadable": 0,
209
+ "extra": 0
210
+ },
211
+ "omissions": [],
212
+ "declaration": {
213
+ "verdict": "failed",
214
+ "present": true,
215
+ "deviations": 1,
216
+ "contentFindings": 1
217
+ },
218
+ "expect": {
219
+ "verdict": "failed",
220
+ "recordsVerified": 26,
221
+ "recordsFailed": 0,
222
+ "recordsUndetermined": 0,
223
+ "causeCount": 2,
224
+ "declaration": "failed"
225
+ }
226
+ },
227
+ {
228
+ "id": "declaration-names-every-deviation",
229
+ "why": "three deviations in one pack are three causes. A rule that reported one shortfall would pass the case above and lose the other two findings",
230
+ "kernel": { "status": "verified", "okBound": 26, "okUnknown": 0, "mismatched": 0, "brokenNoContent": 0 },
231
+ "logs": { "expected": 26, "present": 26, "missing": 0, "unreadable": 0, "extra": 0 },
232
+ "omissions": [],
233
+ "declaration": { "verdict": "failed", "present": true, "deviations": 3 },
234
+ "expect": { "verdict": "failed", "recordsVerified": 26, "recordsFailed": 0, "recordsUndetermined": 0, "causeCount": 3, "declaration": "failed" }
235
+ },
236
+ {
237
+ "id": "declaration-deviation-beats-incomplete",
238
+ "why": "a proven mismatch between what a pack claimed and what it carries is never softened into absence, even when the pack is also missing disclosed bytes",
239
+ "kernel": { "status": "verified", "okBound": 20, "okUnknown": 6, "mismatched": 0, "brokenNoContent": 0 },
240
+ "logs": { "expected": 26, "present": 20, "missing": 6, "unreadable": 0, "extra": 0 },
241
+ "omissions": [],
242
+ "declaration": { "verdict": "failed", "present": true, "deviations": 1 },
243
+ "expect": { "verdict": "failed", "recordsVerified": 20, "recordsFailed": 0, "recordsUndetermined": 6, "causeCount": 2, "declaration": "failed" }
244
+ },
245
+ {
246
+ "id": "declaration-absent-moves-nothing",
247
+ "why": "a pack with no declaration: partial is the declaration check's own answer, and pack grading keeps its existing verdict. Every pack built before \u00a75.4 is this case, and re-grading them would be a spec change wearing an optional argument. `present` is false, which is what separates this case from the one below it",
248
+ "kernel": { "status": "verified", "okBound": 26, "okUnknown": 0, "mismatched": 0, "brokenNoContent": 0 },
249
+ "logs": { "expected": 26, "present": 26, "missing": 0, "unreadable": 0, "extra": 0 },
250
+ "omissions": [],
251
+ "declaration": { "verdict": "partial", "present": false, "deviations": 0 },
252
+ "expect": { "verdict": "verified", "recordsVerified": 26, "recordsFailed": 0, "recordsUndetermined": 0, "causeCount": 0, "declaration": "partial" }
253
+ },
254
+ {
255
+ "id": "declaration-unreadable-is-incomplete",
256
+ "why": "a pack that DOES carry a declaration and wrote it in a shape this reader cannot read. `declaration_version` is chosen by the same party that assembles the pack and has no accept-set, so a holder can trim a record, write one unreadable field and re-seal the whole body with its own key: every other check stays green and the evidence of the trim sits inside the signed body. `partial` keeps its meaning on the CHECK, and the PACK is incomplete, because a claim the reader cannot read is not a claim the reader may ignore. The case above and this one differ only in `present`, and a rule that reads the verdict alone passes both while grading one of them clean",
257
+ "kernel": { "status": "verified", "okBound": 26, "okUnknown": 0, "mismatched": 0, "brokenNoContent": 0 },
258
+ "logs": { "expected": 26, "present": 26, "missing": 0, "unreadable": 0, "extra": 0 },
259
+ "omissions": [],
260
+ "declaration": { "verdict": "partial", "present": true, "deviations": 0 },
261
+ "expect": { "verdict": "incomplete", "recordsVerified": 26, "recordsFailed": 0, "recordsUndetermined": 0, "causeCount": 0, "declaration": "partial" }
134
262
  }
135
263
  ]
136
264
  }
@@ -4,7 +4,7 @@
4
4
  "statement": "What re-fetching a stamp's witness from a gateway DOES and DOES NOT settle. A stamp body carries no producer signature and no inclusion proof of its own, so the witness is the entire independent part of it (evidence-bundle.md §5.2.4): confirming that a gateway holds the exact bytes named by anchor_ref is the only check that turns Sanning's own assertion into evidence. It is an ONLINE act and always optional — an offline verdict is unchanged by it and never claims it happened. Four states, never three: not-checked (no gateway was asked, or the stamp names no witness), confirm (a gateway returned the witnessed bytes), mismatch (a gateway returned DIFFERENT bytes — the only outcome that fails anything), unreachable (we asked and nobody answered — an availability gap, never an accusation). The expected bytes are stamps[].stamp_bytes VERBATIM, because §5.2.2 defines that field as the exact bytes handed to the witness; re-canonicalizing them would compare the chain against bytes nobody signed.",
5
5
  "home": "@sanning/proof — ts/vectors/stamp-on-chain.json. This file is the RULE, not a test fixture of one implementation. The TypeScript kernel implements it in ts/src/evidence.ts (refetchStamps / refetchStamp / probeGateway / rollupOnChain) and is held to it by ts/test/stamp-on-chain-vectors.test.ts; the Python kernel implements it in src/sanning_proof/evidence.py (_refetch_stamps / _refetch_stamp / _probe_gateway / _rollup_on_chain) and is held to the SAME file by tests/test_stamp_on_chain_vectors.py. Neither kernel can pass by agreeing with the other.",
6
6
  "consumers": "A surface that re-fetches a stamp witness SHOULD call the kernel's verifyEvidenceBundle with `gateways` rather than derive the rule again. A surface that cannot yet (an older pinned kernel, another language) vendors this file byte-for-byte, asserts its own derivation against it, and runs a drift check against the copy shipped in the @sanning/proof tarball at vectors/stamp-on-chain.json — failing when it cannot find a copy to compare against. A vendored copy that is not byte-identical to this one is a defect, not a variant. In particular: a surface MUST NOT collapse the four states into a boolean. A reader that cannot distinguish not-checked from unreachable will report that no gateway could be reached when none was tried, which is a false accusation manufactured by a missing state, and is the defect this table was written after.",
7
- "howToUse": "Each case describes one sanning.stamp.evidence/v1 pack and one set of scripted gateway responses. Build the pack from `packScaffold`: for each entry of `stamps` (index i, 0-based) sign ONE sanning.events/v1 envelope over the record {\"event\":{\"content_hash\":\"<64×a>\"},\"i\":i} with event_id `ev-<i>` and the fixed seed, take its leaf under RFC 9162 (SHA-256(0x00 ‖ JCS(envelope))), make a one-leaf tree, and build the stamp body from `stampBodyScaffold` with merkle_root = that root, leaf_count = 1 and one sub-tree covering [0,1). stamp_bytes is JCS(stamp body) hex-encoded; stamp_ref is SHA-256 of the DECODED bytes. When the entry's `witnessed` is true attach anchor_ref = {kind: \"arweave\", id: `<anchorIdPrefix><i>`}, otherwise omit anchor_ref entirely. Give each stamp one event whose inclusion is {stamp_ref, leaf_index: 0, leaf_count: 1, leaf_hash, audit_path: []}. Wrap the body per `wrapperScaffold`, set body_hash = SHA-256(JCS(body)) and sign the wrapper (minus `signature`) with the same seed. Then call the kernel with the case's `gateways` and a fetch seam that answers `{gateway}/{anchor id}` per the `returns` entry for that stamp at that gateway's index, and assert `expect`. A case with an empty `gateways` list is called with no gateway list at all — that is the not-checked state and it must be reachable without a fetch seam.",
7
+ "howToUse": "Each case describes one sanning.stamp.evidence/v2 pack and one set of scripted gateway responses. Build the pack from `packScaffold`: for each entry of `stamps` (index i, 0-based) sign ONE sanning.events/v2 envelope over the record {\"event\":{\"content_hash\":\"<64×a>\"},\"i\":i} with event_id `ev-<i>` and the fixed seed, take its leaf under RFC 9162 (SHA-256(0x00 ‖ JCS(envelope))), make a one-leaf tree, and build the stamp body from `stampBodyScaffold` with merkle_root = that root, leaf_count = 1 and one sub-tree covering [0,1). stamp_bytes is JCS(stamp body) hex-encoded; stamp_ref is SHA-256 of the DECODED bytes. When the entry's `witnessed` is true attach anchor_ref = {kind: \"arweave\", id: `<anchorIdPrefix><i>`}, otherwise omit anchor_ref entirely. Give each stamp one event whose inclusion is {stamp_ref, leaf_index: 0, leaf_count: 1, leaf_hash, audit_path: []}. Wrap the body per `wrapperScaffold`, set body_hash = SHA-256(JCS(body)) and sign the wrapper (minus `signature`) with the same seed. Then call the kernel with the case's `gateways` and a fetch seam that answers `{gateway}/{anchor id}` per the `returns` entry for that stamp at that gateway's index, and assert `expect`. A case with an empty `gateways` list is called with no gateway list at all, that is the not-checked state and it must be reachable without a fetch seam.",
8
8
  "responseKinds": {
9
9
  "witnessed": "The exact decoded stamp_bytes. The honest chain.",
10
10
  "altered": "JCS of the same stamp body with interval_end changed to 2026-09-09T13:00:00.000Z — a well-formed, canonical, semantically different stamp. This is the forgery shape that matters: a pack built on it is fully self-consistent OFFLINE and lies only about what was witnessed.",
@@ -13,7 +13,7 @@
13
13
  "unreachable": "The fetch fails: a network error, a timeout, a non-ok response, or no such transaction. Never a mismatch."
14
14
  },
15
15
  "alsoAppliesTo": "sanning.stamp.trace/v1 — the same case, with body_type changed and events[] dropped. Every case's `expect` holds UNCHANGED for that body, and a grader MUST run all of them twice. §5.2 and §5.3 carry the same stamps[] array under the same §5.2.2 definition, so they must give the same on-chain answer: a stamp that can be re-fetched inside one wrapper and not the other forces every consumer to know which wrapper it is holding before it can read `onChain: null`, and that is the kind of per-body exception a reader eventually gets wrong. The one case-level difference is outside this rule: a §5.2 body with no stamps at all is `partial` for carrying nothing, which no case here exercises.",
16
- "anchorIdPrefix": "stampOnChainFixtureAnchorId0000000000-",
16
+ "anchorIdPrefix": "stampOnChainFixtureAnchorId00000000000000-",
17
17
  "packScaffold": {
18
18
  "seedHex": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
19
19
  "eventIdPrefix": "ev-",
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "wrapperScaffold": {
32
32
  "spec_version": "sanning.evidence/v1",
33
- "body_type": "sanning.stamp.evidence/v1",
33
+ "body_type": "sanning.stamp.evidence/v2",
34
34
  "issuer": { "kind": "sanning" },
35
35
  "generated_at": "2026-09-09T12:20:00.000Z",
36
36
  "gateway": null,