@fluxpointstudios/orynq-sdk-process-trace 0.1.0 → 0.3.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/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { canonicalize, sha256StringHex, bytesToHex, hexToBytes } from '@fluxpointstudios/orynq-sdk-core/utils';
1
+ import { canonicalize, sha256StringHex, sha256Hex, bytesToHex, hexToBytes } from '@fluxpointstudios/orynq-sdk-core/utils';
2
2
 
3
3
  // src/types.ts
4
4
  var DEFAULT_EVENT_VISIBILITY = {
@@ -7,7 +7,11 @@ var DEFAULT_EVENT_VISIBILITY = {
7
7
  decision: "private",
8
8
  observation: "public",
9
9
  error: "private",
10
- custom: "private"
10
+ custom: "private",
11
+ // Governance provenance is meant to be auditable by third parties.
12
+ "governance-attestation": "public",
13
+ // Tool responses may carry PII/secrets; only hashes + signature are required.
14
+ "tool-receipt": "private"
11
15
  };
12
16
  var HASH_DOMAIN_PREFIXES = {
13
17
  event: "poi-trace:event:v1|",
@@ -16,7 +20,11 @@ var HASH_DOMAIN_PREFIXES = {
16
20
  leaf: "poi-trace:leaf:v1|",
17
21
  node: "poi-trace:node:v1|",
18
22
  manifest: "poi-trace:manifest:v1|",
19
- root: "poi-trace:root:v1|"
23
+ root: "poi-trace:root:v1|",
24
+ /** Model/data manifest commitment (pre-execution pinning). */
25
+ modelManifest: "poi-trace:model-manifest:v1|",
26
+ /** Governance-attestation signing preimage. */
27
+ governance: "poi-trace:governance:v1|"
20
28
  };
21
29
  var GENESIS_SEED = "genesis";
22
30
  async function computeEventHash(event) {
@@ -68,7 +76,7 @@ function constantTimeCompare(a, b) {
68
76
  }
69
77
  return result === 0;
70
78
  }
71
- async function computeRootHash(rollingHash, spans) {
79
+ async function computeRootHash(rollingHash, spans, modelManifestHash) {
72
80
  const sortedSpans = [...spans].sort((a, b) => a.spanSeq - b.spanSeq);
73
81
  const spanHashes = sortedSpans.map((span) => {
74
82
  if (!span.hash) {
@@ -80,6 +88,9 @@ async function computeRootHash(rollingHash, spans) {
80
88
  if (spanHashes.length > 0) {
81
89
  input += "|" + spanHashes.join("|");
82
90
  }
91
+ if (modelManifestHash !== void 0 && modelManifestHash.length > 0) {
92
+ input += "|manifest:" + modelManifestHash;
93
+ }
83
94
  return sha256StringHex(input);
84
95
  }
85
96
  async function computeEventHashes(events) {
@@ -281,6 +292,108 @@ async function verifySpanInclusion(proof, span, events) {
281
292
  }
282
293
  return verifyMerkleProof(proof);
283
294
  }
295
+ async function computeModelManifestHash(manifest) {
296
+ const canonical = canonicalize(manifest);
297
+ return sha256StringHex(HASH_DOMAIN_PREFIXES.modelManifest + canonical);
298
+ }
299
+ function validateModelManifest(manifest) {
300
+ if (typeof manifest !== "object" || manifest === null) {
301
+ throw new Error("ModelManifest must be an object");
302
+ }
303
+ if (typeof manifest.modelHash !== "string" || manifest.modelHash.length === 0) {
304
+ throw new Error("ModelManifest.modelHash is required and must be a non-empty string");
305
+ }
306
+ return manifest;
307
+ }
308
+ function freezeModelManifest(manifest) {
309
+ if (manifest.metadata) {
310
+ Object.freeze(manifest.metadata);
311
+ }
312
+ return Object.freeze(manifest);
313
+ }
314
+ async function fingerprint(parts) {
315
+ const joined = parts.filter((p) => typeof p === "string" && p.length > 0).join("\0");
316
+ return "sha256:" + await sha256StringHex("orynq:model-fingerprint:v1|" + joined);
317
+ }
318
+ async function resolveSystemPromptHash(systemPrompt, systemPromptHash) {
319
+ if (systemPromptHash !== void 0) return systemPromptHash;
320
+ if (systemPrompt !== void 0) {
321
+ return "sha256:" + await sha256StringHex("orynq:system-prompt:v1|" + systemPrompt);
322
+ }
323
+ return void 0;
324
+ }
325
+ function assemble(base) {
326
+ const m = { modelHash: base.modelHash, framework: base.framework };
327
+ if (base.modelId !== void 0) m.modelId = base.modelId;
328
+ if (base.revision !== void 0) m.revision = base.revision;
329
+ if (base.tokenizerHash !== void 0) m.tokenizerHash = base.tokenizerHash;
330
+ if (base.systemPromptHash !== void 0) m.systemPromptHash = base.systemPromptHash;
331
+ if (base.trainingDataManifest !== void 0) m.trainingDataManifest = base.trainingDataManifest;
332
+ if (base.metadata !== void 0) m.metadata = base.metadata;
333
+ return m;
334
+ }
335
+ async function manifestFromHuggingFace(opts) {
336
+ if (!opts.modelId) throw new Error("manifestFromHuggingFace: modelId is required");
337
+ const modelHash = await fingerprint(["huggingface", opts.modelId, opts.revision]);
338
+ const systemPromptHash = await resolveSystemPromptHash(opts.systemPrompt, opts.systemPromptHash);
339
+ return assemble({
340
+ modelHash,
341
+ framework: "huggingface",
342
+ modelId: opts.modelId,
343
+ revision: opts.revision,
344
+ tokenizerHash: opts.tokenizerHash,
345
+ systemPromptHash,
346
+ trainingDataManifest: opts.trainingDataManifest,
347
+ metadata: opts.metadata
348
+ });
349
+ }
350
+ async function manifestFromOpenAI(opts) {
351
+ if (!opts.model) throw new Error("manifestFromOpenAI: model is required");
352
+ const modelHash = await fingerprint(["openai", opts.model, opts.snapshotId]);
353
+ const systemPromptHash = await resolveSystemPromptHash(opts.systemPrompt, opts.systemPromptHash);
354
+ return assemble({
355
+ modelHash,
356
+ framework: "openai",
357
+ modelId: opts.model,
358
+ revision: opts.snapshotId,
359
+ tokenizerHash: opts.tokenizerHash,
360
+ systemPromptHash,
361
+ trainingDataManifest: opts.trainingDataManifest,
362
+ metadata: opts.metadata
363
+ });
364
+ }
365
+ async function manifestFromAnthropic(opts) {
366
+ if (!opts.model) throw new Error("manifestFromAnthropic: model is required");
367
+ const modelHash = await fingerprint(["anthropic", opts.model, opts.snapshotId]);
368
+ const systemPromptHash = await resolveSystemPromptHash(opts.systemPrompt, opts.systemPromptHash);
369
+ return assemble({
370
+ modelHash,
371
+ framework: "anthropic",
372
+ modelId: opts.model,
373
+ revision: opts.snapshotId,
374
+ tokenizerHash: opts.tokenizerHash,
375
+ systemPromptHash,
376
+ trainingDataManifest: opts.trainingDataManifest,
377
+ metadata: opts.metadata
378
+ });
379
+ }
380
+ async function manifestFromCheckpoint(filepath, opts = {}) {
381
+ if (!filepath) throw new Error("manifestFromCheckpoint: filepath is required");
382
+ const { readFile } = await import('fs/promises');
383
+ const bytes = await readFile(filepath);
384
+ const modelHash = "sha256:" + await sha256Hex(new Uint8Array(bytes));
385
+ const systemPromptHash = await resolveSystemPromptHash(opts.systemPrompt, opts.systemPromptHash);
386
+ return assemble({
387
+ modelHash,
388
+ framework: "checkpoint",
389
+ modelId: opts.modelId ?? filepath,
390
+ revision: opts.revision,
391
+ tokenizerHash: opts.tokenizerHash,
392
+ systemPromptHash,
393
+ trainingDataManifest: opts.trainingDataManifest,
394
+ metadata: opts.metadata
395
+ });
396
+ }
284
397
 
285
398
  // src/trace-builder.ts
286
399
  async function createTrace(opts) {
@@ -310,6 +423,19 @@ async function createTrace(opts) {
310
423
  description: opts.description
311
424
  };
312
425
  }
426
+ const strict = opts.strict ?? false;
427
+ if (strict) {
428
+ run.strict = true;
429
+ }
430
+ if (opts.manifest !== void 0) {
431
+ const manifest = validateModelManifest(opts.manifest);
432
+ run.modelManifestHash = await computeModelManifestHash(manifest);
433
+ run.modelManifest = freezeModelManifest(manifest);
434
+ } else if (strict) {
435
+ throw new Error(
436
+ "createTrace: strict mode requires a `manifest` to be pinned before execution"
437
+ );
438
+ }
313
439
  return run;
314
440
  }
315
441
  function addSpan(run, opts) {
@@ -430,6 +556,16 @@ async function finalizeTrace(run) {
430
556
  if (isFinalized(run)) {
431
557
  throw new Error("Trace run is already finalized");
432
558
  }
559
+ if (run.modelManifest === void 0) {
560
+ if (run.strict) {
561
+ throw new Error(
562
+ "finalizeTrace: strict mode requires a model manifest pinned at createTrace() time"
563
+ );
564
+ }
565
+ console.warn(
566
+ "[orynq] finalizeTrace: no model manifest was pinned \u2014 model/data immutability is NOT proven for this trace. Pass `manifest` to createTrace() (and `strict: true` to enforce). This will become an error in v1.0."
567
+ );
568
+ }
433
569
  for (const span of run.spans) {
434
570
  if (span.status === "running") {
435
571
  await closeSpan(run, span.id, "completed");
@@ -442,7 +578,11 @@ async function finalizeTrace(run) {
442
578
  const endTime = new Date(endedAt).getTime();
443
579
  run.durationMs = endTime - startTime;
444
580
  const merkleTree = await buildSpanMerkleTree(run.spans, run.events);
445
- const rootHash = await computeRootHash(run.rollingHash, run.spans);
581
+ const rootHash = await computeRootHash(
582
+ run.rollingHash,
583
+ run.spans,
584
+ run.modelManifestHash
585
+ );
446
586
  run.rootHash = rootHash;
447
587
  const publicView = createPublicView(run, merkleTree.rootHash);
448
588
  const bundle = {
@@ -452,6 +592,12 @@ async function finalizeTrace(run) {
452
592
  merkleRoot: merkleTree.rootHash,
453
593
  rootHash
454
594
  };
595
+ if (run.modelManifestHash !== void 0) {
596
+ bundle.modelManifestHash = run.modelManifestHash;
597
+ }
598
+ if (run.modelManifest !== void 0) {
599
+ bundle.modelManifest = run.modelManifest;
600
+ }
455
601
  return bundle;
456
602
  }
457
603
  function createPublicView(run, merkleRoot) {
@@ -493,6 +639,12 @@ function createPublicView(run, merkleRoot) {
493
639
  publicSpans,
494
640
  redactedSpanHashes
495
641
  };
642
+ if (run.modelManifestHash !== void 0) {
643
+ publicView.modelManifestHash = run.modelManifestHash;
644
+ }
645
+ if (run.modelManifest !== void 0) {
646
+ publicView.modelManifest = run.modelManifest;
647
+ }
496
648
  return publicView;
497
649
  }
498
650
  function getEventCount(run) {
@@ -515,6 +667,302 @@ function getEventsByKind(run, kind) {
515
667
  (e) => e.kind === kind
516
668
  );
517
669
  }
670
+
671
+ // src/governance.ts
672
+ function governanceAttestationPreimage(fields) {
673
+ const enc = new TextEncoder();
674
+ const domain = enc.encode(HASH_DOMAIN_PREFIXES.governance);
675
+ const parts = [
676
+ fields.runId,
677
+ fields.role,
678
+ fields.policyRef,
679
+ fields.decisionRef,
680
+ fields.signedAt
681
+ ].map((v) => enc.encode(v));
682
+ const total = domain.length + parts.reduce((n, p) => n + 4 + p.length, 0);
683
+ const out = new Uint8Array(total);
684
+ let off = 0;
685
+ out.set(domain, off);
686
+ off += domain.length;
687
+ for (const p of parts) {
688
+ out[off] = p.length >>> 24 & 255;
689
+ out[off + 1] = p.length >>> 16 & 255;
690
+ out[off + 2] = p.length >>> 8 & 255;
691
+ out[off + 3] = p.length & 255;
692
+ off += 4;
693
+ out.set(p, off);
694
+ off += p.length;
695
+ }
696
+ return out;
697
+ }
698
+ async function addGovernanceAttestation(run, spanId, opts) {
699
+ if (!opts.role) throw new Error("addGovernanceAttestation: role is required");
700
+ if (!opts.policyRef) throw new Error("addGovernanceAttestation: policyRef is required");
701
+ if (!opts.decisionRef) throw new Error("addGovernanceAttestation: decisionRef is required");
702
+ if (!opts.signer) throw new Error("addGovernanceAttestation: signer is required");
703
+ if (opts.signer.signatureScheme === "eip712" && opts.eip712 === void 0) {
704
+ throw new Error(
705
+ "addGovernanceAttestation: an `eip712` binding is required for eip712 signers"
706
+ );
707
+ }
708
+ const signedAt = opts.signedAt ?? (/* @__PURE__ */ new Date()).toISOString();
709
+ const fields = {
710
+ role: opts.role,
711
+ policyRef: opts.policyRef,
712
+ decisionRef: opts.decisionRef,
713
+ signedAt,
714
+ runId: run.id
715
+ };
716
+ const preimage = governanceAttestationPreimage(fields);
717
+ const ctx = opts.eip712 ? { preimage, fields, eip712: opts.eip712 } : { preimage, fields };
718
+ const signature = await opts.signer.sign(ctx);
719
+ const event = {
720
+ kind: "governance-attestation",
721
+ visibility: opts.visibility ?? "public",
722
+ role: opts.role,
723
+ policyRef: opts.policyRef,
724
+ decisionRef: opts.decisionRef,
725
+ attestor: {
726
+ address: opts.signer.address,
727
+ signatureScheme: opts.signer.signatureScheme
728
+ },
729
+ signature,
730
+ signedAt,
731
+ ...opts.eip712 ? { eip712: opts.eip712 } : {}
732
+ };
733
+ const recorded = await addEvent(run, spanId, event);
734
+ return recorded;
735
+ }
736
+ var polkadotPromise = null;
737
+ async function loadPolkadot() {
738
+ if (!polkadotPromise) {
739
+ polkadotPromise = (async () => {
740
+ let crypto2;
741
+ let util;
742
+ try {
743
+ crypto2 = await import('@polkadot/util-crypto');
744
+ util = await import('@polkadot/util');
745
+ } catch {
746
+ throw new Error(
747
+ "Built-in sr25519/ed25519 governance support requires the optional peer dependencies '@polkadot/util-crypto' and '@polkadot/util'. Install them, or pass a custom GovernanceSigner / verifier."
748
+ );
749
+ }
750
+ await crypto2.cryptoWaitReady();
751
+ return { crypto: crypto2, util };
752
+ })();
753
+ }
754
+ return polkadotPromise;
755
+ }
756
+ var SS58_PREFIX = 42;
757
+ function resolveSeed(seed, util) {
758
+ if (typeof seed === "string") {
759
+ return util.hexToU8a(seed.startsWith("0x") ? seed : "0x" + seed);
760
+ }
761
+ return seed;
762
+ }
763
+ async function createSr25519GovernanceSigner(opts) {
764
+ const { crypto: crypto2, util } = await loadPolkadot();
765
+ let publicKey;
766
+ let secretKey;
767
+ if (opts.secretKey && opts.publicKey) {
768
+ publicKey = opts.publicKey;
769
+ secretKey = opts.secretKey;
770
+ } else if (opts.seed !== void 0) {
771
+ const pair = crypto2.sr25519PairFromSeed(resolveSeed(opts.seed, util));
772
+ publicKey = pair.publicKey;
773
+ secretKey = pair.secretKey;
774
+ } else {
775
+ throw new Error("createSr25519GovernanceSigner: provide `seed` or `secretKey`+`publicKey`");
776
+ }
777
+ const address = opts.address ?? crypto2.encodeAddress(publicKey, opts.ss58Format ?? SS58_PREFIX);
778
+ return {
779
+ address,
780
+ signatureScheme: "sr25519",
781
+ sign(ctx) {
782
+ return util.u8aToHex(crypto2.sr25519Sign(ctx.preimage, { publicKey, secretKey }));
783
+ }
784
+ };
785
+ }
786
+ async function createEd25519GovernanceSigner(opts) {
787
+ const { crypto: crypto2, util } = await loadPolkadot();
788
+ let publicKey;
789
+ let secretKey;
790
+ if (opts.secretKey && opts.publicKey) {
791
+ publicKey = opts.publicKey;
792
+ secretKey = opts.secretKey;
793
+ } else if (opts.seed !== void 0) {
794
+ const pair = crypto2.ed25519PairFromSeed(resolveSeed(opts.seed, util));
795
+ publicKey = pair.publicKey;
796
+ secretKey = pair.secretKey;
797
+ } else {
798
+ throw new Error("createEd25519GovernanceSigner: provide `seed` or `secretKey`+`publicKey`");
799
+ }
800
+ const address = opts.address ?? crypto2.encodeAddress(publicKey, opts.ss58Format ?? SS58_PREFIX);
801
+ return {
802
+ address,
803
+ signatureScheme: "ed25519",
804
+ sign(ctx) {
805
+ return util.u8aToHex(crypto2.ed25519Sign(ctx.preimage, { publicKey, secretKey }));
806
+ }
807
+ };
808
+ }
809
+ async function verifyGovernanceAttestations(bundle, opts = {}) {
810
+ const events = bundle.privateRun.events.filter(
811
+ (e) => e.kind === "governance-attestation"
812
+ );
813
+ const runId = bundle.privateRun.id;
814
+ const summaries = [];
815
+ for (const event of events) {
816
+ const scheme = event.attestor.signatureScheme;
817
+ const preimage = governanceAttestationPreimage({
818
+ role: event.role,
819
+ policyRef: event.policyRef,
820
+ decisionRef: event.decisionRef,
821
+ signedAt: event.signedAt,
822
+ runId
823
+ });
824
+ const base = {
825
+ eventId: event.id,
826
+ role: event.role,
827
+ attestor: event.attestor.address,
828
+ scheme,
829
+ policyRef: event.policyRef,
830
+ decisionRef: event.decisionRef
831
+ };
832
+ const authorized = attestorAuthorized(event.attestor.address, event.role, opts);
833
+ if (!authorized) {
834
+ summaries.push({
835
+ ...base,
836
+ authorized: false,
837
+ verified: false,
838
+ error: opts.authorizedAttestors === void 0 && opts.authorizedAttestorsByRole === void 0 ? "no authorized-attestor allow-list supplied \u2014 governance verification fails closed (pass authorizedAttestors)" : `attestor ${event.attestor.address} is not authorized for role "${event.role}"`
839
+ });
840
+ continue;
841
+ }
842
+ try {
843
+ const override = opts.verifiers?.[scheme];
844
+ let signatureValid;
845
+ if (override) {
846
+ signatureValid = await override(event, { preimage, runId });
847
+ } else if (scheme === "sr25519" || scheme === "ed25519") {
848
+ signatureValid = await verifySubstrateSignature(scheme, event, preimage);
849
+ } else {
850
+ summaries.push({
851
+ ...base,
852
+ authorized: true,
853
+ verified: false,
854
+ error: `no verifier registered for scheme "${scheme}" (pass one via verifiers)`
855
+ });
856
+ continue;
857
+ }
858
+ summaries.push({ ...base, authorized: true, verified: signatureValid });
859
+ } catch (error) {
860
+ summaries.push({
861
+ ...base,
862
+ authorized: true,
863
+ verified: false,
864
+ error: error instanceof Error ? error.message : String(error)
865
+ });
866
+ }
867
+ }
868
+ return summaries;
869
+ }
870
+ function attestorAuthorized(address, role, opts) {
871
+ const norm = (s) => s.toLowerCase();
872
+ const roleList = opts.authorizedAttestorsByRole?.[role];
873
+ if (roleList !== void 0) {
874
+ return roleList.map(norm).includes(norm(address));
875
+ }
876
+ if (opts.authorizedAttestors !== void 0) {
877
+ return opts.authorizedAttestors.map(norm).includes(norm(address));
878
+ }
879
+ return false;
880
+ }
881
+ async function verifySubstrateSignature(scheme, event, preimage) {
882
+ const { crypto: crypto2, util } = await loadPolkadot();
883
+ const publicKey = crypto2.decodeAddress(event.attestor.address);
884
+ const sig = util.hexToU8a(
885
+ event.signature.startsWith("0x") ? event.signature : "0x" + event.signature
886
+ );
887
+ return scheme === "sr25519" ? crypto2.sr25519Verify(preimage, sig, publicKey) : crypto2.ed25519Verify(preimage, sig, publicKey);
888
+ }
889
+ var REQUIRED_EIP712_FIELDS = ["role", "policyRef", "decisionRef", "runId", "signedAt"];
890
+ var DEFAULT_EIP712_FRESHNESS_MS = 24 * 60 * 6e4;
891
+ function createEip712GovernanceVerifier(deps) {
892
+ if (deps.expectedDomain === void 0 || deps.expectedPrimaryType === void 0 || deps.expectedTypes === void 0) {
893
+ throw new Error(
894
+ "createEip712GovernanceVerifier: expectedDomain, expectedPrimaryType, and expectedTypes are required \u2014 an unpinned verifier accepts an empty attacker-signed struct (forgery)"
895
+ );
896
+ }
897
+ const declared = deps.expectedTypes[deps.expectedPrimaryType];
898
+ if (!declared) {
899
+ throw new Error(
900
+ `createEip712GovernanceVerifier: expectedTypes has no entry for primaryType "${deps.expectedPrimaryType}"`
901
+ );
902
+ }
903
+ const declaredNames = new Set(declared.map((f) => f.name));
904
+ const missing = REQUIRED_EIP712_FIELDS.filter((f) => !declaredNames.has(f));
905
+ if (missing.length > 0) {
906
+ throw new Error(
907
+ `createEip712GovernanceVerifier: the pinned "${deps.expectedPrimaryType}" type must include ${missing.join(", ")} so the signature commits to them`
908
+ );
909
+ }
910
+ return async (event, context) => {
911
+ if (!event.eip712) {
912
+ throw new Error("eip712 governance attestation is missing its `eip712` binding");
913
+ }
914
+ if (event.eip712.primaryType !== deps.expectedPrimaryType) {
915
+ return false;
916
+ }
917
+ if (!domainMatches(deps.expectedDomain, event.eip712.domain)) {
918
+ return false;
919
+ }
920
+ if (!typesMatch(deps.expectedTypes, event.eip712.types)) {
921
+ return false;
922
+ }
923
+ const signature = event.signature.startsWith("0x") ? event.signature : "0x" + event.signature;
924
+ const message = event.eip712.message ?? {};
925
+ const sigValid = await deps.verifyTypedData({
926
+ address: event.attestor.address,
927
+ domain: event.eip712.domain,
928
+ types: event.eip712.types,
929
+ primaryType: event.eip712.primaryType,
930
+ message,
931
+ signature
932
+ });
933
+ if (!sigValid) return false;
934
+ const claimBound = message.role === event.role && message.policyRef === event.policyRef && message.decisionRef === event.decisionRef && message.runId === context.runId && message.signedAt === event.signedAt;
935
+ if (!claimBound) return false;
936
+ const toleranceMs = deps.freshnessToleranceMs ?? DEFAULT_EIP712_FRESHNESS_MS;
937
+ const nowMs = deps.nowMs ?? Date.now();
938
+ const signedAtMs = Date.parse(String(message.signedAt));
939
+ if (!Number.isFinite(signedAtMs)) return false;
940
+ if (Math.abs(nowMs - signedAtMs) > toleranceMs) return false;
941
+ return true;
942
+ };
943
+ }
944
+ function domainMatches(expected, actual) {
945
+ const expectedKeys = Object.keys(expected);
946
+ const actualKeys = Object.keys(actual);
947
+ if (actualKeys.length !== expectedKeys.length) return false;
948
+ for (const key of expectedKeys) {
949
+ if (actual[key] !== expected[key]) return false;
950
+ }
951
+ return true;
952
+ }
953
+ function typesMatch(expected, actual) {
954
+ const norm = (t) => JSON.stringify(
955
+ Object.fromEntries(
956
+ Object.keys(t).sort().map((k) => [
957
+ k,
958
+ [...t[k]].sort((a, b) => a.name.localeCompare(b.name)).map((f) => `${f.name}:${f.type}`)
959
+ ])
960
+ )
961
+ );
962
+ return norm(expected) === norm(actual);
963
+ }
964
+
965
+ // src/bundle.ts
518
966
  function isPublicSpan(span) {
519
967
  return span.visibility === "public";
520
968
  }
@@ -603,7 +1051,7 @@ function createPublicView2(run, merkleRoot) {
603
1051
  function extractPublicView(bundle) {
604
1052
  return bundle.publicView;
605
1053
  }
606
- async function verifyBundle(bundle) {
1054
+ async function verifyBundle(bundle, options = {}) {
607
1055
  const errors = [];
608
1056
  const warnings = [];
609
1057
  const checks = {
@@ -648,7 +1096,11 @@ async function verifyBundle(bundle) {
648
1096
  );
649
1097
  }
650
1098
  try {
651
- const computedRootHash = await computeRootHash(run.rollingHash, run.spans);
1099
+ const computedRootHash = await computeRootHash(
1100
+ run.rollingHash,
1101
+ run.spans,
1102
+ run.modelManifestHash
1103
+ );
652
1104
  if (computedRootHash === bundle.rootHash) {
653
1105
  checks.rootHashValid = true;
654
1106
  } else {
@@ -675,6 +1127,75 @@ async function verifyBundle(bundle) {
675
1127
  `Failed to compute Merkle root: ${error instanceof Error ? error.message : String(error)}`
676
1128
  );
677
1129
  }
1130
+ const hasManifest = run.modelManifest !== void 0;
1131
+ const hasManifestHash = run.modelManifestHash !== void 0 && run.modelManifestHash.length > 0;
1132
+ if (!hasManifest && !hasManifestHash) {
1133
+ checks.modelManifestValid = true;
1134
+ } else {
1135
+ let manifestBindingValid = true;
1136
+ if (hasManifest && !hasManifestHash) {
1137
+ manifestBindingValid = false;
1138
+ errors.push(
1139
+ "Model manifest present but modelManifestHash (its commitment) is missing"
1140
+ );
1141
+ } else if (!hasManifest && hasManifestHash) {
1142
+ manifestBindingValid = false;
1143
+ errors.push(
1144
+ "modelManifestHash present but the model manifest itself is missing"
1145
+ );
1146
+ } else if (run.modelManifest !== void 0) {
1147
+ try {
1148
+ const recomputed = await computeModelManifestHash(run.modelManifest);
1149
+ if (recomputed !== run.modelManifestHash) {
1150
+ manifestBindingValid = false;
1151
+ errors.push(
1152
+ `Model manifest hash mismatch: recorded ${run.modelManifestHash}, computed ${recomputed}`
1153
+ );
1154
+ }
1155
+ } catch (error) {
1156
+ manifestBindingValid = false;
1157
+ errors.push(
1158
+ `Failed to recompute model manifest hash: ${error instanceof Error ? error.message : String(error)}`
1159
+ );
1160
+ }
1161
+ }
1162
+ if (manifestBindingValid && !checks.rootHashValid) {
1163
+ manifestBindingValid = false;
1164
+ errors.push(
1165
+ "Model manifest is not bound into the committed root hash"
1166
+ );
1167
+ }
1168
+ checks.modelManifestValid = manifestBindingValid;
1169
+ }
1170
+ const pvManifest = bundle.publicView.modelManifest;
1171
+ const pvManifestHash = bundle.publicView.modelManifestHash;
1172
+ if (pvManifestHash !== void 0 && pvManifestHash !== run.modelManifestHash) {
1173
+ checks.modelManifestValid = false;
1174
+ errors.push(
1175
+ `PublicView modelManifestHash (${pvManifestHash}) does not match the bound commitment (${run.modelManifestHash})`
1176
+ );
1177
+ }
1178
+ if (pvManifest !== void 0 && run.modelManifest === void 0) {
1179
+ checks.modelManifestValid = false;
1180
+ errors.push("PublicView carries a model manifest but the bound run has none");
1181
+ }
1182
+ if (pvManifest !== void 0) {
1183
+ try {
1184
+ const recomputed = await computeModelManifestHash(pvManifest);
1185
+ const expected = pvManifestHash ?? run.modelManifestHash;
1186
+ if (expected !== void 0 && recomputed !== expected) {
1187
+ checks.modelManifestValid = false;
1188
+ errors.push(
1189
+ `PublicView model manifest hash mismatch: recorded ${expected}, computed ${recomputed}`
1190
+ );
1191
+ }
1192
+ } catch (error) {
1193
+ checks.modelManifestValid = false;
1194
+ errors.push(
1195
+ `Failed to recompute publicView model manifest hash: ${error instanceof Error ? error.message : String(error)}`
1196
+ );
1197
+ }
1198
+ }
678
1199
  if (bundle.publicView.publicSpans.length === 0 && run.spans.length > 0) {
679
1200
  warnings.push(
680
1201
  "No public spans in bundle. The public view will be empty. Consider marking some spans as public for transparency."
@@ -685,7 +1206,39 @@ async function verifyBundle(bundle) {
685
1206
  `Status mismatch between publicView (${bundle.publicView.status}) and privateRun (${run.status})`
686
1207
  );
687
1208
  }
688
- const valid = checks.rollingHashValid && checks.rootHashValid && checks.merkleRootValid && checks.spanHashesValid && checks.eventHashesValid && checks.sequenceValid;
1209
+ if (options.governance) {
1210
+ try {
1211
+ const govOpts = options.governance === true ? {} : options.governance;
1212
+ const summaries = await verifyGovernanceAttestations(bundle, govOpts);
1213
+ const failed = summaries.filter((s) => !s.verified);
1214
+ checks.governanceValid = failed.length === 0;
1215
+ for (const f of failed) {
1216
+ errors.push(
1217
+ `Governance attestation failed (${f.scheme}, role ${f.role}, attestor ${f.attestor})` + (f.error ? `: ${f.error}` : "")
1218
+ );
1219
+ }
1220
+ } catch (error) {
1221
+ checks.governanceValid = false;
1222
+ errors.push(
1223
+ `Failed to verify governance attestations: ${error instanceof Error ? error.message : String(error)}`
1224
+ );
1225
+ }
1226
+ }
1227
+ if (options.toolReceipts) {
1228
+ try {
1229
+ const outcome = await options.toolReceipts(bundle);
1230
+ checks.toolReceiptsValid = outcome.valid;
1231
+ if (!outcome.valid) {
1232
+ errors.push(...outcome.errors);
1233
+ }
1234
+ } catch (error) {
1235
+ checks.toolReceiptsValid = false;
1236
+ errors.push(
1237
+ `Failed to verify tool receipts: ${error instanceof Error ? error.message : String(error)}`
1238
+ );
1239
+ }
1240
+ }
1241
+ const valid = checks.rollingHashValid && checks.rootHashValid && checks.merkleRootValid && checks.spanHashesValid && checks.eventHashesValid && checks.sequenceValid && checks.modelManifestValid !== false && checks.governanceValid !== false && checks.toolReceiptsValid !== false;
689
1242
  return {
690
1243
  valid,
691
1244
  errors,
@@ -1280,6 +1833,6 @@ function parseChunkContent(content) {
1280
1833
  // src/index.ts
1281
1834
  var VERSION = "0.1.0";
1282
1835
 
1283
- export { DEFAULT_EVENT_VISIBILITY, HASH_DOMAIN_PREFIXES, VERSION, addEvent, addSpan, buildSpanMerkleTree, canDisclose, closeSpan, computeEventHash, computeEventHashes, computeManifestHash, computeRollingHash, computeRootHash, computeSpanHash, countEventsByVisibility, countSpansByVisibility, createBundle, createDisclosureRequest, createManifest, createTrace, extractPublicView, filterPublicEvents, finalizeTrace, generateMerkleProof, getSpanEvents2 as getBundleSpanEvents, getChildSpans, getChunkPath, getEvent, getEventCount, getEventsByKind, getGenesisHash, getRootSpans, getSpan, getSpanCount, getSpanEvents, getSpanIndex, initRollingHash, isFinalized, isPublicEvent, isPublicSpan, parseChunkContent, reconstructBundleFromManifest, selectiveDisclose, signBundle, updateRollingHash, verifyBundle, verifyBundleSignature, verifyDisclosure, verifyManifest, verifyMerkleProof, verifyRollingHash, verifySpanDisclosure, verifySpanInclusion };
1836
+ export { DEFAULT_EVENT_VISIBILITY, HASH_DOMAIN_PREFIXES, SS58_PREFIX, VERSION, addEvent, addGovernanceAttestation, addSpan, buildSpanMerkleTree, canDisclose, closeSpan, computeEventHash, computeEventHashes, computeManifestHash, computeModelManifestHash, computeRollingHash, computeRootHash, computeSpanHash, countEventsByVisibility, countSpansByVisibility, createBundle, createDisclosureRequest, createEd25519GovernanceSigner, createEip712GovernanceVerifier, createManifest, createSr25519GovernanceSigner, createTrace, extractPublicView, filterPublicEvents, finalizeTrace, freezeModelManifest, generateMerkleProof, getSpanEvents2 as getBundleSpanEvents, getChildSpans, getChunkPath, getEvent, getEventCount, getEventsByKind, getGenesisHash, getRootSpans, getSpan, getSpanCount, getSpanEvents, getSpanIndex, governanceAttestationPreimage, initRollingHash, isFinalized, isPublicEvent, isPublicSpan, manifestFromAnthropic, manifestFromCheckpoint, manifestFromHuggingFace, manifestFromOpenAI, parseChunkContent, reconstructBundleFromManifest, selectiveDisclose, signBundle, updateRollingHash, validateModelManifest, verifyBundle, verifyBundleSignature, verifyDisclosure, verifyGovernanceAttestations, verifyManifest, verifyMerkleProof, verifyRollingHash, verifySpanDisclosure, verifySpanInclusion };
1284
1837
  //# sourceMappingURL=index.js.map
1285
1838
  //# sourceMappingURL=index.js.map