@sanning/proof 0.9.1 → 0.10.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/dist/grading.d.ts CHANGED
@@ -113,7 +113,7 @@ export interface PackGrade {
113
113
  export declare function accountDisclosure(bundle: unknown, content: Readonly<Record<string, Uint8Array>> | ReadonlyMap<string, Uint8Array>): DisclosureAccount;
114
114
  /** The `leaf_count` a checkpoint's SIGNED record commits to, or null if unreadable. */
115
115
  export declare function signedLeafCount(checkpoint: {
116
- record_bytes?: unknown;
116
+ event_bytes?: unknown;
117
117
  } | null | undefined): number | null;
118
118
  /**
119
119
  * Omission — the failure a per-record check structurally cannot see.
package/dist/grading.js CHANGED
@@ -104,6 +104,18 @@ function inclusionKeyOf(incl) {
104
104
  return null;
105
105
  return (hasV2 ? incl.checkpoint_ref : incl.checkpoint_tx_id);
106
106
  }
107
+ // The key a checkpoint and an inclusion are MATCHED on. A `checkpoint_ref` is
108
+ // folded to lower case, because the verifier lowercases it before comparing.
109
+ // A retired `tx_id` / `checkpoint_tx_id` is compared exactly as written, as the
110
+ // retired reader did, even when it happens to look like hex.
111
+ function checkpointMatchKey(o) {
112
+ const k = joinKeyOf(o);
113
+ return k !== null && typeof o?.checkpoint_ref === "string" ? k.toLowerCase() : k;
114
+ }
115
+ function inclusionMatchKey(incl) {
116
+ const k = inclusionKeyOf(incl);
117
+ return k !== null && typeof incl?.checkpoint_ref === "string" ? k.toLowerCase() : k;
118
+ }
107
119
  const eventsOf = (bundle) => {
108
120
  const e = bundle?.body?.events;
109
121
  return Array.isArray(e) ? e : [];
@@ -272,7 +284,7 @@ export function accountDisclosure(bundle, content) {
272
284
  }
273
285
  /** The `leaf_count` a checkpoint's SIGNED record commits to, or null if unreadable. */
274
286
  export function signedLeafCount(checkpoint) {
275
- const hex = checkpoint?.record_bytes;
287
+ const hex = checkpoint?.event_bytes;
276
288
  if (typeof hex !== "string" || hex.length === 0)
277
289
  return null;
278
290
  try {
@@ -310,7 +322,14 @@ export function completenessNotes(bundle) {
310
322
  notes.push({ checkpointRef, signed: null, presented: null, kind: "unreadable" });
311
323
  continue;
312
324
  }
313
- const presented = events.filter((e) => inclusionKeyOf(e?.inclusion) === key).length;
325
+ // Compared case-folded. The verifier recomputes `checkpoint_ref` and
326
+ // accepts it in any case (evidence-bundle.md §5.1 names lowercase hex as
327
+ // the form, and the kernel lowercases before comparing), so a pack whose
328
+ // checkpoint spells its ref in upper case still verifies. Grading it
329
+ // case-sensitively reported every record as omitted: a completeness
330
+ // accusation manufactured by letter case. The ref shown is the pack's own.
331
+ const want = checkpointMatchKey(cp);
332
+ const presented = events.filter((e) => inclusionMatchKey(e?.inclusion) === want).length;
314
333
  if (presented < signed)
315
334
  notes.push({ checkpointRef, signed, presented, kind: "omitted" });
316
335
  else if (presented > signed)
@@ -455,7 +474,7 @@ export function renderCause(cause, subject = "this pack") {
455
474
  case "leaf-count-unreadable":
456
475
  return `checkpoint ${cause.checkpointRef} has no readable signed leaf_count — completeness is UNKNOWN`;
457
476
  case "no-disclosure":
458
- return (`no record bytes were disclosed — content binding is UNKNOWN for all ${cause.count} ` +
477
+ return (`no event bytes were disclosed — content binding is UNKNOWN for all ${cause.count} ` +
459
478
  `record(s), not verified`);
460
479
  case "missing-disclosure":
461
480
  return (`${cause.count} of ${cause.of} record(s) disclose no bytes (first: ${cause.firstEventId}) — ` +
package/dist/index.d.ts CHANGED
@@ -2,8 +2,8 @@ export { ACCEPTED_SPEC_MAJORS, contentHashes, jcs, specVersionSupported, verifyE
2
2
  export { bytesToHex, deriveOperatorAddress, ed25519Verify, hexToBytes, sha256Bytes, sha256Hex, utf8, verifyRsaPssSha256, } from "./crypto.js";
3
3
  export { EMPTY_TREE_ROOT_HEX, auditPath, leafHash, merkleRoot, nodeHash, verifyInclusion, } from "./merkle.js";
4
4
  export type { ContentRole, Envelope, Subject, VerificationResult, VerifyOptions } from "./types.js";
5
- export { ANCHOR_TRACE_BODY_TYPE, ANCHOR_TRACE_BODY_TYPE_V1, ANCHOR_TRACE_BODY_TYPES, EXPORT_BODY_TYPE, STAMP_BODY_VERSION, STAMP_TRACE_BODY_TYPE, STAMP_EVIDENCE_BODY_TYPE, disclosedContent, recomputeExportVerdict, verifyEvidenceBundle, } from "./evidence.js";
6
- export type { AnchorTraceBody, AttestationPayload, AttestationRecord, AttestationResult, CheckpointResult, ComposeVerdictResult, DisclosedContentResult, DisclosedEventContent, DisclosureStatus, EvidenceBundle, EvidenceBundleResult, EvidenceIssuer, EvidenceStatus, EvidenceVerdict, EventResult, ExportBody, ExportResult, OnChainOutcome, OnChainResult, PerGatewayOutcome, AnchorRef, StampBody, StampEntry, StampResult, StampSubtree, StampSubtreeResult, StampTraceBody, StampEvidenceBody, StampEvidenceEvent, StampEvidenceInclusion, TraceCheckpoint, TraceCheckpointV1, TraceEvent, TraceInclusion, VerdictAttestation, VerdictCheckpoint, VerdictEvent, VerdictObject, VerdictOnChain, VerdictOnChainGateway, VerifyEvidenceOptions, } from "./evidence.js";
5
+ export { ANCHOR_TRACE_BODY_TYPE, EXPORT_BODY_TYPE, STAMP_BODY_VERSION, STAMP_TRACE_BODY_TYPE, STAMP_EVIDENCE_BODY_TYPE, disclosedContent, recomputeExportVerdict, verifyEvidenceBundle, } from "./evidence.js";
6
+ export type { AnchorTraceBody, AttestationPayload, AttestationRecord, AttestationResult, CheckpointResult, ComposeVerdictResult, DisclosedContentResult, DisclosedEventContent, DisclosureStatus, EvidenceBundle, EvidenceBundleResult, EvidenceIssuer, EvidenceStatus, EvidenceVerdict, EventResult, ExportBody, ExportResult, OnChainOutcome, OnChainResult, PerGatewayOutcome, AnchorRef, StampBody, StampEntry, StampResult, StampSubtree, StampSubtreeResult, StampTraceBody, StampEvidenceBody, StampEvidenceEvent, StampEvidenceInclusion, TraceCheckpoint, TraceEvent, TraceInclusion, VerdictAttestation, VerdictCheckpoint, VerdictEvent, VerdictObject, VerdictOnChain, VerdictOnChainGateway, VerifyEvidenceOptions, } from "./evidence.js";
7
7
  export { accountDisclosure, completenessNotes, gradeCauses, gradePack, renderCause, signedLeafCount, } from "./grading.js";
8
8
  export type { CompletenessNote, DisclosureAccount, GradeInput, PackGrade, PackGradeCause, PackGradeCauseKind, PackVerdict, } from "./grading.js";
9
9
  export { ADAPTER_CHAIN_NAMESPACES, CHAIN_READING_SCOPE, readChains, renderChainFinding, } from "./chain.js";
package/dist/index.js CHANGED
@@ -21,9 +21,9 @@ export { ACCEPTED_SPEC_MAJORS, contentHashes, jcs, specVersionSupported, verifyE
21
21
  export { bytesToHex, deriveOperatorAddress, ed25519Verify, hexToBytes, sha256Bytes, sha256Hex, utf8, verifyRsaPssSha256, } from "./crypto.js";
22
22
  export { EMPTY_TREE_ROOT_HEX, auditPath, leafHash, merkleRoot, nodeHash, verifyInclusion, } from "./merkle.js";
23
23
  // Evidence-bundle verification (specs/evidence-bundle.md): the signed
24
- // sanning.evidence/v1 wrapper carrying the sanning.anchor.trace/v1 body. The CLI
24
+ // sanning.evidence/v1 wrapper carrying the sanning.anchor.trace/v3 body. The CLI
25
25
  // (`npx @sanning/proof verify`) is built on this. The same path dispatches on the
26
- // issuer-composed sanning.evidence.export/v1 body (specs/evidence-export.md) to
26
+ // issuer-composed sanning.evidence.export/v2 body (specs/evidence-export.md) to
27
27
  // verifyExportBody (embedded RSA-PSS operator attestations + a recomputed §4
28
28
  // verdict object with per-gateway on-chain outcomes).
29
29
  //
@@ -34,7 +34,7 @@ export { EMPTY_TREE_ROOT_HEX, auditPath, leafHash, merkleRoot, nodeHash, verifyI
34
34
  // files that failed verification on genuine evidence. This hands back DECODED
35
35
  // bytes, and only for events whose verification bound them to the committed
36
36
  // content_hash. Additive; verifyEvidenceBundle's behavior is unchanged.
37
- export { ANCHOR_TRACE_BODY_TYPE, ANCHOR_TRACE_BODY_TYPE_V1, ANCHOR_TRACE_BODY_TYPES, EXPORT_BODY_TYPE, STAMP_BODY_VERSION, STAMP_TRACE_BODY_TYPE, STAMP_EVIDENCE_BODY_TYPE, disclosedContent, recomputeExportVerdict, verifyEvidenceBundle, } from "./evidence.js";
37
+ export { ANCHOR_TRACE_BODY_TYPE, EXPORT_BODY_TYPE, STAMP_BODY_VERSION, STAMP_TRACE_BODY_TYPE, STAMP_EVIDENCE_BODY_TYPE, disclosedContent, recomputeExportVerdict, verifyEvidenceBundle, } from "./evidence.js";
38
38
  // Pack grading (src/grading.ts): what a verification SURFACE may claim about a
39
39
  // pack — the signed bundle PLUS whatever disclosed bytes travelled beside it.
40
40
  //
@@ -51,7 +51,7 @@ export { ANCHOR_TRACE_BODY_TYPE, ANCHOR_TRACE_BODY_TYPE_V1, ANCHOR_TRACE_BODY_TY
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
- // Chain reading (src/chain.ts): following the pointer every sanning.events/v1
54
+ // Chain reading (src/chain.ts): following the pointer every sanning.events/v2
55
55
  // record carries to the record before it, over the records ONE bundle presents.
56
56
  //
57
57
  // Every anchored event has carried that pointer since the profile was ratified
@@ -67,13 +67,13 @@ export { accountDisclosure, completenessNotes, gradeCauses, gradePack, renderCau
67
67
  //
68
68
  // It returns FINDINGS, never a verdict, and it feeds no status. Chain links are
69
69
  // deletion/reorder-evidence for the events present and NOT a completeness proof
70
- // (`anchor/docs/profile-sanning.events-v1.md` §5) — drop the last N events of a
70
+ // (`anchor/docs/profile-sanning.events-v2.md` §5), drop the last N events of a
71
71
  // chain and nothing dangles. `CHAIN_READING_SCOPE` is that sentence, exported so
72
72
  // every surface prints the same limit rather than paraphrasing it into a claim.
73
73
  // Additive — no existing export changes.
74
74
  export { ADAPTER_CHAIN_NAMESPACES, CHAIN_READING_SCOPE, readChains, renderChainFinding, } from "./chain.js";
75
75
  // The `proof export` composer (specs/evidence-export.md §5, phase 2): produce a
76
- // signed, offline-verifiable sanning.evidence.export/v1 artifact from a source
76
+ // signed, offline-verifiable sanning.evidence.export/v2 artifact from a source
77
77
  // anchor-trace bundle + operator attestations + an exporter Ed25519 key. The
78
78
  // inverse of verifyEvidenceBundle's verifyExportBody dispatch.
79
79
  export { attestationSignatureToHex, composeExport } from "./compose.js";
@@ -4,6 +4,7 @@ export declare const MAX_CANONICAL_DEPTH = 128;
4
4
  export declare function exceedsDepth(value: unknown, max: number): boolean;
5
5
  export declare function jcs(value: unknown): string;
6
6
  export declare function specVersionSupported(specVersion: string): boolean;
7
+ export declare function isRfc3339UtcInstant(value: unknown): boolean;
7
8
  export declare function contentHashes(env: Envelope): {
8
9
  role: ContentRole;
9
10
  hash: string;
package/dist/verifier.js CHANGED
@@ -13,7 +13,7 @@ import { ed25519Verify, sha256Hex, utf8 } from "./crypto.js";
13
13
  // major ("sanning.agent/v1.<minor>") are additive and tolerated — matching the Go
14
14
  // reference kernel's semantics (pkg/proof isSupportedSpec) so the kernels do
15
15
  // not diverge on minors. Accepting a new profile is a deliberate one-entry
16
- // addition HERE and only here. sanning.events/v1 (external commitment + Minimal
16
+ // addition HERE and only here. sanning.events/v2 (external commitment + Minimal
17
17
  // disclosure; envelope-spec §4) is verified through the SAME primitives — the
18
18
  // signed scope is still envelope-minus-signature-minus-co_signatures, with no
19
19
  // profile-specific dialect (no underscore strip, that is mlflow-only).
@@ -35,9 +35,13 @@ import { ed25519Verify, sha256Hex, utf8 } from "./crypto.js";
35
35
  // always strict mode) and the `readonly string[]` type rejects it at compile
36
36
  // time. The elements are strings, so a shallow freeze is a deep one. Pinned by
37
37
  // test/accept-set.test.ts "the exported registry cannot be widened at runtime".
38
+ //
39
+ // `sanning.events/v2` replaced `sanning.events/v1` in the producer-to-agent cut
40
+ // (envelope-spec A10, governance.md §8 W-3). The old major is rejected like any
41
+ // other unknown one: DL-73 keeps no reader for it.
38
42
  export const ACCEPTED_SPEC_MAJORS = Object.freeze([
39
43
  "sanning.agent/v1",
40
- "sanning.events/v1",
44
+ "sanning.events/v2",
41
45
  ]);
42
46
  // Maximum JSON container-nesting depth a verifier will canonicalize. Inputs
43
47
  // nested deeper are rejected as malformed BEFORE canonicalization runs. This is
@@ -228,6 +232,57 @@ export function specVersionSupported(specVersion) {
228
232
  return minor.length > 0 && /^[0-9]+$/.test(minor);
229
233
  });
230
234
  }
235
+ // `signed_at` format (envelope-spec §2: "RFC 3339 UTC, `Z`"), proof#95. The
236
+ // accepted form is RFC 3339 `date-time` narrowed three ways, identically in
237
+ // every kernel:
238
+ // - the zone is an uppercase `Z`: never a numeric offset (not even
239
+ // `+00:00`), never a lowercase `z` or `t`;
240
+ // - the date is a real proleptic Gregorian date from 0001-01-01 to
241
+ // 9999-12-31, so February 29 only in a leap year;
242
+ // - second 60 is refused. RFC 3339 allows it at a real leap second, but
243
+ // which instants those were needs a table, and JavaScript's `Date`,
244
+ // Python's `datetime` and Go's `time` cannot hold one, so a verified `:60`
245
+ // would fail the first consumer that parses it.
246
+ // Fractional seconds of any length are allowed. Digits are ASCII `[0-9]`
247
+ // (Python's `\d` matches any Unicode digit, so the Python kernel spells it the
248
+ // same way).
249
+ const RFC3339_UTC = /^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(?:\.[0-9]+)?Z$/;
250
+ const DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
251
+ export function isRfc3339UtcInstant(value) {
252
+ if (typeof value !== "string")
253
+ return false;
254
+ const m = RFC3339_UTC.exec(value);
255
+ if (m === null)
256
+ return false;
257
+ const [year, month, day, hour, minute, second] = m.slice(1).map(Number);
258
+ if (year < 1 || month < 1 || month > 12 || day < 1)
259
+ return false;
260
+ const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
261
+ const days = month === 2 && leap ? 29 : DAYS_IN_MONTH[month - 1];
262
+ return day <= days && hour <= 23 && minute <= 59 && second <= 59;
263
+ }
264
+ // The skeleton fields whose FORMAT §2 fixes and no other check reaches: a
265
+ // correctly signed envelope with `signed_at: "2026-02-30T00:00:00Z"` or
266
+ // `environment: "prod\u0000uction"` passes the signature and the payload
267
+ // binding, so without this it verified in full. Each is checked only when
268
+ // present: §2 flags an absent `signed_at` as non-conformant rather than
269
+ // rejecting it, and `environment` is optional in the skeleton.
270
+ //
271
+ // Own properties only, because those are the members `jcs` signs over.
272
+ function skeletonFieldErrors(env) {
273
+ const fields = env;
274
+ const own = (k) => (Object.prototype.hasOwnProperty.call(fields, k) ? fields[k] : undefined);
275
+ const out = [];
276
+ const signedAt = own("signed_at");
277
+ if (signedAt !== undefined && !isRfc3339UtcInstant(signedAt)) {
278
+ out.push("malformed signed_at: not an RFC 3339 UTC date-time with Z naming a real date and time (envelope-spec §2)");
279
+ }
280
+ const environment = own("environment");
281
+ if (environment !== undefined && environment !== "dev" && environment !== "production") {
282
+ out.push('malformed environment: not "dev" or "production" (envelope-spec §2)');
283
+ }
284
+ return out;
285
+ }
231
286
  // The content hash(es) an envelope commits to, by event type — the values a
232
287
  // reverse lookup can match the user's in-browser file hash against.
233
288
  // asset_registered -> payload.hash (the registered, known-good bytes)
@@ -339,6 +394,13 @@ export async function verifyEnvelope(env, optionsOrContentHash) {
339
394
  const specVersionOk = specVersionSupported(env.spec_version);
340
395
  if (!specVersionOk)
341
396
  errors.push(`unsupported spec_version: ${JSON.stringify(env.spec_version)}`);
397
+ // Skeleton field format (envelope-spec §2, proof#95): a present `signed_at`
398
+ // or `environment` that is not what §2 says fails the verdict. There is no
399
+ // result field for it, so the verdict shape is unchanged for every envelope
400
+ // the spec allows; the reason is in `errors`.
401
+ const fieldErrors = skeletonFieldErrors(env);
402
+ errors.push(...fieldErrors);
403
+ const fieldsOk = fieldErrors.length === 0;
342
404
  // Check 1 — Payload binding (both modes; see header).
343
405
  //
344
406
  // payload_hash MUST be present and a string in every mode — envelope-spec
@@ -404,7 +466,7 @@ export async function verifyEnvelope(env, optionsOrContentHash) {
404
466
  const committed = contentHashes(env);
405
467
  if (committed.length === 0) {
406
468
  // UNDETERMINED, not failed. An external-commitment envelope
407
- // (`sanning.events/v1`) has no inline payload, so it commits to no
469
+ // (`sanning.events/v2`) has no inline payload, so it commits to no
408
470
  // envelope-readable content hash and there is nothing here to compare
409
471
  // against. Reporting that as a failure would render "we cannot check
410
472
  // this" as "it did not match" — what P-10 and I-5 exist to prevent — and
@@ -439,7 +501,7 @@ export async function verifyEnvelope(env, optionsOrContentHash) {
439
501
  //
440
502
  // Absence stays undetermined: `contentHashOk` is null when no hash was
441
503
  // supplied, and null must not fail (P-10 — absence is not failure).
442
- ok: specVersionOk && signatureOk && payloadHashOk !== false && contentHashOk !== false,
504
+ ok: specVersionOk && fieldsOk && signatureOk && payloadHashOk !== false && contentHashOk !== false,
443
505
  specVersionOk,
444
506
  payloadHashOk,
445
507
  signatureOk,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sanning/proof",
3
- "version": "0.9.1",
4
- "description": "TypeScript kernel of the Sanning verification stack: RFC 8785 (JCS) canonicalization, SHA-256, and Ed25519 verification for Verifiable Event Envelopes. Full-family verifier (inline + external commitment); accepts sanning.agent/v1, sanning.mlflow/v1, sanning.events/v1. Conformance-gated byte-for-byte against the test-vectors corpus.",
3
+ "version": "0.10.0",
4
+ "description": "TypeScript kernel of the Sanning verification stack: RFC 8785 (JCS) canonicalization, SHA-256, and Ed25519 verification for Verifiable Event Envelopes. Full-family verifier (inline + external commitment); accepts sanning.agent/v1 and sanning.events/v2. Conformance-gated byte-for-byte against the test-vectors corpus.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "sideEffects": false,
@@ -118,6 +118,9 @@ export async function verifyAgentProofBundle(
118
118
  checkpointOk = res.ok && res.payloadHashOk === true;
119
119
  if (!res.signatureOk) errors.push("checkpoint envelope signature failed");
120
120
  if (res.payloadHashOk !== true) errors.push("checkpoint envelope payload binding failed");
121
+ // A malformed skeleton field (proof#95) fails the envelope with a valid
122
+ // signature and binding; name it rather than fail with no reason.
123
+ for (const m of res.errors) if (m.startsWith("malformed ")) errors.push(`checkpoint envelope ${m}`);
121
124
  const payload = (b.checkpoint_envelope.payload ?? {}) as {
122
125
  merkle_root?: unknown;
123
126
  leaf_count?: unknown;
package/src/chain.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  // event before it, and reporting what does not line up.
3
3
  //
4
4
  // WHY THIS LIVES IN THE KERNEL, BESIDE PACK GRADING.
5
- // `sanning.events/v1` records carry `previous_hash`, and until now nothing we
6
- // shipped read it. The envelope verifier hashes each event's `record_bytes`
5
+ // `sanning.events/v2` records carry `previous_hash`, and until now nothing we
6
+ // shipped read it. The envelope verifier hashes each event's `event_bytes`
7
7
  // and stops — `evidence.ts` says following the link is "consumer-layer logic
8
8
  // above it", and it was right that a SINGLE-envelope verifier should not do
9
9
  // it. But the consumer layer never did it either, so a bundle with an event
@@ -18,7 +18,7 @@
18
18
  // ---- WHAT THIS IS NOT ------------------------------------------------------
19
19
  //
20
20
  // It is not a completeness statement and it must never be read as one. The
21
- // profile says so in its own words (`anchor/docs/profile-sanning.events-v1.md`
21
+ // profile says so in its own words (`anchor/docs/profile-sanning.events-v2.md`
22
22
  // §5): chain semantics are "deletion/reorder-evidence for the events present
23
23
  // ... not a completeness proof." Drop the last N events of a chain and nothing
24
24
  // here dangles; the record that would have pointed at them was never written.
@@ -273,7 +273,7 @@ async function groupChains(bundle: unknown): Promise<Group[]> {
273
273
  // event id an adapter pointer has nothing to resolve against.
274
274
  if (eventId === null || publicKey === null) continue;
275
275
 
276
- const decoded = decodeRecord(entry?.record_bytes);
276
+ const decoded = decodeRecord(entry?.event_bytes);
277
277
  if (decoded === null) continue;
278
278
  const { record, bytes } = decoded;
279
279
 
package/src/cli.ts CHANGED
@@ -2,8 +2,9 @@
2
2
  // npx @sanning/proof verify <bundle.json> [comma,sep,gateways]
3
3
  //
4
4
  // One turnkey verifier for both producer bundles in the stack:
5
- // - sanning.evidence/v1 (body sanning.anchor.trace/v1) — the @sanning/anchor SDK trace
6
- // - sanning.agent.proof/v1 — the agent inclusion proof
5
+ // - sanning.evidence/v1: bodies sanning.anchor.trace/v3, sanning.stamp.trace/v1,
6
+ // sanning.stamp.evidence/v2 and sanning.evidence.export/v2
7
+ // - sanning.agent.proof/v1: the agent inclusion proof
7
8
  // Dispatch is by sniffed spec_version; no flag tells the CLI which it is.
8
9
  //
9
10
  // Pinned exit codes (mirror `sanningd verify-status`):
@@ -184,8 +185,8 @@ export async function runCli(argv: string[], io: Cli): Promise<number> {
184
185
  // `proof bundle <source-bundle.json> --attestations <f> --key <f> [-o <out>]`
185
186
  // (`proof export` still reaches this, undocumented — see the dispatch above.)
186
187
  //
187
- // Compose a signed, offline-verifiable sanning.evidence.export/v1 artifact from a
188
- // source sanning.anchor.trace/v1 bundle + operator attestation records + an exporter
188
+ // Compose a signed, offline-verifiable sanning.evidence.export/v2 artifact from a
189
+ // source sanning.anchor.trace/v3 bundle + operator attestation records + an exporter
189
190
  // Ed25519 key (evidence-export.md §5, composer). Writes the export to `-o <out>`
190
191
  // (or stdout when omitted, so it can be piped straight into `proof verify`).
191
192
  //
@@ -307,8 +308,8 @@ function usageExport(io: Cli): number {
307
308
  "Usage: npx @sanning/proof bundle <source-bundle.json> --attestations <file> --key <file> [-o <out.json>]",
308
309
  );
309
310
  io.out("");
310
- io.out("Compose a signed, offline-verifiable sanning.evidence.export/v1 artifact from an");
311
- io.out("sanning.anchor.trace/v1 source bundle + operator attestation records + an exporter");
311
+ io.out("Compose a signed, offline-verifiable sanning.evidence.export/v2 artifact from an");
312
+ io.out("sanning.anchor.trace/v3 source bundle + operator attestation records + an exporter");
312
313
  io.out("Ed25519 key. The output verifies with `proof verify <out.json>`.");
313
314
  io.out("");
314
315
  io.out("--attestations <file> JSON array of attestation records (or {attestations:[...]}).");
@@ -642,10 +643,12 @@ function printUsage(io: Cli): void {
642
643
  io.out(" verify <bundle.json> [gateways] [--logs <path>] Verify a bundle");
643
644
  io.out(" bundle <source.json> --attestations <f> --key <f> [-o] Create an attested bundle");
644
645
  io.out("");
645
- io.out("Verify a sanning.evidence/v1 (sanning.anchor.trace/v1) bundle, an");
646
- io.out("sanning.evidence.export/v1 attested export, or a sanning.agent.proof/v1");
647
- io.out("inclusion bundle, fully offline. Supply a comma-separated gateway list");
648
- io.out("to also re-fetch each checkpoint on-chain.");
646
+ io.out("Verify a sanning.evidence/v1 bundle (body sanning.anchor.trace/v3,");
647
+ io.out("sanning.stamp.trace/v1, sanning.stamp.evidence/v2 or the attested export");
648
+ io.out("sanning.evidence.export/v2) or a sanning.agent.proof/v1 inclusion bundle,");
649
+ io.out("fully offline. Supply a comma-separated gateway list to also re-fetch");
650
+ io.out("each checkpoint and stamp on-chain. Only arweave witnesses are re-fetched;");
651
+ io.out("a witness of any other kind is not checked and the run exits 3.");
649
652
  io.out("");
650
653
  io.out("--logs <path> Bind disclosed raw logs to each event's committed");
651
654
  io.out(" content_hash. Either shape works, and which one you");
package/src/compose.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  // Attested-evidence-export composer (`proof export`, evidence-export.md §5,
2
2
  // phase 2). The last-mile issuer/runtime job the kernel does NOT own (§5.1
3
- // "Out of the kernel"): take a source `sanning.anchor.trace/v1` bundle + a set of
3
+ // "Out of the kernel"): take a source `sanning.anchor.trace/v3` bundle + a set of
4
4
  // operator attestation records + an exporter Ed25519 key, and PRODUCE one signed,
5
- // offline-verifiable `sanning.evidence.export/v1` artifact — exactly the bytes the
5
+ // offline-verifiable `sanning.evidence.export/v2` artifact, exactly the bytes the
6
6
  // kernel's verifyExportBody round-trip-verifies.
7
7
  //
8
8
  // This is the inverse of evidence.ts's verifyExportBody, and it is deliberately
@@ -64,7 +64,7 @@ export interface ComposeExportOptions {
64
64
  }
65
65
 
66
66
  export interface ComposeExportResult {
67
- // The signed, offline-verifiable sanning.evidence.export/v1 artifact.
67
+ // The signed, offline-verifiable sanning.evidence.export/v2 artifact.
68
68
  bundle: EvidenceBundle;
69
69
  // The export rollup the embedded kernel_verdict carries (verified|partial|failed).
70
70
  status: EvidenceStatus;
@@ -75,7 +75,7 @@ export interface ComposeExportResult {
75
75
  }
76
76
 
77
77
  const GENESIS = "GENESIS";
78
- const EXPORT_SCHEMA = "sanning.evidence.export/v1";
78
+ const EXPORT_SCHEMA = EXPORT_BODY_TYPE;
79
79
 
80
80
  // Transcode an attestation `signature` to the lowercase hex §2.2 mandates. The
81
81
  // issuer emits either lowercase-hex or unpadded base64url; §2.2 stores hex, so
package/src/crypto.ts CHANGED
@@ -85,7 +85,7 @@ export async function ed25519PublicKey(privateKeyHex: string): Promise<string> {
85
85
  }
86
86
 
87
87
  // ---------------------------------------------------------------------------
88
- // RSA-PSS operator-attestation crypto (sanning.evidence.export/v1)
88
+ // RSA-PSS operator-attestation crypto (sanning.evidence.export/v2)
89
89
  //
90
90
  // The attested-evidence-export body embeds operator attestations signed with
91
91
  // RSA-PSS-SHA-256 over the JCS-canonical attestation payload; the operator key