@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.
Files changed (98) hide show
  1. package/README.md +30 -29
  2. package/dist/agent-record.d.ts +62 -0
  3. package/dist/agent-record.js +527 -0
  4. package/dist/agent-schemas.generated.d.ts +15 -0
  5. package/dist/agent-schemas.generated.js +5759 -0
  6. package/dist/case-file.d.ts +32 -89
  7. package/dist/case-file.js +41 -93
  8. package/dist/case.d.ts +339 -822
  9. package/dist/case.js +667 -1160
  10. package/dist/chain.d.ts +19 -47
  11. package/dist/chain.js +78 -205
  12. package/dist/claims.d.ts +211 -0
  13. package/dist/claims.js +1081 -0
  14. package/dist/cli.d.ts +45 -68
  15. package/dist/cli.js +618 -1022
  16. package/dist/compose.js +23 -65
  17. package/dist/crypto.d.ts +7 -8
  18. package/dist/crypto.js +60 -147
  19. package/dist/declaration.d.ts +68 -194
  20. package/dist/declaration.js +175 -518
  21. package/dist/environment.d.ts +9 -30
  22. package/dist/environment.js +17 -66
  23. package/dist/evidence.d.ts +119 -160
  24. package/dist/evidence.js +744 -1103
  25. package/dist/exit-codes.d.ts +11 -27
  26. package/dist/exit-codes.js +24 -59
  27. package/dist/file-match.d.ts +33 -79
  28. package/dist/file-match.js +33 -69
  29. package/dist/grading.d.ts +98 -135
  30. package/dist/grading.js +224 -380
  31. package/dist/handoff.d.ts +62 -160
  32. package/dist/handoff.js +153 -393
  33. package/dist/index.d.ts +14 -10
  34. package/dist/index.js +60 -167
  35. package/dist/json-schema.d.ts +29 -0
  36. package/dist/json-schema.js +298 -0
  37. package/dist/merkle.js +16 -28
  38. package/dist/message-parts.d.ts +56 -212
  39. package/dist/message-parts.js +142 -502
  40. package/dist/side-content.d.ts +11 -22
  41. package/dist/side-content.js +22 -65
  42. package/dist/types.d.ts +17 -0
  43. package/dist/types.js +19 -5
  44. package/dist/verifier.d.ts +40 -114
  45. package/dist/verifier.js +196 -535
  46. package/dist/witness-cache.d.ts +24 -45
  47. package/dist/witness-cache.js +38 -81
  48. package/dist/witness-lookup.d.ts +36 -57
  49. package/dist/witness-lookup.js +89 -139
  50. package/dist/zip-reader.d.ts +18 -33
  51. package/dist/zip-reader.js +19 -34
  52. package/package.json +11 -3
  53. package/src/agent-record.ts +608 -0
  54. package/src/agent-schemas.generated.ts +5764 -0
  55. package/src/case-file.ts +45 -109
  56. package/src/case.ts +814 -1328
  57. package/src/chain.ts +85 -224
  58. package/src/claims.ts +1253 -0
  59. package/src/cli.ts +714 -1176
  60. package/src/compose.ts +33 -79
  61. package/src/crypto.ts +60 -147
  62. package/src/declaration.ts +219 -656
  63. package/src/environment.ts +17 -65
  64. package/src/evidence.ts +958 -1381
  65. package/src/exit-codes.ts +24 -63
  66. package/src/file-match.ts +40 -89
  67. package/src/grading.ts +287 -421
  68. package/src/handoff.ts +175 -427
  69. package/src/index.ts +131 -177
  70. package/src/json-schema.ts +295 -0
  71. package/src/merkle.ts +16 -28
  72. package/src/message-parts.ts +155 -533
  73. package/src/side-content.ts +25 -68
  74. package/src/types.ts +55 -36
  75. package/src/verifier.ts +202 -526
  76. package/src/witness-cache.ts +45 -94
  77. package/src/witness-lookup.ts +107 -177
  78. package/src/zip-reader.ts +20 -38
  79. package/vectors/README.md +52 -34
  80. package/vectors/agent-record.json +2175 -0
  81. package/vectors/case-file-index.json +1 -1
  82. package/vectors/case-split.json +2 -2
  83. package/vectors/chain-reading.json +3 -3
  84. package/vectors/file-match.json +285 -148
  85. package/vectors/jcs-reader.json +6 -6
  86. package/vectors/pack-grading.json +102 -25
  87. package/vectors/sandbox-evidence.json +573 -581
  88. package/vectors/stamp-on-chain.json +18 -18
  89. package/vectors/witness-cache.json +49 -49
  90. package/dist/agent-proof.d.ts +0 -28
  91. package/dist/agent-proof.js +0 -226
  92. package/dist/run-adapters.d.ts +0 -10
  93. package/dist/run-adapters.js +0 -35
  94. package/dist/run.d.ts +0 -209
  95. package/dist/run.js +0 -1405
  96. package/src/agent-proof.ts +0 -264
  97. package/src/run-adapters.ts +0 -44
  98. package/src/run.ts +0 -1598
