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