@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.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 +15 -1
- package/src/__tests__/bundle.test.ts +82 -0
- 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__/model-manifest.test.ts +136 -0
- package/src/__tests__/types.test.ts +8 -2
- package/src/bundle.ts +199 -5
- package/src/governance.ts +711 -0
- package/src/index.ts +69 -0
- package/src/model-manifest.ts +0 -0
- package/src/rolling-hash.ts +15 -6
- package/src/trace-builder.ts +68 -2
- package/src/types.ts +192 -1
package/src/bundle.ts
CHANGED
|
@@ -69,8 +69,48 @@ import {
|
|
|
69
69
|
computeRootHash,
|
|
70
70
|
} from "./rolling-hash.js";
|
|
71
71
|
|
|
72
|
+
import { computeModelManifestHash } from "./model-manifest.js";
|
|
73
|
+
|
|
72
74
|
import { buildSpanMerkleTree, computeSpanHash } from "./merkle.js";
|
|
73
75
|
|
|
76
|
+
import {
|
|
77
|
+
verifyGovernanceAttestations,
|
|
78
|
+
type VerifyGovernanceOptions,
|
|
79
|
+
} from "./governance.js";
|
|
80
|
+
|
|
81
|
+
// =============================================================================
|
|
82
|
+
// VERIFY OPTIONS
|
|
83
|
+
// =============================================================================
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Outcome shape returned by an injected tool-receipt verifier (provided by
|
|
87
|
+
* `@fluxpointstudios/orynq-sdk-tool-receipts` — passed in to avoid a circular
|
|
88
|
+
* dependency on that package from process-trace).
|
|
89
|
+
*/
|
|
90
|
+
export interface ToolReceiptVerifyOutcome {
|
|
91
|
+
valid: boolean;
|
|
92
|
+
errors: string[];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Options for {@link verifyBundle}. */
|
|
96
|
+
export interface VerifyBundleOptions {
|
|
97
|
+
/**
|
|
98
|
+
* Verify `governance-attestation` events. `true` uses the built-in
|
|
99
|
+
* sr25519/ed25519 verifiers; pass {@link VerifyGovernanceOptions} to register
|
|
100
|
+
* a pluggable verifier (e.g. eip712). Any unverified attestation fails the
|
|
101
|
+
* bundle.
|
|
102
|
+
*/
|
|
103
|
+
governance?: boolean | VerifyGovernanceOptions;
|
|
104
|
+
/**
|
|
105
|
+
* Verify `tool-receipt` events with an injected verifier from
|
|
106
|
+
* `@fluxpointstudios/orynq-sdk-tool-receipts`. Any failed receipt fails the
|
|
107
|
+
* bundle.
|
|
108
|
+
*/
|
|
109
|
+
toolReceipts?: (
|
|
110
|
+
bundle: TraceBundle
|
|
111
|
+
) => Promise<ToolReceiptVerifyOutcome> | ToolReceiptVerifyOutcome;
|
|
112
|
+
}
|
|
113
|
+
|
|
74
114
|
// =============================================================================
|
|
75
115
|
// VISIBILITY HELPERS
|
|
76
116
|
// =============================================================================
|
|
@@ -350,11 +390,12 @@ export function extractPublicView(bundle: TraceBundle): TraceBundlePublicView {
|
|
|
350
390
|
* ```
|
|
351
391
|
*/
|
|
352
392
|
export async function verifyBundle(
|
|
353
|
-
bundle: TraceBundle
|
|
393
|
+
bundle: TraceBundle,
|
|
394
|
+
options: VerifyBundleOptions = {}
|
|
354
395
|
): Promise<TraceVerificationResult> {
|
|
355
396
|
const errors: string[] = [];
|
|
356
397
|
const warnings: string[] = [];
|
|
357
|
-
const checks = {
|
|
398
|
+
const checks: TraceVerificationResult["checks"] = {
|
|
358
399
|
rollingHashValid: false,
|
|
359
400
|
rootHashValid: false,
|
|
360
401
|
merkleRootValid: false,
|
|
@@ -422,7 +463,13 @@ export async function verifyBundle(
|
|
|
422
463
|
// ---------------------------------------------------------------------------
|
|
423
464
|
|
|
424
465
|
try {
|
|
425
|
-
|
|
466
|
+
// Recompute the root binding the recorded model-manifest commitment (#59),
|
|
467
|
+
// so a manifest swapped after commitment fails here.
|
|
468
|
+
const computedRootHash = await computeRootHash(
|
|
469
|
+
run.rollingHash,
|
|
470
|
+
run.spans,
|
|
471
|
+
run.modelManifestHash
|
|
472
|
+
);
|
|
426
473
|
if (computedRootHash === bundle.rootHash) {
|
|
427
474
|
checks.rootHashValid = true;
|
|
428
475
|
} else {
|
|
@@ -455,6 +502,105 @@ export async function verifyBundle(
|
|
|
455
502
|
);
|
|
456
503
|
}
|
|
457
504
|
|
|
505
|
+
// ---------------------------------------------------------------------------
|
|
506
|
+
// Verify Model-Manifest Pin Binding (issue #59)
|
|
507
|
+
// ---------------------------------------------------------------------------
|
|
508
|
+
// When a manifest is pinned it MUST (a) hash to the recorded commitment and
|
|
509
|
+
// (b) be bound into the committed root. (b) is enforced by folding
|
|
510
|
+
// modelManifestHash into computeRootHash above — a swapped hash breaks
|
|
511
|
+
// rootHashValid. Here we additionally recompute the commitment from the
|
|
512
|
+
// manifest itself so that swapping the manifest (while leaving the recorded
|
|
513
|
+
// hash) is caught, and reject a manifest present but not bound into the root.
|
|
514
|
+
|
|
515
|
+
const hasManifest = run.modelManifest !== undefined;
|
|
516
|
+
const hasManifestHash =
|
|
517
|
+
run.modelManifestHash !== undefined && run.modelManifestHash.length > 0;
|
|
518
|
+
|
|
519
|
+
if (!hasManifest && !hasManifestHash) {
|
|
520
|
+
// No manifest pinned — nothing to bind (warn-only path lives in finalizeTrace).
|
|
521
|
+
checks.modelManifestValid = true;
|
|
522
|
+
} else {
|
|
523
|
+
let manifestBindingValid = true;
|
|
524
|
+
|
|
525
|
+
if (hasManifest && !hasManifestHash) {
|
|
526
|
+
manifestBindingValid = false;
|
|
527
|
+
errors.push(
|
|
528
|
+
"Model manifest present but modelManifestHash (its commitment) is missing"
|
|
529
|
+
);
|
|
530
|
+
} else if (!hasManifest && hasManifestHash) {
|
|
531
|
+
manifestBindingValid = false;
|
|
532
|
+
errors.push(
|
|
533
|
+
"modelManifestHash present but the model manifest itself is missing"
|
|
534
|
+
);
|
|
535
|
+
} else if (run.modelManifest !== undefined) {
|
|
536
|
+
try {
|
|
537
|
+
const recomputed = await computeModelManifestHash(run.modelManifest);
|
|
538
|
+
if (recomputed !== run.modelManifestHash) {
|
|
539
|
+
manifestBindingValid = false;
|
|
540
|
+
errors.push(
|
|
541
|
+
`Model manifest hash mismatch: recorded ${run.modelManifestHash}, computed ${recomputed}`
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
} catch (error) {
|
|
545
|
+
manifestBindingValid = false;
|
|
546
|
+
errors.push(
|
|
547
|
+
`Failed to recompute model manifest hash: ${error instanceof Error ? error.message : String(error)}`
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// The manifest must actually be bound into the committed root. If the root
|
|
553
|
+
// recompute (with the manifest folded in) matched, rootHashValid is true;
|
|
554
|
+
// a manifest that is NOT bound produces a root mismatch above.
|
|
555
|
+
if (manifestBindingValid && !checks.rootHashValid) {
|
|
556
|
+
manifestBindingValid = false;
|
|
557
|
+
errors.push(
|
|
558
|
+
"Model manifest is not bound into the committed root hash"
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
checks.modelManifestValid = manifestBindingValid;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// ---------------------------------------------------------------------------
|
|
566
|
+
// Verify PublicView Model-Manifest (issue #59)
|
|
567
|
+
// ---------------------------------------------------------------------------
|
|
568
|
+
// publicView.{modelManifest,modelManifestHash} is the shared artifact an
|
|
569
|
+
// EXTERNAL verifier reads. It must (a) equal the bound privateRun commitment
|
|
570
|
+
// and (b) recompute consistently from the publicView manifest itself — else an
|
|
571
|
+
// attacker can leave privateRun honest but fabricate the public fields.
|
|
572
|
+
|
|
573
|
+
const pvManifest = bundle.publicView.modelManifest;
|
|
574
|
+
const pvManifestHash = bundle.publicView.modelManifestHash;
|
|
575
|
+
|
|
576
|
+
if (pvManifestHash !== undefined && pvManifestHash !== run.modelManifestHash) {
|
|
577
|
+
checks.modelManifestValid = false;
|
|
578
|
+
errors.push(
|
|
579
|
+
`PublicView modelManifestHash (${pvManifestHash}) does not match the bound commitment (${run.modelManifestHash})`
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
if (pvManifest !== undefined && run.modelManifest === undefined) {
|
|
583
|
+
checks.modelManifestValid = false;
|
|
584
|
+
errors.push("PublicView carries a model manifest but the bound run has none");
|
|
585
|
+
}
|
|
586
|
+
if (pvManifest !== undefined) {
|
|
587
|
+
try {
|
|
588
|
+
const recomputed = await computeModelManifestHash(pvManifest);
|
|
589
|
+
const expected = pvManifestHash ?? run.modelManifestHash;
|
|
590
|
+
if (expected !== undefined && recomputed !== expected) {
|
|
591
|
+
checks.modelManifestValid = false;
|
|
592
|
+
errors.push(
|
|
593
|
+
`PublicView model manifest hash mismatch: recorded ${expected}, computed ${recomputed}`
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
} catch (error) {
|
|
597
|
+
checks.modelManifestValid = false;
|
|
598
|
+
errors.push(
|
|
599
|
+
`Failed to recompute publicView model manifest hash: ${error instanceof Error ? error.message : String(error)}`
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
458
604
|
// ---------------------------------------------------------------------------
|
|
459
605
|
// Additional Warnings
|
|
460
606
|
// ---------------------------------------------------------------------------
|
|
@@ -474,14 +620,62 @@ export async function verifyBundle(
|
|
|
474
620
|
);
|
|
475
621
|
}
|
|
476
622
|
|
|
477
|
-
//
|
|
623
|
+
// ---------------------------------------------------------------------------
|
|
624
|
+
// Verify Governance Attestations (issue #58, opt-in)
|
|
625
|
+
// ---------------------------------------------------------------------------
|
|
626
|
+
|
|
627
|
+
if (options.governance) {
|
|
628
|
+
try {
|
|
629
|
+
const govOpts: VerifyGovernanceOptions =
|
|
630
|
+
options.governance === true ? {} : options.governance;
|
|
631
|
+
const summaries = await verifyGovernanceAttestations(bundle, govOpts);
|
|
632
|
+
const failed = summaries.filter((s) => !s.verified);
|
|
633
|
+
checks.governanceValid = failed.length === 0;
|
|
634
|
+
for (const f of failed) {
|
|
635
|
+
errors.push(
|
|
636
|
+
`Governance attestation failed (${f.scheme}, role ${f.role}, attestor ${f.attestor})` +
|
|
637
|
+
(f.error ? `: ${f.error}` : "")
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
} catch (error) {
|
|
641
|
+
checks.governanceValid = false;
|
|
642
|
+
errors.push(
|
|
643
|
+
`Failed to verify governance attestations: ${error instanceof Error ? error.message : String(error)}`
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// ---------------------------------------------------------------------------
|
|
649
|
+
// Verify Tool-Call Receipts (issue #60, opt-in, injected verifier)
|
|
650
|
+
// ---------------------------------------------------------------------------
|
|
651
|
+
|
|
652
|
+
if (options.toolReceipts) {
|
|
653
|
+
try {
|
|
654
|
+
const outcome = await options.toolReceipts(bundle);
|
|
655
|
+
checks.toolReceiptsValid = outcome.valid;
|
|
656
|
+
if (!outcome.valid) {
|
|
657
|
+
errors.push(...outcome.errors);
|
|
658
|
+
}
|
|
659
|
+
} catch (error) {
|
|
660
|
+
checks.toolReceiptsValid = false;
|
|
661
|
+
errors.push(
|
|
662
|
+
`Failed to verify tool receipts: ${error instanceof Error ? error.message : String(error)}`
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Determine overall validity. Optional checks only fail the bundle when
|
|
668
|
+
// explicitly run and false (undefined === "not checked").
|
|
478
669
|
const valid =
|
|
479
670
|
checks.rollingHashValid &&
|
|
480
671
|
checks.rootHashValid &&
|
|
481
672
|
checks.merkleRootValid &&
|
|
482
673
|
checks.spanHashesValid &&
|
|
483
674
|
checks.eventHashesValid &&
|
|
484
|
-
checks.sequenceValid
|
|
675
|
+
checks.sequenceValid &&
|
|
676
|
+
checks.modelManifestValid !== false &&
|
|
677
|
+
checks.governanceValid !== false &&
|
|
678
|
+
checks.toolReceiptsValid !== false;
|
|
485
679
|
|
|
486
680
|
return {
|
|
487
681
|
valid,
|