@@ -1,69 +1,37 @@
1
- // ONE answer to "did the holder actually supply bytes for this event_id?"
1
+ // The one answer to "did the holder supply bytes for this event_id?", shared by
2
+ // the verifier and the grader so the two cannot disagree.
2
3
  //
3
- // WHY THIS IS A MODULE AND NOT TWO PRIVATE HELPERS.
4
- // The kernel asks that question in two places — the verifier, deciding whether
5
- // an event's content was offered at all (`evidence.ts`), and the grader,
6
- // accounting which records a pack disclosed (`grading.ts`). It had two answers.
7
- // On one and the same content map the grader said PRESENT while the verifier
8
- // said nothing was offered, so `gradePack` returned `incomplete` with ZERO
9
- // causes: a surface saying "incomplete" and unable to say why. That is exactly
10
- // the outcome `grading.ts`'s own comment claims was removed, reappearing
11
- // because the fix landed on one of the two definitions.
4
+ // An entry exists when the map carries an own, enumerable, data property (or
5
+ // `Map` key) holding something other than `null`/`undefined`:
12
6
  //
13
- // Two implementations of one verification rule is the defect `grading.ts` was
14
- // extracted into the kernel to end. A rule stated once cannot drift; a rule
15
- // stated twice already has.
7
+ // own `event_id` is producer-chosen, and `constructor` or `__proto__`
8
+ // must not resolve through the prototype chain.
9
+ // enumerable what `Object.keys` shows is the map the caller built.
10
+ // data an accessor is never invoked: a getter can throw, and a
11
+ // verifier does not raise on adversarial input.
12
+ // non-nullish `undefined` is how `Object.fromEntries` spells "no entry".
13
+ // Anything else, even unusable, is a disclosure and a finding.
16
14
  //
17
- // ── THE RULE ────────────────────────────────────────────────────────────────
18
- //
19
- // An entry exists for `eventId` when the map carries an OWN, ENUMERABLE, DATA
20
- // property (or `Map` key) holding something other than `null`/`undefined`.
21
- // This is the STRICTER of the two definitions that were in the kernel — the
22
- // verifier's — and it is stricter on purpose. Every clause was paid for:
23
- //
24
- // own — an `event_id` is PRODUCER-CHOSEN, and every key on
25
- // `Object.prototype` is a legal one: `constructor`,
26
- // `toString`, `valueOf`, `hasOwnProperty`, `isPrototypeOf`,
27
- // `propertyIsEnumerable`, `toLocaleString`, `__proto__`. An
28
- // indexed lookup walks the prototype chain and resolves every
29
- // one of them, so absent evidence read as good evidence.
30
- // enumerable — what a caller means by "the map I built" is what
31
- // `Object.keys` shows; nothing hidden behind it counts.
32
- // data — an accessor is NEVER invoked. A getter can throw, and this
33
- // kernel's standing rule is that a verifier does not raise on
34
- // adversarial input. Not hypothetical: a plain-object map that
35
- // has taken a `__proto__` write carries `Uint8Array.prototype`
36
- // as its prototype, and reading `byteLength`, `length`,
37
- // `buffer` or `byteOffset` off a plain object through it throws
38
- // a TypeError. Reading a descriptor cannot.
39
- // non-nullish — `{ [id]: undefined }` is how `Object.fromEntries` and friends
40
- // spell "no entry", and `new Map([[id, undefined]])` is the
41
- // same statement in the other shape. Anything else non-nullish
42
- // — a number, an object, a string that is not hex — IS a real,
43
- // unusable disclosure and stays a finding.
44
- //
45
- // `offered` is what separates "nothing was disclosed" (undetermined) from
46
- // "something was disclosed and is unusable" (a finding), and absent disclosure
47
- // is undetermined, not failed (evidence-bundle.md §5.1). So it is returned
48
- // SEPARATELY from the value and is never inferred from it: `value !== undefined`
49
- // is the conflation this module exists to remove, in both directions.
15
+ // `offered` is returned separately from the value and never inferred from it:
16
+ // nothing disclosed is undetermined, something unusable is a finding
17
+ // (evidence-bundle.md §5.1).
50
18
 
51
19
  /** A caller-built map of disclosed bytes, in either shape it can take. */
52
20
  export type SideContentMap =
53
21
  | Readonly<Record<string, Uint8Array | string | undefined>>
54
22
  | ReadonlyMap<string, Uint8Array | string | undefined>;
55
23
 
56
- /** One event's entry, and — separately — whether an entry existed at all. */
24
+ /** One event's entry, and separately whether an entry existed at all. */
57
25
  export interface SideEntry {
58
- /** An entry EXISTS for this id. Never inferred from the value. */
26
+ /** Whether an entry exists for this id. Never inferred from the value. */
59
27
  offered: boolean;
60
- /** What it held, read without ever invoking an accessor. */
28
+ /** What it held, read without invoking an accessor. */
61
29
  value: Uint8Array | string | undefined;
62
30
  }
63
31
 
64
32
  const ABSENT: SideEntry = { offered: false, value: undefined };
65
33
 
66
- /** Look up one event's out-of-band disclosure. The rule, stated once. */
34
+ /** Look up one event's out-of-band disclosure. */
67
35
  export function sideContentEntry(
68
36
  content: SideContentMap | null | undefined,
69
37
  eventId: string,
@@ -79,7 +47,7 @@ export function sideContentEntry(
79
47
  return value === undefined || value === null ? ABSENT : { offered: true, value };
80
48
  }
81
49
 
82
- /** Whether the map carries a usable entry for this id. The same rule. */
50
+ /** Whether the map carries an entry for this id. */
83
51
  export function hasSideContent(
84
52
  content: SideContentMap | null | undefined,
85
53
  eventId: string,
@@ -88,8 +56,8 @@ export function hasSideContent(
88
56
  }
89
57
 
90
58
  /**
91
- * The ids the map actually carries — the same rule applied to every key, so a
92
- * key that is not an entry cannot be reported as surplus disclosure either.
59
+ * The ids the map carries, by the same rule, so a key that is not an entry is
60
+ * never reported as surplus disclosure.
93
61
  */
94
62
  export function sideContentKeys(content: SideContentMap | null | undefined): string[] {
95
63
  if (content === undefined || content === null) return [];
@@ -98,21 +66,10 @@ export function sideContentKeys(content: SideContentMap | null | undefined): str
98
66
  }
99
67
 
100
68
  /**
101
- * Whether an event's IN-BODY `events[].content` field is a disclosure.
102
- *
103
- * The other half of the same question, and it broke the same way. `content`
104
- * is an OPTIONAL field, so `null` is how a producer's serializer spells its
105
- * absence — Go's `json.Marshal` on a nil pointer without `omitempty`, Python's
106
- * `json.dumps(None)`. A bare `!== undefined` marks such an event OFFERED while
107
- * no bytes are selected (only a `string` decodes), and the kernel then splits
108
- * against itself: `verifyEvidenceBundle` reports `contentOk: null` and rolls up
109
- * VERIFIED, while `disclosedContent` reports FAILED — "content was disclosed
110
- * for this event but is not a decodable lowercase-hex string". One kernel, one
111
- * bundle, two verdicts, reachable from any conforming producer.
112
- *
113
- * Non-nullish and not a string — a number, an object, a hex string that is not
114
- * hex — remains a real, unusable disclosure and stays a finding, exactly as on
115
- * the side-input path.
69
+ * Whether an event's in-body `events[].content` is a disclosure: anything
70
+ * non-nullish. `null` is how serializers spell an absent optional field, and
71
+ * treating it as offered would split the bundle verdict from
72
+ * `disclosedContent`.
116
73
  */
117
74
  export function inBodyContentOffered(content: unknown): boolean {
118
75
  return content !== undefined && content !== null;
package/src/types.ts CHANGED
@@ -1,7 +1,6 @@
1
- // Wire types for Sanning Verifiable Event Envelopes (sanning-io/proof
2
- // specs/envelope-spec.md). We model only the fields the verifier consumes;
3
- // unknown fields are preserved by structural typing (envelopes are verified
4
- // over their canonical bytes, not this interface).
1
+ // Wire types for Sanning Verifiable Event Envelopes (specs/envelope-spec.md).
2
+ // Only the fields the verifier consumes are modelled; envelopes are verified
3
+ // over their canonical bytes, not this interface.
5
4
 
6
5
  export interface Subject {
7
6
  type: string;
@@ -16,62 +15,82 @@ export interface Envelope {
16
15
  signed_at: string;
17
16
  public_key: string;
18
17
  signature: string;
19
- // Inline-binding profiles (envelope-spec §3) carry the committed payload in
20
- // the envelope; external-commitment profiles do not — the caller supplies
21
- // the committed bytes to bind (verifyEnvelope's payloadBytes option).
18
+ // Inline-binding profiles (envelope-spec §3) carry the committed payload;
19
+ // external-commitment profiles do not, and the caller supplies the bytes.
22
20
  payload?: Record<string, unknown>;
23
- // Disclosure fields (§3.1): present on Promoted profiles, absent on Minimal
24
- // ones (they live inside the committed payload instead).
21
+ // Disclosure fields (§3.1): on Promoted profiles, inside the payload on Minimal ones.
25
22
  event_type?: string;
26
23
  subject?: Subject;
27
24
  previous_hash?: string;
28
- // Optional v1.1+ fields: dev/production marking inside the signed scope,
29
- // and the external-commitment locator.
25
+ // Optional: dev/production marking inside the signed scope, and the external-commitment locator.
30
26
  environment?: string;
31
27
  payload_ref?: string;
32
- // Reserved, default-absent (envelope-spec §7.1): countersignatures over the
33
- // same skeleton. OUTSIDE the signed scope — the primary signature covers the
34
- // envelope minus `signature` AND minus `co_signatures`, so a countersignature
35
- // can be added without invalidating the primary. Absence implies a single
36
- // signer and MUST NOT be treated as a failure.
28
+ // Reserved (envelope-spec §7.1): countersignatures, outside the signed scope.
29
+ // The primary signature covers the envelope minus `signature` and
30
+ // `co_signatures`. Absence implies a single signer and MUST NOT be a failure.
37
31
  co_signatures?: unknown[];
38
32
  }
39
33
 
40
34
  // Which payload field a matched content hash came from. tamper_detected commits
41
- // to both the tampered ("observed") bytes and the known-good ("baseline") bytes.
35
+ // to both the tampered ("observed") and known-good ("baseline") bytes.
42
36
  export type ContentRole = "asset" | "baseline" | "observed";
43
37
 
44
38
  export interface VerifyOptions {
45
- // External commitment (§3): the committed canonical payload bytes. When
46
- // supplied, SHA-256(payloadBytes) must equal the envelope's payload_hash.
39
+ // External commitment (§3): the committed canonical payload bytes. SHA-256 of them must equal `payload_hash`.
47
40
  payloadBytes?: Uint8Array;
48
- // Content bind: the caller's own hash of an artifact (e.g. the in-browser
49
- // hash of a user's file) to match against the hashes the envelope commits to.
41
+ // Content bind: the caller's own hash of an artifact, matched against the hashes the envelope commits to.
50
42
  expectedContentHash?: string;
51
- // NARROWS the accept-set for this call and never widens it: the set used is
52
- // `ACCEPTED_SPEC_MAJORS` intersected with this list, so a caller cannot
53
- // admit a profile this kernel does not. The Python twin is
54
- // `accepted_spec_majors` (P-10, 0.13.0).
43
+ // Narrows the accept-set for this call, never widens it: `ACCEPTED_SPEC_MAJORS`
44
+ // intersected with this list.
55
45
  acceptedSpecMajors?: readonly string[];
56
46
  }
57
47
 
58
48
  export interface VerificationResult {
59
- // Cryptographic validity: spec_version ok, Ed25519 signature ok, and the
60
- // payload binding did not FAIL. payloadHashOk === null (binding not
61
- // checkable: external-commitment envelope, no payloadBytes supplied) does
62
- // not fail ok — that is the "signature-valid, semantics-undetermined"
63
- // outcome of envelope-spec §3.1/§6.2.
49
+ // Spec_version ok, Ed25519 signature ok, and the payload binding did not fail.
50
+ // `payloadHashOk === null` (no binding material) does not fail `ok`: the
51
+ // "signature-valid, semantics-undetermined" outcome of envelope-spec §3.1, §6 item 2.
64
52
  ok: boolean;
65
53
  specVersionOk: boolean;
66
- // true: binding checked and matches (inline recompute, external bytes, or
67
- // both when both were available). false: a check failed, or payload_hash is
68
- // missing/malformed. null: no binding material to check against.
54
+ // true: binding checked and matches. false: a check failed, or payload_hash is
55
+ // missing or malformed. null: no binding material to check against.
69
56
  payloadHashOk: boolean | null;
70
57
  signatureOk: boolean;
71
- // Content bind: did the hash the caller supplied match a hash this envelope
72
- // commits to? null when no hash was supplied. This is the check that defeats
73
- // a lying gateway — the tag only got us to the candidate.
58
+ // Content bind: whether the supplied hash matches a hash the envelope commits
59
+ // to; null when none was supplied. This defeats a lying gateway: the tag only
60
+ // gets to the candidate.
74
61
  contentHashOk: boolean | null;
75
62
  contentRole: ContentRole | null;
76
63
  errors: string[];
64
+ /**
65
+ * Why `ok` is false: the worst of `faults`, or `null` where `ok` is true or only the
66
+ * content bind failed. A caller maps each fault to a claim (`ENVELOPE_FAULT_KINDS`) rather
67
+ * than reading `ok` alone: a signature or a binding that fails disproves, a key or an
68
+ * envelope that cannot be read and a version this verifier does not know leave a claim not
69
+ * proven, and a field that breaks the format under a signature that holds is the
70
+ * producer's error.
71
+ */
72
+ fault: EnvelopeFault | null;
73
+ /**
74
+ * Every fault found, in `ENVELOPE_FAULTS` order. A fault that leaves one claim not proven
75
+ * never hides a disproof found beside it.
76
+ */
77
+ faults: EnvelopeFault[];
77
78
  }
79
+
80
+ /** Why an envelope does not verify, worst first: the disproofs, then what is not proven. */
81
+ export const ENVELOPE_FAULTS = [
82
+ // The Ed25519 signature does not hold.
83
+ "signature_failed",
84
+ // `payload_hash` is 64 hex characters that do not match the bytes it commits to.
85
+ "payload_unbound",
86
+ // The signature holds over a version this verifier knows, and a field breaks the
87
+ // envelope format (a missing `payload_hash` included).
88
+ "nonconforming",
89
+ // Not an object, or an object with none of the envelope's own members.
90
+ "not_an_envelope",
91
+ // `public_key` is not 64 hex characters: no signature can be checked.
92
+ "key_unreadable",
93
+ // A `spec_version` this verifier does not know.
94
+ "spec_version_unknown",
95
+ ] as const;
96
+ export type EnvelopeFault = (typeof ENVELOPE_FAULTS)[number];