@jmanuelcorral/openteam 0.1.42 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/.opencode/openteam.example.json +6 -1
  2. package/README.es.md +1 -1
  3. package/README.md +1 -1
  4. package/dist/capabilities/types.d.ts +4 -4
  5. package/dist/classifier/types.d.ts +3 -3
  6. package/dist/cli.js +11 -0
  7. package/dist/commands/doctor.d.ts +2 -0
  8. package/dist/commands/doctor.d.ts.map +1 -1
  9. package/dist/commands/graph.d.ts +28 -2
  10. package/dist/commands/graph.d.ts.map +1 -1
  11. package/dist/config/graphFeatureGate.d.ts +96 -0
  12. package/dist/config/graphFeatureGate.d.ts.map +1 -0
  13. package/dist/config/migrations.d.ts.map +1 -1
  14. package/dist/config/resolve.d.ts +20 -1
  15. package/dist/config/resolve.d.ts.map +1 -1
  16. package/dist/config/schema.d.ts +22 -19
  17. package/dist/config/schema.d.ts.map +1 -1
  18. package/dist/console/render.d.ts.map +1 -1
  19. package/dist/context/redaction.d.ts +23 -0
  20. package/dist/context/redaction.d.ts.map +1 -1
  21. package/dist/context/schema.d.ts +3 -3
  22. package/dist/contract/opencode.d.ts +2 -2
  23. package/dist/contract/opencode.d.ts.map +1 -1
  24. package/dist/graph/certificate.d.ts +505 -0
  25. package/dist/graph/certificate.d.ts.map +1 -0
  26. package/dist/graph/ids.d.ts +9 -0
  27. package/dist/graph/ids.d.ts.map +1 -1
  28. package/dist/graph/privacyPolicy.d.ts.map +1 -1
  29. package/dist/graph/reducer.d.ts.map +1 -1
  30. package/dist/graph/replay.d.ts.map +1 -1
  31. package/dist/graph/reviewerPolicy.d.ts.map +1 -1
  32. package/dist/graph/schema.d.ts +33 -33
  33. package/dist/graph/schema.d.ts.map +1 -1
  34. package/dist/graph/soakLedger.d.ts +144 -0
  35. package/dist/graph/soakLedger.d.ts.map +1 -0
  36. package/dist/graph/types.d.ts +2 -0
  37. package/dist/graph/types.d.ts.map +1 -1
  38. package/dist/graph/validate.d.ts.map +1 -1
  39. package/dist/index.d.ts +47 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +2343 -178
  42. package/dist/local/registry.d.ts.map +1 -1
  43. package/dist/memory/types.d.ts +2 -2
  44. package/dist/orchestrator/graphFacade.d.ts +39 -0
  45. package/dist/orchestrator/graphFacade.d.ts.map +1 -0
  46. package/dist/orchestrator/graphIngress.d.ts +136 -0
  47. package/dist/orchestrator/graphIngress.d.ts.map +1 -0
  48. package/dist/orchestrator/graphShadow.d.ts +121 -0
  49. package/dist/orchestrator/graphShadow.d.ts.map +1 -0
  50. package/dist/orchestrator/ledger.d.ts +14 -2
  51. package/dist/orchestrator/ledger.d.ts.map +1 -1
  52. package/dist/orchestrator/opencodeSessionAdapter.d.ts.map +1 -1
  53. package/dist/orchestrator/roles.d.ts +3 -3
  54. package/dist/orchestrator/sdd.d.ts +55 -1
  55. package/dist/orchestrator/sdd.d.ts.map +1 -1
  56. package/dist/orchestrator/sddCompiler.d.ts +6 -2
  57. package/dist/orchestrator/sddCompiler.d.ts.map +1 -1
  58. package/dist/orchestrator/worktreeAdapter.d.ts.map +1 -1
  59. package/dist/orchestrator/worktreeDispatch.d.ts.map +1 -1
  60. package/dist/plugin/graphShadowIngress.d.ts +165 -0
  61. package/dist/plugin/graphShadowIngress.d.ts.map +1 -0
  62. package/dist/plugin/graphTool.d.ts +63 -0
  63. package/dist/plugin/graphTool.d.ts.map +1 -0
  64. package/dist/plugin/hooks.d.ts +6 -0
  65. package/dist/plugin/hooks.d.ts.map +1 -1
  66. package/dist/plugin/legacyRunProducer.d.ts +60 -0
  67. package/dist/plugin/legacyRunProducer.d.ts.map +1 -0
  68. package/dist/plugin/reportRunSchema.d.ts +60 -0
  69. package/dist/plugin/reportRunSchema.d.ts.map +1 -0
  70. package/dist/plugin/sessionTracker.d.ts +42 -0
  71. package/dist/plugin/sessionTracker.d.ts.map +1 -0
  72. package/dist/plugin/shadowPipeline.d.ts +49 -0
  73. package/dist/plugin/shadowPipeline.d.ts.map +1 -0
  74. package/dist/plugin/soakObserver.d.ts +73 -0
  75. package/dist/plugin/soakObserver.d.ts.map +1 -0
  76. package/dist/storage/graph/codec.d.ts.map +1 -1
  77. package/dist/storage/graph/migrateLegacyBriefs.d.ts.map +1 -1
  78. package/dist/storage/graph/migration.d.ts +91 -0
  79. package/dist/storage/graph/migration.d.ts.map +1 -0
  80. package/dist/storage/graph/provider.d.ts +1 -0
  81. package/dist/storage/graph/provider.d.ts.map +1 -1
  82. package/dist/storage/graph/recovery.d.ts.map +1 -1
  83. package/dist/storage/graph/runRegistry.d.ts.map +1 -1
  84. package/dist/storage/graph/soakRecorder.d.ts +59 -0
  85. package/dist/storage/graph/soakRecorder.d.ts.map +1 -0
  86. package/dist/storage/graph/workspaceLease.d.ts.map +1 -1
  87. package/dist/storage/graph/worktreeRegistry.d.ts.map +1 -1
  88. package/dist/storage/graph/writer.d.ts +15 -0
  89. package/dist/storage/graph/writer.d.ts.map +1 -1
  90. package/dist/storage/path.d.ts.map +1 -1
  91. package/dist/telemetry/events.d.ts +12 -12
  92. package/dist/telemetry/graphRuntimeHealth.d.ts +53 -0
  93. package/dist/telemetry/graphRuntimeHealth.d.ts.map +1 -0
  94. package/dist/telemetry/graphView.d.ts +1 -0
  95. package/dist/telemetry/graphView.d.ts.map +1 -1
  96. package/dist/telemetry/types.d.ts +4 -4
  97. package/package.json +7 -5
package/dist/index.js CHANGED
@@ -340,6 +340,7 @@ var GraphModeSchema = z.enum(["off", "shadow", "active"]);
340
340
  var GraphConfigSchema = z.object({
341
341
  mode: GraphModeSchema.default("off"),
342
342
  killSwitch: z.boolean().default(false),
343
+ operatorApproval: z.boolean().default(false),
343
344
  worktrees: z.object({
344
345
  enabled: z.boolean().default(false)
345
346
  }).default({ enabled: false })
@@ -510,6 +511,59 @@ async function writeOpenTeamConfigFile(config, path = DEFAULT_CONFIG_PATH, stora
510
511
  // src/commands/setup.ts
511
512
  var OPENCODE_CONFIG_PATH = "opencode.json";
512
513
 
514
+ // src/config/graphFeatureGate.ts
515
+ function checkCertificate(status, name) {
516
+ if (status.status === "absent") {
517
+ return {
518
+ code: `${name}-certificate-absent`,
519
+ detail: `${name} certificate not found — run the ${name} certification suite to produce it`
520
+ };
521
+ }
522
+ if (status.status === "invalid") {
523
+ return {
524
+ code: `${name}-certificate-invalid`,
525
+ detail: `${name} certificate rejected by recomputing parser: ${status.code} — ${status.detail}`
526
+ };
527
+ }
528
+ return;
529
+ }
530
+ function evaluateGraphGate(input) {
531
+ const violations = [];
532
+ const shadowV = checkCertificate(input.shadow, "shadow");
533
+ if (shadowV !== undefined)
534
+ violations.push(shadowV);
535
+ const releaseV = checkCertificate(input.release, "release");
536
+ if (releaseV !== undefined)
537
+ violations.push(releaseV);
538
+ const soakV = checkCertificate(input.soak, "soak");
539
+ if (soakV !== undefined)
540
+ violations.push(soakV);
541
+ if (input.migration === "pending") {
542
+ violations.push({
543
+ code: "migration-pending",
544
+ detail: "legacy migration has pending work — run the migration tool to completion before enabling active mode"
545
+ });
546
+ } else if (input.migration === "unknown") {
547
+ violations.push({
548
+ code: "migration-unknown",
549
+ detail: "could not determine migration status — check ledger readability and storage access"
550
+ });
551
+ }
552
+ if (!input.operatorApproval) {
553
+ violations.push({
554
+ code: "operator-approval-missing",
555
+ detail: "set graph.operatorApproval to true in .opencode/openteam.json after reviewing all certificates and completing migration — this is a declaration of intent, not a proof"
556
+ });
557
+ }
558
+ return {
559
+ allowed: violations.length === 0,
560
+ violations
561
+ };
562
+ }
563
+ function isGateApplicable(mode) {
564
+ return mode === "active";
565
+ }
566
+
513
567
  // src/config/migrations.ts
514
568
  var CURRENT_CONFIG_VERSION = 1;
515
569
 
@@ -546,6 +600,725 @@ function loadOpenTeamConfig(raw) {
546
600
  return OpenTeamConfigSchema.parse(migrateConfig(raw ?? {}));
547
601
  }
548
602
 
603
+ // src/config/resolve.ts
604
+ function resolveGraphConfig(config, source) {
605
+ const configuredMode = config.graph?.mode ?? "off";
606
+ const killSwitch = config.graph?.killSwitch ?? false;
607
+ const effectiveMode = killSwitch ? "off" : configuredMode;
608
+ return {
609
+ configuredMode,
610
+ effectiveMode,
611
+ executorEnabled: effectiveMode === "active",
612
+ killSwitch,
613
+ source
614
+ };
615
+ }
616
+ function applyGraphGate(resolved, gateResult) {
617
+ if (gateResult.allowed) {
618
+ return { ...resolved, gateDenied: false, gateViolations: [] };
619
+ }
620
+ return {
621
+ ...resolved,
622
+ effectiveMode: "off",
623
+ executorEnabled: false,
624
+ gateDenied: true,
625
+ gateViolations: gateResult.violations
626
+ };
627
+ }
628
+
629
+ // src/graph/certificate.ts
630
+ import { z as z3 } from "zod";
631
+
632
+ // src/context/redaction.ts
633
+ import { createHash } from "node:crypto";
634
+
635
+ class RedactionError extends Error {
636
+ code;
637
+ key;
638
+ constructor(key) {
639
+ super(`raw content field is not allowed: "${key}"`);
640
+ this.name = "RedactionError";
641
+ this.code = "raw-content";
642
+ this.key = key;
643
+ }
644
+ }
645
+ function sha256Hex(input) {
646
+ return createHash("sha256").update(input, "utf8").digest("hex");
647
+ }
648
+ var FORBIDDEN_RAW_KEYS = new Set([
649
+ "content",
650
+ "text",
651
+ "brief",
652
+ "prompt",
653
+ "raw",
654
+ "body",
655
+ "output",
656
+ "findings",
657
+ "message",
658
+ "source"
659
+ ]);
660
+ function walkForbiddenKeys(value, onForbidden) {
661
+ if (Array.isArray(value)) {
662
+ for (const item of value) {
663
+ walkForbiddenKeys(item, onForbidden);
664
+ }
665
+ return;
666
+ }
667
+ if (value === null || typeof value !== "object") {
668
+ return;
669
+ }
670
+ for (const [key, child] of Object.entries(value)) {
671
+ if (FORBIDDEN_RAW_KEYS.has(key)) {
672
+ const shouldStop = onForbidden(key);
673
+ if (shouldStop)
674
+ return;
675
+ }
676
+ walkForbiddenKeys(child, onForbidden);
677
+ }
678
+ }
679
+ function assertReferenceOnly(value) {
680
+ walkForbiddenKeys(value, (key) => {
681
+ throw new RedactionError(key);
682
+ });
683
+ }
684
+ function scanReferenceOnly(surfaces) {
685
+ let rawFindings = 0;
686
+ for (const surface of surfaces) {
687
+ walkForbiddenKeys(surface, () => {
688
+ rawFindings++;
689
+ return false;
690
+ });
691
+ }
692
+ return { surfacesScanned: surfaces.length, rawFindings };
693
+ }
694
+
695
+ // src/graph/soakLedger.ts
696
+ import { z as z2 } from "zod";
697
+ var Sha256Schema = z2.string().regex(/^[0-9a-f]{64}$/);
698
+ var SoakPlatformSchema = z2.enum(["linux", "win32", "darwin"]);
699
+ var PrivacyPairSchema = z2.object({
700
+ surfacesScanned: z2.number().int().nonnegative(),
701
+ rawFindings: z2.number().int().nonnegative()
702
+ }).strict();
703
+ var DuplicateEffectPairSchema = z2.object({
704
+ effectsExamined: z2.number().int().nonnegative(),
705
+ duplicatesFound: z2.number().int().nonnegative()
706
+ }).strict();
707
+ var ModelVerificationPairSchema = z2.object({
708
+ nodesChecked: z2.number().int().nonnegative(),
709
+ unverified: z2.number().int().nonnegative(),
710
+ mismatched: z2.number().int().nonnegative()
711
+ }).strict();
712
+ var SoakObservationSchema = z2.object({
713
+ version: z2.literal(1),
714
+ seq: z2.number().int().nonnegative(),
715
+ timestamp: z2.string().datetime(),
716
+ platform: SoakPlatformSchema,
717
+ opencodeVersion: z2.string().min(1),
718
+ provenance: z2.enum(["genuine-usage", "ci-synthetic"]),
719
+ traceDigest: Sha256Schema,
720
+ criticalDivergences: z2.number().int().nonnegative().nullable(),
721
+ privacy: PrivacyPairSchema.nullable(),
722
+ duplicateEffects: DuplicateEffectPairSchema.nullable(),
723
+ modelVerification: ModelVerificationPairSchema.nullable(),
724
+ chainDigest: Sha256Schema
725
+ }).strict();
726
+ function canonicalObservationPreimage(obs) {
727
+ return JSON.stringify({
728
+ version: obs.version,
729
+ seq: obs.seq,
730
+ timestamp: obs.timestamp,
731
+ platform: obs.platform,
732
+ opencodeVersion: obs.opencodeVersion,
733
+ provenance: obs.provenance,
734
+ traceDigest: obs.traceDigest,
735
+ criticalDivergences: obs.criticalDivergences,
736
+ privacy: obs.privacy,
737
+ duplicateEffects: obs.duplicateEffects,
738
+ modelVerification: obs.modelVerification
739
+ });
740
+ }
741
+ function validateChain(observations) {
742
+ if (observations.length === 0) {
743
+ return { valid: true, observations: [] };
744
+ }
745
+ let prevChainDigest = "0".repeat(64);
746
+ for (let i = 0;i < observations.length; i++) {
747
+ const obs = observations[i];
748
+ if (obs.seq !== i) {
749
+ return { valid: false, observations };
750
+ }
751
+ if (i > 0) {
752
+ const prev = observations[i - 1];
753
+ if (obs.timestamp <= prev.timestamp) {
754
+ return { valid: false, observations };
755
+ }
756
+ }
757
+ const preimage = canonicalObservationPreimage(obs);
758
+ const expectedDigest = sha256Hex(prevChainDigest + preimage);
759
+ if (obs.chainDigest !== expectedDigest) {
760
+ return { valid: false, observations };
761
+ }
762
+ prevChainDigest = obs.chainDigest;
763
+ }
764
+ return { valid: true, observations };
765
+ }
766
+ function canonicalLedger(chains) {
767
+ const validChains = [];
768
+ for (const chain of chains) {
769
+ const result = validateChain(chain);
770
+ if (result.valid && chain.length > 0) {
771
+ validChains.push(chain);
772
+ }
773
+ }
774
+ validChains.sort((a, b) => {
775
+ const aDigest = a[0].chainDigest;
776
+ const bDigest = b[0].chainDigest;
777
+ return aDigest < bDigest ? -1 : aDigest > bDigest ? 1 : 0;
778
+ });
779
+ const lines = [];
780
+ for (const chain of validChains) {
781
+ for (const obs of chain) {
782
+ lines.push(JSON.stringify(obs));
783
+ }
784
+ }
785
+ return sha256Hex(lines.join(`
786
+ `));
787
+ }
788
+
789
+ // src/graph/certificate.ts
790
+ var SHADOW_CERTIFICATE_MIN_RUNS = 1000;
791
+ var DivergenceCodeSchema = z3.enum([
792
+ "status",
793
+ "order",
794
+ "roles",
795
+ "models",
796
+ "fixes",
797
+ "outcome"
798
+ ]);
799
+ var ProvenanceSchema = z3.enum([
800
+ "recomputed",
801
+ "compiled",
802
+ "validated",
803
+ "carried"
804
+ ]);
805
+ var EvidenceSchema = z3.object({
806
+ opencodeVersion: z3.string().min(1),
807
+ parity: z3.object({
808
+ runs: z3.number().int().nonnegative(),
809
+ match: z3.number().int().nonnegative(),
810
+ divergent: z3.number().int().nonnegative(),
811
+ inconclusive: z3.number().int().nonnegative(),
812
+ divergences: z3.array(DivergenceCodeSchema)
813
+ }).strict(),
814
+ legacy: z3.object({
815
+ observed: z3.number().int().nonnegative(),
816
+ duplicated: z3.number().int().nonnegative()
817
+ }).strict(),
818
+ privacy: z3.object({
819
+ surfacesScanned: z3.number().int().nonnegative(),
820
+ rawFindings: z3.number().int().nonnegative()
821
+ }).strict(),
822
+ overhead: z3.object({
823
+ p95Millis: z3.number().nonnegative(),
824
+ budgetMillis: z3.number().positive(),
825
+ samples: z3.number().int().nonnegative()
826
+ }).strict(),
827
+ provenance: z3.record(DivergenceCodeSchema, ProvenanceSchema)
828
+ }).strict();
829
+ var CertificateSchema = z3.object({
830
+ version: z3.literal(1),
831
+ certificate: z3.literal("graph-shadow"),
832
+ opencodeVersion: z3.string().min(1),
833
+ evidence: EvidenceSchema,
834
+ verdict: z3.enum(["pass", "fail"]),
835
+ failedGates: z3.array(z3.enum([
836
+ "sample",
837
+ "parity",
838
+ "one-legacy-execution",
839
+ "privacy",
840
+ "overhead"
841
+ ])),
842
+ digest: z3.string().regex(/^[0-9a-f]{64}$/)
843
+ }).strict();
844
+
845
+ class ShadowCertificateError extends Error {
846
+ code;
847
+ detail;
848
+ constructor(code, detail) {
849
+ super(`${code}: ${detail}`);
850
+ this.name = "ShadowCertificateError";
851
+ this.code = code;
852
+ this.detail = detail;
853
+ }
854
+ }
855
+ function evaluateGates(evidence) {
856
+ const failed = [];
857
+ if (evidence.parity.runs < SHADOW_CERTIFICATE_MIN_RUNS) {
858
+ failed.push("sample");
859
+ }
860
+ if (evidence.parity.divergent > 0 || evidence.parity.inconclusive > 0 || evidence.parity.divergences.length > 0 || evidence.parity.match !== evidence.parity.runs) {
861
+ failed.push("parity");
862
+ }
863
+ if (evidence.legacy.duplicated > 0 || evidence.legacy.observed < 1) {
864
+ failed.push("one-legacy-execution");
865
+ }
866
+ if (evidence.privacy.rawFindings > 0 || evidence.privacy.surfacesScanned < 1) {
867
+ failed.push("privacy");
868
+ }
869
+ if (evidence.overhead.samples < 1 || evidence.overhead.p95Millis > evidence.overhead.budgetMillis) {
870
+ failed.push("overhead");
871
+ }
872
+ return failed;
873
+ }
874
+ function canonicalCertificate(certificate) {
875
+ const evidence = certificate.evidence;
876
+ return JSON.stringify({
877
+ version: certificate.version,
878
+ certificate: certificate.certificate,
879
+ opencodeVersion: certificate.opencodeVersion,
880
+ verdict: certificate.verdict,
881
+ failedGates: [...certificate.failedGates],
882
+ evidence: {
883
+ opencodeVersion: evidence.opencodeVersion,
884
+ parity: {
885
+ runs: evidence.parity.runs,
886
+ match: evidence.parity.match,
887
+ divergent: evidence.parity.divergent,
888
+ inconclusive: evidence.parity.inconclusive,
889
+ divergences: [...evidence.parity.divergences].sort()
890
+ },
891
+ legacy: {
892
+ observed: evidence.legacy.observed,
893
+ duplicated: evidence.legacy.duplicated
894
+ },
895
+ privacy: {
896
+ surfacesScanned: evidence.privacy.surfacesScanned,
897
+ rawFindings: evidence.privacy.rawFindings
898
+ },
899
+ overhead: {
900
+ p95Millis: evidence.overhead.p95Millis,
901
+ budgetMillis: evidence.overhead.budgetMillis,
902
+ samples: evidence.overhead.samples
903
+ },
904
+ provenance: Object.fromEntries(Object.entries(evidence.provenance).sort(([a], [b]) => a < b ? -1 : 1))
905
+ }
906
+ });
907
+ }
908
+ function sameGates(a, b) {
909
+ return a.length === b.length && a.every((gate, index) => gate === b[index]);
910
+ }
911
+ function parseShadowCertificate(value, opencodeVersion) {
912
+ const parsed = CertificateSchema.safeParse(value);
913
+ if (!parsed.success) {
914
+ const detail = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.code}`).join("; ");
915
+ throw new ShadowCertificateError("invalid-certificate", detail);
916
+ }
917
+ const certificate = parsed.data;
918
+ const failedGates = evaluateGates(certificate.evidence);
919
+ const verdict = failedGates.length === 0 ? "pass" : "fail";
920
+ if (certificate.verdict !== verdict || !sameGates(certificate.failedGates, failedGates)) {
921
+ throw new ShadowCertificateError("verdict-mismatch", `evidence yields "${verdict}" (${failedGates.join(",") || "no failed gate"})`);
922
+ }
923
+ const { digest, ...unsigned } = certificate;
924
+ if (sha256Hex(canonicalCertificate(unsigned)) !== digest) {
925
+ throw new ShadowCertificateError("digest-mismatch", "certificate digest does not cover the presented evidence");
926
+ }
927
+ if (certificate.opencodeVersion !== opencodeVersion) {
928
+ throw new ShadowCertificateError("opencode-mismatch", `certified for "${certificate.opencodeVersion}", expected "${opencodeVersion}"`);
929
+ }
930
+ return certificate;
931
+ }
932
+ var ReleaseDeterminismSchema = z3.object({ runs: z3.number().int().nonnegative(), allMatch: z3.boolean() }).strict();
933
+ var ReleaseCrashRecoverySchema = z3.object({
934
+ scenarios: z3.number().int().nonnegative(),
935
+ allConverge: z3.boolean()
936
+ }).strict();
937
+ var ReleaseReplayEquivalenceSchema = z3.object({
938
+ checks: z3.number().int().nonnegative(),
939
+ allEquivalent: z3.boolean()
940
+ }).strict();
941
+ var ReleasePrivacySchema = z3.object({
942
+ surfacesScanned: z3.number().int().nonnegative(),
943
+ rawFindings: z3.number().int().nonnegative()
944
+ }).strict();
945
+ var ReleaseContractSchema = z3.object({
946
+ roundTrips: z3.number().int().nonnegative(),
947
+ allSettled: z3.boolean()
948
+ }).strict();
949
+ var ReleaseMigrationSchema = z3.object({
950
+ cases: z3.number().int().nonnegative(),
951
+ allSucceeded: z3.boolean()
952
+ }).strict();
953
+ var ReleaseHealthSchema = z3.object({
954
+ samples: z3.number().int().nonnegative(),
955
+ bounded: z3.boolean(),
956
+ idempotent: z3.boolean()
957
+ }).strict();
958
+ var ReleasePerformanceSchema = z3.object({
959
+ p95Millis: z3.number().nonnegative(),
960
+ budgetMillis: z3.number().positive(),
961
+ samples: z3.number().int().nonnegative()
962
+ }).strict();
963
+ var ReleaseEvidenceSchema = z3.object({
964
+ opencodeVersion: z3.string().min(1),
965
+ platform: z3.enum(["linux", "win32"]),
966
+ shadowCertificateDigest: z3.string().regex(/^[0-9a-f]{64}$/),
967
+ determinism: ReleaseDeterminismSchema,
968
+ crashRecovery: ReleaseCrashRecoverySchema,
969
+ replayEquivalence: ReleaseReplayEquivalenceSchema,
970
+ privacy: ReleasePrivacySchema,
971
+ contract: ReleaseContractSchema,
972
+ migration: ReleaseMigrationSchema,
973
+ health: ReleaseHealthSchema,
974
+ performance: ReleasePerformanceSchema
975
+ }).strict();
976
+ var ReleaseCertificateSchema = z3.object({
977
+ version: z3.literal(1),
978
+ certificate: z3.literal("graph-release"),
979
+ platform: z3.enum(["linux", "win32"]),
980
+ opencodeVersion: z3.string().min(1),
981
+ shadowCertificateDigest: z3.string().regex(/^[0-9a-f]{64}$/),
982
+ evidence: ReleaseEvidenceSchema,
983
+ verdict: z3.enum(["pass", "fail"]),
984
+ failedGates: z3.array(z3.enum([
985
+ "shadow-valid",
986
+ "determinism",
987
+ "crash-recovery",
988
+ "replay-equivalence",
989
+ "privacy",
990
+ "contract",
991
+ "migration",
992
+ "health",
993
+ "performance"
994
+ ])),
995
+ digest: z3.string().regex(/^[0-9a-f]{64}$/)
996
+ }).strict();
997
+
998
+ class ReleaseCertificateError extends Error {
999
+ code;
1000
+ detail;
1001
+ constructor(code, detail) {
1002
+ super(`${code}: ${detail}`);
1003
+ this.name = "ReleaseCertificateError";
1004
+ this.code = code;
1005
+ this.detail = detail;
1006
+ }
1007
+ }
1008
+ function evaluateReleaseGates(evidence, expectedShadowDigest) {
1009
+ const failed = [];
1010
+ if (evidence.shadowCertificateDigest !== expectedShadowDigest) {
1011
+ failed.push("shadow-valid");
1012
+ }
1013
+ if (evidence.determinism.runs < 1 || !evidence.determinism.allMatch) {
1014
+ failed.push("determinism");
1015
+ }
1016
+ if (evidence.crashRecovery.scenarios < 1 || !evidence.crashRecovery.allConverge) {
1017
+ failed.push("crash-recovery");
1018
+ }
1019
+ if (evidence.replayEquivalence.checks < 1 || !evidence.replayEquivalence.allEquivalent) {
1020
+ failed.push("replay-equivalence");
1021
+ }
1022
+ if (evidence.privacy.surfacesScanned < 1 || evidence.privacy.rawFindings > 0) {
1023
+ failed.push("privacy");
1024
+ }
1025
+ if (evidence.contract.roundTrips < 1 || !evidence.contract.allSettled) {
1026
+ failed.push("contract");
1027
+ }
1028
+ if (evidence.migration.cases < 1 || !evidence.migration.allSucceeded) {
1029
+ failed.push("migration");
1030
+ }
1031
+ if (evidence.health.samples < 1 || !evidence.health.bounded || !evidence.health.idempotent) {
1032
+ failed.push("health");
1033
+ }
1034
+ if (evidence.performance.samples < 1 || evidence.performance.p95Millis > evidence.performance.budgetMillis) {
1035
+ failed.push("performance");
1036
+ }
1037
+ return failed;
1038
+ }
1039
+ function canonicalReleaseCertificate(certificate) {
1040
+ const ev = certificate.evidence;
1041
+ return JSON.stringify({
1042
+ version: certificate.version,
1043
+ certificate: certificate.certificate,
1044
+ platform: certificate.platform,
1045
+ opencodeVersion: certificate.opencodeVersion,
1046
+ shadowCertificateDigest: certificate.shadowCertificateDigest,
1047
+ verdict: certificate.verdict,
1048
+ failedGates: [...certificate.failedGates],
1049
+ evidence: {
1050
+ opencodeVersion: ev.opencodeVersion,
1051
+ platform: ev.platform,
1052
+ shadowCertificateDigest: ev.shadowCertificateDigest,
1053
+ determinism: {
1054
+ runs: ev.determinism.runs,
1055
+ allMatch: ev.determinism.allMatch
1056
+ },
1057
+ crashRecovery: {
1058
+ scenarios: ev.crashRecovery.scenarios,
1059
+ allConverge: ev.crashRecovery.allConverge
1060
+ },
1061
+ replayEquivalence: {
1062
+ checks: ev.replayEquivalence.checks,
1063
+ allEquivalent: ev.replayEquivalence.allEquivalent
1064
+ },
1065
+ privacy: {
1066
+ surfacesScanned: ev.privacy.surfacesScanned,
1067
+ rawFindings: ev.privacy.rawFindings
1068
+ },
1069
+ contract: {
1070
+ roundTrips: ev.contract.roundTrips,
1071
+ allSettled: ev.contract.allSettled
1072
+ },
1073
+ migration: {
1074
+ cases: ev.migration.cases,
1075
+ allSucceeded: ev.migration.allSucceeded
1076
+ },
1077
+ health: {
1078
+ samples: ev.health.samples,
1079
+ bounded: ev.health.bounded,
1080
+ idempotent: ev.health.idempotent
1081
+ },
1082
+ performance: {
1083
+ p95Millis: ev.performance.p95Millis,
1084
+ budgetMillis: ev.performance.budgetMillis,
1085
+ samples: ev.performance.samples
1086
+ }
1087
+ }
1088
+ });
1089
+ }
1090
+ function sameReleaseGates(a, b) {
1091
+ return a.length === b.length && a.every((gate, index) => gate === b[index]);
1092
+ }
1093
+ function parseReleaseCertificate(value, opencodeVersion, expectedShadowDigest) {
1094
+ const parsed = ReleaseCertificateSchema.safeParse(value);
1095
+ if (!parsed.success) {
1096
+ const detail = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.code}`).join("; ");
1097
+ throw new ReleaseCertificateError("invalid-certificate", detail);
1098
+ }
1099
+ const certificate = parsed.data;
1100
+ const inconsistencies = [];
1101
+ if (certificate.platform !== certificate.evidence.platform) {
1102
+ inconsistencies.push("platform");
1103
+ }
1104
+ if (certificate.opencodeVersion !== certificate.evidence.opencodeVersion) {
1105
+ inconsistencies.push("opencodeVersion");
1106
+ }
1107
+ if (certificate.shadowCertificateDigest !== certificate.evidence.shadowCertificateDigest) {
1108
+ inconsistencies.push("shadowCertificateDigest");
1109
+ }
1110
+ if (inconsistencies.length > 0) {
1111
+ throw new ReleaseCertificateError("field-inconsistency", `top-level/evidence divergence: ${inconsistencies.join(", ")}`);
1112
+ }
1113
+ const failedGates = evaluateReleaseGates(certificate.evidence, expectedShadowDigest);
1114
+ const verdict = failedGates.length === 0 ? "pass" : "fail";
1115
+ if (certificate.verdict !== verdict || !sameReleaseGates(certificate.failedGates, failedGates)) {
1116
+ throw new ReleaseCertificateError("verdict-mismatch", `evidence yields "${verdict}" (${failedGates.join(",") || "no failed gate"})`);
1117
+ }
1118
+ const { digest, ...unsigned } = certificate;
1119
+ if (sha256Hex(canonicalReleaseCertificate(unsigned)) !== digest) {
1120
+ throw new ReleaseCertificateError("digest-mismatch", "certificate digest does not cover the presented evidence");
1121
+ }
1122
+ if (certificate.opencodeVersion !== opencodeVersion) {
1123
+ throw new ReleaseCertificateError("opencode-mismatch", `certified for "${certificate.opencodeVersion}", expected "${opencodeVersion}"`);
1124
+ }
1125
+ return certificate;
1126
+ }
1127
+ var ShadowDivergenceCodeSchema = z3.enum([
1128
+ "status",
1129
+ "order",
1130
+ "roles",
1131
+ "models",
1132
+ "fixes",
1133
+ "outcome"
1134
+ ]);
1135
+ var RECOMPUTED_DIVERGENCE_CODES = ["fixes"];
1136
+ var RecomputedDivergenceCodeSchema = z3.enum(RECOMPUTED_DIVERGENCE_CODES);
1137
+ var SoakPolicySchema = z3.object({
1138
+ minimumGenuineObservations: z3.number().int().positive(),
1139
+ minimumDistinctDays: z3.number().int().positive(),
1140
+ requireGenuineUsageOnAllPlatforms: z3.boolean(),
1141
+ supportedOpencodeVersion: z3.string().min(1),
1142
+ criticalDivergenceCodes: z3.array(RecomputedDivergenceCodeSchema).min(1)
1143
+ }).strict();
1144
+ var DEFAULT_SOAK_POLICY = {
1145
+ minimumGenuineObservations: 100,
1146
+ minimumDistinctDays: 14,
1147
+ requireGenuineUsageOnAllPlatforms: true,
1148
+ supportedOpencodeVersion: "1.17.13",
1149
+ criticalDivergenceCodes: ["fixes"]
1150
+ };
1151
+ var SoakPlatformEnumSchema = z3.enum(["linux", "win32", "darwin"]);
1152
+ var SoakPrivacyPairEvidenceSchema = z3.object({
1153
+ surfacesScanned: z3.number().int().nonnegative(),
1154
+ rawFindings: z3.number().int().nonnegative()
1155
+ }).strict();
1156
+ var SoakDuplicateEffectPairEvidenceSchema = z3.object({
1157
+ effectsExamined: z3.number().int().nonnegative(),
1158
+ duplicatesFound: z3.number().int().nonnegative()
1159
+ }).strict();
1160
+ var SoakModelVerificationPairEvidenceSchema = z3.object({
1161
+ nodesChecked: z3.number().int().nonnegative(),
1162
+ unverified: z3.number().int().nonnegative(),
1163
+ mismatched: z3.number().int().nonnegative()
1164
+ }).strict();
1165
+ var SoakEvidenceSchema = z3.object({
1166
+ totalObservations: z3.number().int().nonnegative(),
1167
+ genuineUsageObservations: z3.number().int().nonnegative(),
1168
+ ciSyntheticObservations: z3.number().int().nonnegative(),
1169
+ recorderCount: z3.number().int().nonnegative(),
1170
+ firstTimestamp: z3.string().datetime().nullable(),
1171
+ lastTimestamp: z3.string().datetime().nullable(),
1172
+ distinctDays: z3.number().int().nonnegative(),
1173
+ criticalDivergences: z3.number().int().nonnegative().nullable(),
1174
+ privacy: SoakPrivacyPairEvidenceSchema.nullable(),
1175
+ duplicateEffects: SoakDuplicateEffectPairEvidenceSchema.nullable(),
1176
+ modelVerification: SoakModelVerificationPairEvidenceSchema.nullable(),
1177
+ platformsGenuine: z3.array(SoakPlatformEnumSchema),
1178
+ platformsSynthetic: z3.array(SoakPlatformEnumSchema),
1179
+ opencodeVersionsObserved: z3.array(z3.string().min(1)),
1180
+ invalidChains: z3.number().int().nonnegative()
1181
+ }).strict();
1182
+ var SoakCertificateSchema = z3.object({
1183
+ version: z3.literal(1),
1184
+ certificate: z3.literal("graph-soak"),
1185
+ trustBoundary: z3.literal("contributors"),
1186
+ policy: SoakPolicySchema,
1187
+ evidence: SoakEvidenceSchema,
1188
+ verdict: z3.enum(["pass", "fail"]),
1189
+ failedGates: z3.array(z3.enum([
1190
+ "minimum-observations",
1191
+ "minimum-duration",
1192
+ "chain-integrity",
1193
+ "zero-critical-divergence",
1194
+ "zero-privacy-breach",
1195
+ "zero-duplicate-effect",
1196
+ "zero-model-mismatch",
1197
+ "os-diversity",
1198
+ "opencode-pin"
1199
+ ])),
1200
+ ledgerDigest: z3.string().regex(/^[0-9a-f]{64}$/),
1201
+ digest: z3.string().regex(/^[0-9a-f]{64}$/)
1202
+ }).strict();
1203
+
1204
+ class SoakCertificateError extends Error {
1205
+ code;
1206
+ detail;
1207
+ constructor(code, detail) {
1208
+ super(`${code}: ${detail}`);
1209
+ this.name = "SoakCertificateError";
1210
+ this.code = code;
1211
+ this.detail = detail;
1212
+ }
1213
+ }
1214
+ function evaluateSoakGates(evidence, policy) {
1215
+ const failed = [];
1216
+ if (evidence.genuineUsageObservations < policy.minimumGenuineObservations) {
1217
+ failed.push("minimum-observations");
1218
+ }
1219
+ if (evidence.distinctDays < policy.minimumDistinctDays) {
1220
+ failed.push("minimum-duration");
1221
+ }
1222
+ if (evidence.invalidChains > 0) {
1223
+ failed.push("chain-integrity");
1224
+ }
1225
+ if (evidence.criticalDivergences === null || evidence.criticalDivergences > 0) {
1226
+ failed.push("zero-critical-divergence");
1227
+ }
1228
+ if (evidence.privacy === null || evidence.privacy.surfacesScanned < 1 || evidence.privacy.rawFindings > 0) {
1229
+ failed.push("zero-privacy-breach");
1230
+ }
1231
+ if (evidence.duplicateEffects === null || evidence.duplicateEffects.effectsExamined < 1 || evidence.duplicateEffects.duplicatesFound > 0) {
1232
+ failed.push("zero-duplicate-effect");
1233
+ }
1234
+ if (evidence.modelVerification === null || evidence.modelVerification.nodesChecked < 1 || evidence.modelVerification.mismatched > 0) {
1235
+ failed.push("zero-model-mismatch");
1236
+ }
1237
+ if (policy.requireGenuineUsageOnAllPlatforms) {
1238
+ if (!evidence.platformsGenuine.includes("linux") || !evidence.platformsGenuine.includes("win32")) {
1239
+ failed.push("os-diversity");
1240
+ }
1241
+ } else {
1242
+ const allPlatforms = new Set([
1243
+ ...evidence.platformsGenuine,
1244
+ ...evidence.platformsSynthetic
1245
+ ]);
1246
+ if (!allPlatforms.has("linux") || !allPlatforms.has("win32") || evidence.platformsGenuine.length < 1) {
1247
+ failed.push("os-diversity");
1248
+ }
1249
+ }
1250
+ if (!evidence.opencodeVersionsObserved.includes(policy.supportedOpencodeVersion)) {
1251
+ failed.push("opencode-pin");
1252
+ }
1253
+ return failed;
1254
+ }
1255
+ function canonicalSoakCertificate(certificate) {
1256
+ const ev = certificate.evidence;
1257
+ const pol = certificate.policy;
1258
+ return JSON.stringify({
1259
+ version: certificate.version,
1260
+ certificate: certificate.certificate,
1261
+ trustBoundary: certificate.trustBoundary,
1262
+ policy: {
1263
+ minimumGenuineObservations: pol.minimumGenuineObservations,
1264
+ minimumDistinctDays: pol.minimumDistinctDays,
1265
+ requireGenuineUsageOnAllPlatforms: pol.requireGenuineUsageOnAllPlatforms,
1266
+ supportedOpencodeVersion: pol.supportedOpencodeVersion,
1267
+ criticalDivergenceCodes: [...pol.criticalDivergenceCodes].sort()
1268
+ },
1269
+ verdict: certificate.verdict,
1270
+ failedGates: [...certificate.failedGates],
1271
+ ledgerDigest: certificate.ledgerDigest,
1272
+ evidence: {
1273
+ totalObservations: ev.totalObservations,
1274
+ genuineUsageObservations: ev.genuineUsageObservations,
1275
+ ciSyntheticObservations: ev.ciSyntheticObservations,
1276
+ recorderCount: ev.recorderCount,
1277
+ firstTimestamp: ev.firstTimestamp,
1278
+ lastTimestamp: ev.lastTimestamp,
1279
+ distinctDays: ev.distinctDays,
1280
+ criticalDivergences: ev.criticalDivergences,
1281
+ privacy: ev.privacy,
1282
+ duplicateEffects: ev.duplicateEffects,
1283
+ modelVerification: ev.modelVerification,
1284
+ platformsGenuine: [...ev.platformsGenuine].sort(),
1285
+ platformsSynthetic: [...ev.platformsSynthetic].sort(),
1286
+ opencodeVersionsObserved: [...ev.opencodeVersionsObserved].sort(),
1287
+ invalidChains: ev.invalidChains
1288
+ }
1289
+ });
1290
+ }
1291
+ function sameSoakGates(a, b) {
1292
+ return a.length === b.length && a.every((gate, index) => gate === b[index]);
1293
+ }
1294
+ function sameSoakPolicy(a, b) {
1295
+ return a.minimumGenuineObservations === b.minimumGenuineObservations && a.minimumDistinctDays === b.minimumDistinctDays && a.requireGenuineUsageOnAllPlatforms === b.requireGenuineUsageOnAllPlatforms && a.supportedOpencodeVersion === b.supportedOpencodeVersion && a.criticalDivergenceCodes.length === b.criticalDivergenceCodes.length && [...a.criticalDivergenceCodes].sort().every((code, i) => code === [...b.criticalDivergenceCodes].sort()[i]);
1296
+ }
1297
+ function parseSoakCertificate(value, expectedPolicy, expectedLedgerDigest) {
1298
+ const parsed = SoakCertificateSchema.safeParse(value);
1299
+ if (!parsed.success) {
1300
+ const detail = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.code}`).join("; ");
1301
+ throw new SoakCertificateError("invalid-certificate", detail);
1302
+ }
1303
+ const certificate = parsed.data;
1304
+ if (!sameSoakPolicy(certificate.policy, expectedPolicy)) {
1305
+ throw new SoakCertificateError("policy-mismatch", "embedded policy diverges from the expected policy");
1306
+ }
1307
+ const failedGates = evaluateSoakGates(certificate.evidence, certificate.policy);
1308
+ const verdict = failedGates.length === 0 ? "pass" : "fail";
1309
+ if (certificate.verdict !== verdict || !sameSoakGates(certificate.failedGates, failedGates)) {
1310
+ throw new SoakCertificateError("verdict-mismatch", `evidence yields "${verdict}" (${failedGates.join(",") || "no failed gate"})`);
1311
+ }
1312
+ const { digest, ...unsigned } = certificate;
1313
+ if (sha256Hex(canonicalSoakCertificate(unsigned)) !== digest) {
1314
+ throw new SoakCertificateError("digest-mismatch", "certificate digest does not cover the presented evidence");
1315
+ }
1316
+ if (certificate.ledgerDigest !== expectedLedgerDigest) {
1317
+ throw new SoakCertificateError("ledger-mismatch", "ledger digest does not match the evidence the caller holds");
1318
+ }
1319
+ return certificate;
1320
+ }
1321
+
549
1322
  // src/local/openai-compatible.ts
550
1323
  function normalizeBaseURL(baseURL) {
551
1324
  return baseURL.replace(/\/+$/, "");
@@ -562,8 +1335,8 @@ function errorMessage(error) {
562
1335
  }
563
1336
  return String(error);
564
1337
  }
565
- async function listOpenAICompatibleModels(baseURL, fetch) {
566
- const response = await fetch(modelsEndpoint(baseURL), {
1338
+ async function listOpenAICompatibleModels(baseURL, fetch2) {
1339
+ const response = await fetch2(modelsEndpoint(baseURL), {
567
1340
  method: "GET",
568
1341
  headers: { accept: "application/json" }
569
1342
  });
@@ -986,123 +1759,123 @@ function createAvailabilityCache(options) {
986
1759
  }
987
1760
 
988
1761
  // src/telemetry/events.ts
989
- import { z as z3 } from "zod";
1762
+ import { z as z5 } from "zod";
990
1763
 
991
1764
  // src/capabilities/types.ts
992
- import { z as z2 } from "zod";
993
- var CapabilityTierSchema = z2.union([
994
- z2.literal(0),
995
- z2.literal(1),
996
- z2.literal(2),
997
- z2.literal(3),
998
- z2.literal(4),
999
- z2.literal(5)
1765
+ import { z as z4 } from "zod";
1766
+ var CapabilityTierSchema = z4.union([
1767
+ z4.literal(0),
1768
+ z4.literal(1),
1769
+ z4.literal(2),
1770
+ z4.literal(3),
1771
+ z4.literal(4),
1772
+ z4.literal(5)
1000
1773
  ]);
1001
- var ComplexityTierSchema = z2.enum([
1774
+ var ComplexityTierSchema = z4.enum([
1002
1775
  "trivial",
1003
1776
  "simple",
1004
1777
  "moderate",
1005
1778
  "hard"
1006
1779
  ]);
1007
- var ModelCapabilityProfileSchema = z2.object({
1008
- ref: z2.object({
1009
- providerID: z2.string().min(1),
1010
- modelID: z2.string().min(1)
1780
+ var ModelCapabilityProfileSchema = z4.object({
1781
+ ref: z4.object({
1782
+ providerID: z4.string().min(1),
1783
+ modelID: z4.string().min(1)
1011
1784
  }),
1012
- kind: z2.enum(["local", "frontier", "router"]),
1013
- contextWindow: z2.number().int().positive(),
1014
- maxOutputTokens: z2.number().int().positive(),
1015
- supportsToolCalling: z2.boolean(),
1016
- supportsVision: z2.boolean(),
1785
+ kind: z4.enum(["local", "frontier", "router"]),
1786
+ contextWindow: z4.number().int().positive(),
1787
+ maxOutputTokens: z4.number().int().positive(),
1788
+ supportsToolCalling: z4.boolean(),
1789
+ supportsVision: z4.boolean(),
1017
1790
  reasoningTier: CapabilityTierSchema,
1018
1791
  codeQualityTier: CapabilityTierSchema,
1019
- costPer1M: z2.object({
1020
- inputUSD: z2.number().min(0),
1021
- outputUSD: z2.number().min(0)
1792
+ costPer1M: z4.object({
1793
+ inputUSD: z4.number().min(0),
1794
+ outputUSD: z4.number().min(0)
1022
1795
  }),
1023
- availability: z2.enum(["available", "degraded", "unavailable"])
1796
+ availability: z4.enum(["available", "degraded", "unavailable"])
1024
1797
  });
1025
1798
 
1026
1799
  // src/telemetry/events.ts
1027
1800
  var EVENT_SCHEMA_VERSION = 1;
1028
- var EventBaseSchema = z3.object({
1029
- v: z3.literal(EVENT_SCHEMA_VERSION),
1030
- ts: z3.number().finite(),
1031
- sessionID: z3.string().min(1)
1801
+ var EventBaseSchema = z5.object({
1802
+ v: z5.literal(EVENT_SCHEMA_VERSION),
1803
+ ts: z5.number().finite(),
1804
+ sessionID: z5.string().min(1)
1032
1805
  });
1033
1806
  var RouteEventSchema = EventBaseSchema.extend({
1034
- type: z3.literal("route"),
1035
- promptHash: z3.string().min(1),
1036
- promptChars: z3.number().int().min(0),
1807
+ type: z5.literal("route"),
1808
+ promptHash: z5.string().min(1),
1809
+ promptChars: z5.number().int().min(0),
1037
1810
  tier: ComplexityTierSchema,
1038
- routeKind: z3.enum(["local", "frontier"]),
1811
+ routeKind: z5.enum(["local", "frontier"]),
1039
1812
  selected: ModelRefSchema,
1040
- rationale: z3.string(),
1041
- estimatedCostUSD: z3.number().finite().min(0),
1042
- baselineCostUSD: z3.number().finite().min(0),
1043
- estimatedSavingsUSD: z3.number().finite(),
1044
- budgetAction: z3.string().min(1),
1045
- tokensIn: z3.number().int().min(0).optional(),
1046
- tokensOut: z3.number().int().min(0).optional(),
1047
- decisionID: z3.string().min(1).optional(),
1048
- agent: z3.string().min(1).optional(),
1049
- success: z3.boolean().optional(),
1050
- batchID: z3.string().min(1).optional(),
1051
- taskID: z3.string().min(1).optional(),
1052
- failureReason: z3.string().min(1).optional(),
1053
- failureStage: z3.string().min(1).optional()
1813
+ rationale: z5.string(),
1814
+ estimatedCostUSD: z5.number().finite().min(0),
1815
+ baselineCostUSD: z5.number().finite().min(0),
1816
+ estimatedSavingsUSD: z5.number().finite(),
1817
+ budgetAction: z5.string().min(1),
1818
+ tokensIn: z5.number().int().min(0).optional(),
1819
+ tokensOut: z5.number().int().min(0).optional(),
1820
+ decisionID: z5.string().min(1).optional(),
1821
+ agent: z5.string().min(1).optional(),
1822
+ success: z5.boolean().optional(),
1823
+ batchID: z5.string().min(1).optional(),
1824
+ taskID: z5.string().min(1).optional(),
1825
+ failureReason: z5.string().min(1).optional(),
1826
+ failureStage: z5.string().min(1).optional()
1054
1827
  });
1055
1828
  var MessageEventSchema = EventBaseSchema.extend({
1056
- type: z3.literal("message"),
1057
- providerID: z3.string().min(1),
1058
- modelID: z3.string().min(1),
1059
- agent: z3.string().min(1).optional(),
1060
- mode: z3.string().min(1).optional(),
1061
- messageID: z3.string().min(1).optional(),
1062
- costUSD: z3.number().finite().min(0),
1063
- tokensIn: z3.number().int().min(0),
1064
- tokensOut: z3.number().int().min(0),
1065
- tokensReasoning: z3.number().int().min(0),
1066
- tokensCacheRead: z3.number().int().min(0),
1067
- tokensCacheWrite: z3.number().int().min(0),
1068
- durationMs: z3.number().int().min(0)
1829
+ type: z5.literal("message"),
1830
+ providerID: z5.string().min(1),
1831
+ modelID: z5.string().min(1),
1832
+ agent: z5.string().min(1).optional(),
1833
+ mode: z5.string().min(1).optional(),
1834
+ messageID: z5.string().min(1).optional(),
1835
+ costUSD: z5.number().finite().min(0),
1836
+ tokensIn: z5.number().int().min(0),
1837
+ tokensOut: z5.number().int().min(0),
1838
+ tokensReasoning: z5.number().int().min(0),
1839
+ tokensCacheRead: z5.number().int().min(0),
1840
+ tokensCacheWrite: z5.number().int().min(0),
1841
+ durationMs: z5.number().int().min(0)
1069
1842
  });
1070
1843
  var ToolcallEventSchema = EventBaseSchema.extend({
1071
- type: z3.literal("toolcall"),
1072
- tool: z3.string().min(1),
1073
- callID: z3.string().min(1),
1074
- agent: z3.string().min(1).optional(),
1075
- durationMs: z3.number().int().min(0),
1076
- ok: z3.boolean(),
1077
- title: z3.string().optional()
1844
+ type: z5.literal("toolcall"),
1845
+ tool: z5.string().min(1),
1846
+ callID: z5.string().min(1),
1847
+ agent: z5.string().min(1).optional(),
1848
+ durationMs: z5.number().int().min(0),
1849
+ ok: z5.boolean(),
1850
+ title: z5.string().optional()
1078
1851
  });
1079
1852
  var MeetingEventSchema = EventBaseSchema.extend({
1080
- type: z3.literal("meeting"),
1081
- batchID: z3.string().min(1),
1082
- purpose: z3.string().optional(),
1083
- roles: z3.array(z3.string().min(1)),
1084
- decisionIDs: z3.array(z3.string().min(1)).optional()
1853
+ type: z5.literal("meeting"),
1854
+ batchID: z5.string().min(1),
1855
+ purpose: z5.string().optional(),
1856
+ roles: z5.array(z5.string().min(1)),
1857
+ decisionIDs: z5.array(z5.string().min(1)).optional()
1085
1858
  });
1086
1859
  var DecisionEventSchema = EventBaseSchema.extend({
1087
- type: z3.literal("decision"),
1088
- agent: z3.string().min(1).optional(),
1089
- summary: z3.string().min(1),
1090
- tags: z3.array(z3.string().min(1)).optional()
1860
+ type: z5.literal("decision"),
1861
+ agent: z5.string().min(1).optional(),
1862
+ summary: z5.string().min(1),
1863
+ tags: z5.array(z5.string().min(1)).optional()
1091
1864
  });
1092
1865
  var ActivityEventSchema = EventBaseSchema.extend({
1093
- type: z3.literal("activity"),
1094
- kind: z3.enum(["route", "agent", "decision", "commit"]),
1095
- agent: z3.string().min(1).optional(),
1096
- summary: z3.string().min(1)
1866
+ type: z5.literal("activity"),
1867
+ kind: z5.enum(["route", "agent", "decision", "commit"]),
1868
+ agent: z5.string().min(1).optional(),
1869
+ summary: z5.string().min(1)
1097
1870
  });
1098
1871
  var SessionEndpointEventSchema = EventBaseSchema.extend({
1099
- type: z3.literal("session-endpoint"),
1100
- url: z3.string().url(),
1101
- directory: z3.string().min(1).optional(),
1102
- worktree: z3.string().min(1).optional(),
1103
- title: z3.string().min(1).optional()
1872
+ type: z5.literal("session-endpoint"),
1873
+ url: z5.string().url(),
1874
+ directory: z5.string().min(1).optional(),
1875
+ worktree: z5.string().min(1).optional(),
1876
+ title: z5.string().min(1).optional()
1104
1877
  });
1105
- var OpenTeamEventSchema = z3.discriminatedUnion("type", [
1878
+ var OpenTeamEventSchema = z5.discriminatedUnion("type", [
1106
1879
  RouteEventSchema,
1107
1880
  MessageEventSchema,
1108
1881
  ToolcallEventSchema,
@@ -1479,6 +2252,15 @@ function renderDoctor(input) {
1479
2252
  if (reachable === 0) {
1480
2253
  lines.push(" ⚠ Ningún runtime local alcanzable: todo el tráfico irá a frontier.");
1481
2254
  }
2255
+ if (input.graphHealth !== undefined) {
2256
+ const h = input.graphHealth;
2257
+ lines.push(" graph health:");
2258
+ lines.push(` recuperación requerida: ${h.recoveryRequired ? "sí" : "no"}`);
2259
+ lines.push(` efectos desconocidos: ${h.unknownEffects}`);
2260
+ lines.push(` completaciones tardías: ${h.staleCompletions}`);
2261
+ lines.push(` conflictos: ${h.conflicts}`);
2262
+ lines.push(` kill switch: ${h.killSwitch ? "activo" : "inactivo"}`);
2263
+ }
1482
2264
  return lines.join(`
1483
2265
  `);
1484
2266
  }
@@ -1927,42 +2709,254 @@ function createCommandTool(deps) {
1927
2709
  });
1928
2710
  }
1929
2711
 
1930
- // src/capabilities/budget.ts
1931
- var WARNING_UTILIZATION = 0.8;
1932
- function projectedSpend(current, estimatedCostUSD) {
1933
- return Math.max(0, current) + Math.max(0, estimatedCostUSD);
2712
+ // src/plugin/graphTool.ts
2713
+ import { tool as tool2 } from "@opencode-ai/plugin";
2714
+
2715
+ // src/graph/schema.ts
2716
+ import { z as z7 } from "zod";
2717
+
2718
+ // src/context/schema.ts
2719
+ import { z as z6 } from "zod";
2720
+ var PrivacyClassSchema = z6.enum(["public", "internal", "sensitive"]);
2721
+ var Sha256Schema2 = z6.string().regex(/^[0-9a-f]{64}$/, "expected lowercase hex sha-256");
2722
+ var IdentifierSchema = z6.string().min(1).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/, "invalid identifier");
2723
+ var TaskAnchorSchema = z6.object({
2724
+ taskID: IdentifierSchema,
2725
+ specDigest: Sha256Schema2
2726
+ }).strict();
2727
+ var ContextRefSchema = z6.object({
2728
+ uri: z6.string().min(1).max(1024),
2729
+ sha256: Sha256Schema2,
2730
+ bytes: z6.number().int().nonnegative(),
2731
+ mediaType: z6.string().min(1).max(128).optional()
2732
+ }).strict();
2733
+ var ManifestEntrySchema = z6.object({
2734
+ label: IdentifierSchema,
2735
+ sha256: Sha256Schema2,
2736
+ bytes: z6.number().int().nonnegative(),
2737
+ ref: ContextRefSchema.optional()
2738
+ }).strict();
2739
+ var ArtifactManifestSchema = z6.object({
2740
+ version: z6.literal(1),
2741
+ taskID: IdentifierSchema,
2742
+ privacyClass: PrivacyClassSchema,
2743
+ entries: z6.array(ManifestEntrySchema)
2744
+ }).strict();
2745
+ var NodeContextContractSchema = z6.object({
2746
+ version: z6.literal(1),
2747
+ refs: z6.array(ContextRefSchema),
2748
+ privacyClass: PrivacyClassSchema,
2749
+ maxInputTokens: z6.number().int().positive().optional()
2750
+ }).strict();
2751
+
2752
+ // src/graph/schema.ts
2753
+ var GRAPH_SPEC_VERSION = 1;
2754
+ var GRAPH_EVENT_VERSION = 1;
2755
+ var NodeIDSchema = z7.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/, "invalid node id");
2756
+ var NodeRoleSchema = z7.enum(["implementer", "reviewer"]);
2757
+ var OperationIDSchema = z7.string().regex(/^[0-9a-f]{8,64}$/, "invalid operation id");
2758
+ var AttemptIDSchema = z7.string().regex(/^[0-9a-f]{8,64}$/, "invalid attempt id");
2759
+ var ErrorClassSchema = z7.enum([
2760
+ "transient",
2761
+ "permanent",
2762
+ "timeout",
2763
+ "cancelled",
2764
+ "unknown"
2765
+ ]);
2766
+ var GraphNodeSchema = z7.object({
2767
+ id: NodeIDSchema,
2768
+ role: NodeRoleSchema,
2769
+ dependsOn: z7.array(NodeIDSchema).default([]),
2770
+ anchor: TaskAnchorSchema,
2771
+ reviews: NodeIDSchema.optional()
2772
+ }).strict();
2773
+ var GraphSpecV1Schema = z7.object({
2774
+ version: z7.literal(GRAPH_SPEC_VERSION),
2775
+ runID: NodeIDSchema,
2776
+ nodes: z7.array(GraphNodeSchema).min(1)
2777
+ }).strict();
2778
+ var eventEnvelope = {
2779
+ v: z7.literal(GRAPH_EVENT_VERSION),
2780
+ seq: z7.number().int().nonnegative(),
2781
+ runID: NodeIDSchema
2782
+ };
2783
+ var RunStartedEventSchema = z7.object({
2784
+ ...eventEnvelope,
2785
+ type: z7.literal("run.started"),
2786
+ specDigest: Sha256Schema2
2787
+ }).strict();
2788
+ var NodeDispatchedEventSchema = z7.object({
2789
+ ...eventEnvelope,
2790
+ type: z7.literal("node.dispatched"),
2791
+ nodeID: NodeIDSchema,
2792
+ operationID: OperationIDSchema,
2793
+ attemptID: AttemptIDSchema
2794
+ }).strict();
2795
+ var NodeSucceededEventSchema = z7.object({
2796
+ ...eventEnvelope,
2797
+ type: z7.literal("node.succeeded"),
2798
+ nodeID: NodeIDSchema,
2799
+ operationID: OperationIDSchema,
2800
+ attemptID: AttemptIDSchema,
2801
+ artifact: ContextRefSchema
2802
+ }).strict();
2803
+ var NodeFailedEventSchema = z7.object({
2804
+ ...eventEnvelope,
2805
+ type: z7.literal("node.failed"),
2806
+ nodeID: NodeIDSchema,
2807
+ operationID: OperationIDSchema,
2808
+ attemptID: AttemptIDSchema,
2809
+ errorClass: ErrorClassSchema
2810
+ }).strict();
2811
+ var NodeCancelledEventSchema = z7.object({
2812
+ ...eventEnvelope,
2813
+ type: z7.literal("node.cancelled"),
2814
+ nodeID: NodeIDSchema
2815
+ }).strict();
2816
+ var RunCompletedEventSchema = z7.object({ ...eventEnvelope, type: z7.literal("run.completed") }).strict();
2817
+ var RunFailedEventSchema = z7.object({ ...eventEnvelope, type: z7.literal("run.failed") }).strict();
2818
+ var RunCancelledEventSchema = z7.object({ ...eventEnvelope, type: z7.literal("run.cancelled") }).strict();
2819
+ var GraphEventV1Schema = z7.discriminatedUnion("type", [
2820
+ RunStartedEventSchema,
2821
+ NodeDispatchedEventSchema,
2822
+ NodeSucceededEventSchema,
2823
+ NodeFailedEventSchema,
2824
+ NodeCancelledEventSchema,
2825
+ RunCompletedEventSchema,
2826
+ RunFailedEventSchema,
2827
+ RunCancelledEventSchema
2828
+ ]);
2829
+
2830
+ // src/graph/ids.ts
2831
+ var NUL = "\x00";
2832
+ function operationID(runID, nodeID, revision = 0) {
2833
+ return sha256Hex(`op${NUL}${runID}${NUL}${nodeID}${NUL}${revision}`).slice(0, 32);
2834
+ }
2835
+ function attemptID(operation, attempt) {
2836
+ return sha256Hex(`att${NUL}${operation}${NUL}${attempt}`).slice(0, 32);
2837
+ }
2838
+ function canonicalSpec(spec) {
2839
+ const nodes = spec.nodes.map((node) => ({
2840
+ id: node.id,
2841
+ role: node.role,
2842
+ dependsOn: [...node.dependsOn],
2843
+ anchor: { taskID: node.anchor.taskID, specDigest: node.anchor.specDigest },
2844
+ ...node.reviews === undefined ? {} : { reviews: node.reviews }
2845
+ }));
2846
+ return JSON.stringify({
2847
+ version: spec.version,
2848
+ runID: spec.runID,
2849
+ nodes
2850
+ });
1934
2851
  }
1935
- function budgetExceeded(projected, limit) {
1936
- return limit !== undefined && projected >= limit;
2852
+ function graphSpecDigest(spec) {
2853
+ return sha256Hex(canonicalSpec(spec));
1937
2854
  }
1938
- function budgetNearLimit(projected, limit) {
1939
- return limit !== undefined && projected >= limit * WARNING_UTILIZATION && projected < limit;
2855
+
2856
+ // src/graph/validate.ts
2857
+ class GraphValidationError extends Error {
2858
+ code;
2859
+ detail;
2860
+ constructor(code, detail) {
2861
+ super(`${code}: ${detail}`);
2862
+ this.name = "GraphValidationError";
2863
+ this.code = code;
2864
+ this.detail = detail;
2865
+ }
1940
2866
  }
1941
- function exhaustedDecision(budgets, reason) {
1942
- return {
1943
- action: budgets.hardStopOnBudgetExhaustion ? "blockFrontier" : "forceLocal",
1944
- reason
1945
- };
2867
+ function indexNodes(nodes) {
2868
+ const byID = new Map;
2869
+ for (const node of nodes) {
2870
+ if (byID.has(node.id)) {
2871
+ throw new GraphValidationError("duplicate-node", node.id);
2872
+ }
2873
+ byID.set(node.id, node);
2874
+ }
2875
+ return byID;
1946
2876
  }
1947
- function evaluateBudget(state, estimatedCostUSD, budgets) {
1948
- const projectedSessionUSD = projectedSpend(state.sessionUSD, estimatedCostUSD);
1949
- const projectedMonthlyUSD = budgets.monthlyUSD === undefined ? undefined : projectedSpend(state.monthlyUSD ?? 0, estimatedCostUSD);
1950
- if (budgetExceeded(projectedSessionUSD, budgets.sessionUSD)) {
1951
- return exhaustedDecision(budgets, "session-budget-exhausted");
2877
+ function assertDependenciesExist(nodes, byID) {
2878
+ for (const node of nodes) {
2879
+ for (const dep of node.dependsOn) {
2880
+ if (!byID.has(dep)) {
2881
+ throw new GraphValidationError("missing-dependency", `${node.id} -> ${dep}`);
2882
+ }
2883
+ }
1952
2884
  }
1953
- if (budgetExceeded(projectedMonthlyUSD ?? 0, budgets.monthlyUSD)) {
1954
- return exhaustedDecision(budgets, "monthly-budget-exhausted");
2885
+ }
2886
+ function assertAcyclic(nodes, byID) {
2887
+ const state = new Map;
2888
+ const visit = (id) => {
2889
+ const current = state.get(id);
2890
+ if (current === "done") {
2891
+ return;
2892
+ }
2893
+ if (current === "visiting") {
2894
+ throw new GraphValidationError("cycle", id);
2895
+ }
2896
+ state.set(id, "visiting");
2897
+ const node = byID.get(id);
2898
+ for (const dep of node.dependsOn) {
2899
+ visit(dep);
2900
+ }
2901
+ state.set(id, "done");
2902
+ };
2903
+ for (const node of nodes) {
2904
+ visit(node.id);
1955
2905
  }
1956
- if (state.frontierTokens !== undefined && budgets.frontierTokensPerSession !== undefined && state.frontierTokens >= budgets.frontierTokensPerSession) {
1957
- return exhaustedDecision(budgets, "frontier-token-budget-exhausted");
2906
+ }
2907
+ function assertReviewRules(nodes, byID) {
2908
+ for (const node of nodes) {
2909
+ if (node.role === "implementer") {
2910
+ if (node.reviews !== undefined) {
2911
+ throw new GraphValidationError("implementer-has-review", node.id);
2912
+ }
2913
+ continue;
2914
+ }
2915
+ if (node.reviews === undefined) {
2916
+ throw new GraphValidationError("reviewer-missing-review", node.id);
2917
+ }
2918
+ if (node.reviews === node.id) {
2919
+ throw new GraphValidationError("self-review", node.id);
2920
+ }
2921
+ const target = byID.get(node.reviews);
2922
+ if (target === undefined || target.role !== "implementer") {
2923
+ throw new GraphValidationError("invalid-review-target", `${node.id} -> ${node.reviews}`);
2924
+ }
1958
2925
  }
1959
- if (budgetNearLimit(projectedSessionUSD, budgets.sessionUSD)) {
1960
- return { action: "warn", reason: "session-budget-near-limit" };
2926
+ }
2927
+ function assertConnected(nodes) {
2928
+ const adjacency = new Map;
2929
+ for (const node of nodes) {
2930
+ adjacency.set(node.id, adjacency.get(node.id) ?? new Set);
2931
+ for (const dep of node.dependsOn) {
2932
+ adjacency.get(node.id)?.add(dep);
2933
+ const back = adjacency.get(dep) ?? new Set;
2934
+ back.add(node.id);
2935
+ adjacency.set(dep, back);
2936
+ }
1961
2937
  }
1962
- if (budgetNearLimit(projectedMonthlyUSD ?? 0, budgets.monthlyUSD)) {
1963
- return { action: "warn", reason: "monthly-budget-near-limit" };
2938
+ const root = nodes[0];
2939
+ const seen = new Set([root.id]);
2940
+ const queue = [root.id];
2941
+ while (queue.length > 0) {
2942
+ const id = queue.shift();
2943
+ for (const neighbor of adjacency.get(id) ?? new Set) {
2944
+ if (!seen.has(neighbor)) {
2945
+ seen.add(neighbor);
2946
+ queue.push(neighbor);
2947
+ }
2948
+ }
2949
+ }
2950
+ if (seen.size !== nodes.length) {
2951
+ throw new GraphValidationError("disconnected", `${seen.size}/${nodes.length} reachable`);
1964
2952
  }
1965
- return { action: "ok" };
2953
+ }
2954
+ function validateGraphSpec(spec) {
2955
+ const byID = indexNodes(spec.nodes);
2956
+ assertDependenciesExist(spec.nodes, byID);
2957
+ assertAcyclic(spec.nodes, byID);
2958
+ assertReviewRules(spec.nodes, byID);
2959
+ assertConnected(spec.nodes);
1966
2960
  }
1967
2961
 
1968
2962
  // src/capabilities/classify.ts
@@ -2039,24 +3033,62 @@ function classifyHeuristic(task) {
2039
3033
  };
2040
3034
  }
2041
3035
 
2042
- // src/router/modelSelection.ts
2043
- function modelKey2(model) {
2044
- return `${model.providerID}/${model.modelID}`;
3036
+ // src/capabilities/budget.ts
3037
+ var WARNING_UTILIZATION = 0.8;
3038
+ function projectedSpend(current, estimatedCostUSD) {
3039
+ return Math.max(0, current) + Math.max(0, estimatedCostUSD);
2045
3040
  }
2046
- function sameModel(left, right) {
2047
- return left.providerID === right.providerID && left.modelID === right.modelID;
3041
+ function budgetExceeded(projected, limit) {
3042
+ return limit !== undefined && projected >= limit;
2048
3043
  }
2049
- function compareModels(left, right) {
2050
- return modelKey2(left).localeCompare(modelKey2(right));
3044
+ function budgetNearLimit(projected, limit) {
3045
+ return limit !== undefined && projected >= limit * WARNING_UTILIZATION && projected < limit;
2051
3046
  }
2052
- function uniqueModelSelections(models) {
2053
- const byKey = new Map;
2054
- for (const model of models) {
2055
- byKey.set(modelKey2(model), model);
2056
- }
2057
- return [...byKey.values()].sort(compareModels);
3047
+ function exhaustedDecision(budgets, reason) {
3048
+ return {
3049
+ action: budgets.hardStopOnBudgetExhaustion ? "blockFrontier" : "forceLocal",
3050
+ reason
3051
+ };
2058
3052
  }
2059
- function localDefault(config) {
3053
+ function evaluateBudget(state, estimatedCostUSD, budgets) {
3054
+ const projectedSessionUSD = projectedSpend(state.sessionUSD, estimatedCostUSD);
3055
+ const projectedMonthlyUSD = budgets.monthlyUSD === undefined ? undefined : projectedSpend(state.monthlyUSD ?? 0, estimatedCostUSD);
3056
+ if (budgetExceeded(projectedSessionUSD, budgets.sessionUSD)) {
3057
+ return exhaustedDecision(budgets, "session-budget-exhausted");
3058
+ }
3059
+ if (budgetExceeded(projectedMonthlyUSD ?? 0, budgets.monthlyUSD)) {
3060
+ return exhaustedDecision(budgets, "monthly-budget-exhausted");
3061
+ }
3062
+ if (state.frontierTokens !== undefined && budgets.frontierTokensPerSession !== undefined && state.frontierTokens >= budgets.frontierTokensPerSession) {
3063
+ return exhaustedDecision(budgets, "frontier-token-budget-exhausted");
3064
+ }
3065
+ if (budgetNearLimit(projectedSessionUSD, budgets.sessionUSD)) {
3066
+ return { action: "warn", reason: "session-budget-near-limit" };
3067
+ }
3068
+ if (budgetNearLimit(projectedMonthlyUSD ?? 0, budgets.monthlyUSD)) {
3069
+ return { action: "warn", reason: "monthly-budget-near-limit" };
3070
+ }
3071
+ return { action: "ok" };
3072
+ }
3073
+
3074
+ // src/router/modelSelection.ts
3075
+ function modelKey2(model) {
3076
+ return `${model.providerID}/${model.modelID}`;
3077
+ }
3078
+ function sameModel(left, right) {
3079
+ return left.providerID === right.providerID && left.modelID === right.modelID;
3080
+ }
3081
+ function compareModels(left, right) {
3082
+ return modelKey2(left).localeCompare(modelKey2(right));
3083
+ }
3084
+ function uniqueModelSelections(models) {
3085
+ const byKey = new Map;
3086
+ for (const model of models) {
3087
+ byKey.set(modelKey2(model), model);
3088
+ }
3089
+ return [...byKey.values()].sort(compareModels);
3090
+ }
3091
+ function localDefault(config) {
2060
3092
  return config.local.runtimes.find((runtime) => runtime.enabled)?.defaultModel ?? config.router.localDefault;
2061
3093
  }
2062
3094
  function resolveConfiguredFrontierBaseline(config) {
@@ -2359,22 +3391,22 @@ function toCostRecord(decision2, context) {
2359
3391
  }
2360
3392
 
2361
3393
  // src/telemetry/types.ts
2362
- import { z as z4 } from "zod";
2363
- var CostRecordSchema = z4.object({
2364
- ts: z4.number().finite(),
2365
- sessionID: z4.string().min(1).optional(),
2366
- promptHash: z4.string().min(1),
2367
- promptChars: z4.number().int().min(0),
3394
+ import { z as z8 } from "zod";
3395
+ var CostRecordSchema = z8.object({
3396
+ ts: z8.number().finite(),
3397
+ sessionID: z8.string().min(1).optional(),
3398
+ promptHash: z8.string().min(1),
3399
+ promptChars: z8.number().int().min(0),
2368
3400
  tier: ComplexityTierSchema,
2369
- routeKind: z4.enum(["local", "frontier"]),
3401
+ routeKind: z8.enum(["local", "frontier"]),
2370
3402
  selected: ModelRefSchema,
2371
- rationale: z4.string(),
2372
- estimatedCostUSD: z4.number().finite().min(0),
2373
- baselineCostUSD: z4.number().finite().min(0),
2374
- estimatedSavingsUSD: z4.number().finite(),
2375
- budgetAction: z4.string().min(1),
2376
- tokensIn: z4.number().int().min(0).optional(),
2377
- tokensOut: z4.number().int().min(0).optional()
3403
+ rationale: z8.string(),
3404
+ estimatedCostUSD: z8.number().finite().min(0),
3405
+ baselineCostUSD: z8.number().finite().min(0),
3406
+ estimatedSavingsUSD: z8.number().finite(),
3407
+ budgetAction: z8.string().min(1),
3408
+ tokensIn: z8.number().int().min(0).optional(),
3409
+ tokensOut: z8.number().int().min(0).optional()
2378
3410
  });
2379
3411
 
2380
3412
  // src/telemetry/read.ts
@@ -2534,6 +3566,973 @@ function hashPrompt(prompt) {
2534
3566
  return `fnv1a32:${(hash >>> 0).toString(16).padStart(8, "0")}`;
2535
3567
  }
2536
3568
 
3569
+ // src/orchestrator/graphIngress.ts
3570
+ var ACTIVE_INGRESS_BRAND = Symbol("openteam.activeIngress");
3571
+ function gateReason(policy) {
3572
+ if (policy.capability === undefined) {
3573
+ return "gate-closed";
3574
+ }
3575
+ if (policy.graph.killSwitch) {
3576
+ return "kill-switch";
3577
+ }
3578
+ return policy.graph.configuredMode === "active" ? undefined : "graph-disabled";
3579
+ }
3580
+ function decideIngress(policy, taskID) {
3581
+ const blocked = gateReason(policy);
3582
+ if (blocked !== undefined) {
3583
+ return { route: "legacy", reason: blocked };
3584
+ }
3585
+ if (!policy.allowList.includes(taskID)) {
3586
+ return { route: "legacy", reason: "not-allow-listed" };
3587
+ }
3588
+ return { route: "graph", taskID };
3589
+ }
3590
+
3591
+ // src/plugin/legacyRunProducer.ts
3592
+ function toObservedNode(node) {
3593
+ return {
3594
+ id: node.id,
3595
+ role: node.role,
3596
+ model: node.model,
3597
+ ok: node.ok,
3598
+ ...node.sessionRef !== undefined ? { sessionRef: node.sessionRef } : {},
3599
+ ...node.errorClass !== undefined ? { errorClass: node.errorClass } : {}
3600
+ };
3601
+ }
3602
+ function buildObservedRun(payload) {
3603
+ return {
3604
+ runID: payload.runID,
3605
+ executed: true,
3606
+ status: payload.status,
3607
+ fixes: payload.fixes,
3608
+ nodes: payload.nodes.map(toObservedNode),
3609
+ ...payload.parentSessionRef !== undefined ? { parentSessionRef: payload.parentSessionRef } : {}
3610
+ };
3611
+ }
3612
+ function crossVerify(payload, tracker) {
3613
+ const unverifiedModels = [];
3614
+ const modelMismatches = [];
3615
+ const unknownSessions = [];
3616
+ for (const node of payload.nodes) {
3617
+ if (node.sessionRef !== undefined) {
3618
+ const mv = tracker.verifyModel(node.sessionRef, node.model);
3619
+ if (!mv.verified) {
3620
+ unverifiedModels.push(node.id);
3621
+ } else if (!mv.match) {
3622
+ modelMismatches.push(node.id);
3623
+ }
3624
+ const sv = tracker.verifySessionRef(node.sessionRef);
3625
+ if (sv.known === false) {
3626
+ unknownSessions.push(node.id);
3627
+ }
3628
+ } else {
3629
+ unverifiedModels.push(node.id);
3630
+ }
3631
+ }
3632
+ return { unverifiedModels, modelMismatches, unknownSessions };
3633
+ }
3634
+ function formatWarnings(verification) {
3635
+ const warnings = [];
3636
+ if (verification.unverifiedModels.length > 0) {
3637
+ warnings.push(`model not verifiable for nodes: ${verification.unverifiedModels.join(", ")}`);
3638
+ }
3639
+ if (verification.modelMismatches.length > 0) {
3640
+ warnings.push(`model mismatch for nodes: ${verification.modelMismatches.join(", ")}`);
3641
+ }
3642
+ if (verification.unknownSessions.length > 0) {
3643
+ warnings.push(`unknown sessionRef for nodes: ${verification.unknownSessions.join(", ")}`);
3644
+ }
3645
+ return warnings;
3646
+ }
3647
+ function formatResponse(response) {
3648
+ const parts = [`report-run: ${response.result}`];
3649
+ if (response.detail !== undefined) {
3650
+ parts.push(response.detail);
3651
+ }
3652
+ if (response.warnings !== undefined && response.warnings.length > 0) {
3653
+ parts.push(`warnings: ${response.warnings.join("; ")}`);
3654
+ }
3655
+ return parts.join(" — ");
3656
+ }
3657
+
3658
+ // src/plugin/reportRunSchema.ts
3659
+ import { z as z10 } from "zod";
3660
+
3661
+ // src/plugin/graphShadowIngress.ts
3662
+ import { z as z9 } from "zod";
3663
+ var LEGACY_EXECUTION_TRACE_VERSION = 1;
3664
+ var LegacyExecutionStatusSchema = z9.enum([
3665
+ "completed",
3666
+ "failed",
3667
+ "cancelled"
3668
+ ]);
3669
+ var LegacyReviewOutcomeSchema = z9.enum([
3670
+ "approved",
3671
+ "rejected",
3672
+ "inconclusive"
3673
+ ]);
3674
+ var LegacyTraceNodeV1Schema = z9.object({
3675
+ id: NodeIDSchema,
3676
+ role: NodeRoleSchema,
3677
+ model: z9.string().min(1),
3678
+ sessionRef: z9.string().min(1).optional(),
3679
+ errorClass: ErrorClassSchema.optional()
3680
+ }).strict();
3681
+ var LegacyExecutionTraceV1Schema = z9.object({
3682
+ version: z9.literal(LEGACY_EXECUTION_TRACE_VERSION),
3683
+ runID: NodeIDSchema,
3684
+ status: LegacyExecutionStatusSchema,
3685
+ outcome: LegacyReviewOutcomeSchema,
3686
+ fixes: z9.number().int().nonnegative(),
3687
+ nodes: z9.array(LegacyTraceNodeV1Schema).min(1),
3688
+ parentSessionRef: z9.string().min(1).optional()
3689
+ }).strict();
3690
+ function gateReason2(gate) {
3691
+ if (gate === undefined || gate.mode === "off") {
3692
+ return "graph-disabled";
3693
+ }
3694
+ if (gate.killSwitch) {
3695
+ return "kill-switch";
3696
+ }
3697
+ return;
3698
+ }
3699
+ function deriveOutcome(nodes) {
3700
+ for (let index = nodes.length - 1;index >= 0; index -= 1) {
3701
+ const node = nodes[index];
3702
+ if (node !== undefined && node.role === "reviewer") {
3703
+ return node.ok ? "approved" : "rejected";
3704
+ }
3705
+ }
3706
+ return "inconclusive";
3707
+ }
3708
+ function toTraceNode(node) {
3709
+ return {
3710
+ id: node.id,
3711
+ role: node.role,
3712
+ model: node.model,
3713
+ ...node.sessionRef !== undefined ? { sessionRef: node.sessionRef } : {},
3714
+ ...node.errorClass !== undefined ? { errorClass: node.errorClass } : {}
3715
+ };
3716
+ }
3717
+ function ingestLegacyExecution(observation, gate) {
3718
+ const gated = gateReason2(gate);
3719
+ if (gated !== undefined) {
3720
+ return { eligible: false, reason: gated };
3721
+ }
3722
+ if (!observation.executed) {
3723
+ return { eligible: false, reason: "not-executed" };
3724
+ }
3725
+ if (observation.nodes.length === 0) {
3726
+ return { eligible: false, reason: "no-observation" };
3727
+ }
3728
+ const trace = {
3729
+ version: LEGACY_EXECUTION_TRACE_VERSION,
3730
+ runID: observation.runID,
3731
+ status: observation.status,
3732
+ outcome: deriveOutcome(observation.nodes),
3733
+ fixes: Math.max(0, Math.trunc(observation.fixes)),
3734
+ nodes: observation.nodes.map(toTraceNode),
3735
+ ...observation.parentSessionRef !== undefined ? { parentSessionRef: observation.parentSessionRef } : {}
3736
+ };
3737
+ assertReferenceOnly(trace);
3738
+ return { eligible: true, trace };
3739
+ }
3740
+
3741
+ class LegacyTraceError extends Error {
3742
+ code = "invalid-legacy-trace";
3743
+ issues;
3744
+ constructor(error) {
3745
+ super(`invalid-legacy-trace: ${error.issues.map((issue) => issue.message).join("; ")}`);
3746
+ this.name = "LegacyTraceError";
3747
+ this.issues = error.issues.map((issue) => issue.message);
3748
+ }
3749
+ }
3750
+ function parseLegacyExecutionTrace(input) {
3751
+ const result = LegacyExecutionTraceV1Schema.safeParse(input);
3752
+ if (!result.success) {
3753
+ throw new LegacyTraceError(result.error);
3754
+ }
3755
+ return result.data;
3756
+ }
3757
+
3758
+ // src/plugin/reportRunSchema.ts
3759
+ var ReportRunNodeSchema = z10.object({
3760
+ id: NodeIDSchema,
3761
+ role: NodeRoleSchema,
3762
+ model: z10.string().min(1),
3763
+ ok: z10.boolean(),
3764
+ sessionRef: z10.string().min(1).optional(),
3765
+ errorClass: ErrorClassSchema.optional()
3766
+ }).strict();
3767
+ var ReportRunPayloadSchema = z10.object({
3768
+ runID: NodeIDSchema,
3769
+ status: LegacyExecutionStatusSchema,
3770
+ fixes: z10.number().int().nonnegative(),
3771
+ nodes: z10.array(ReportRunNodeSchema).min(1),
3772
+ parentSessionRef: z10.string().min(1).optional()
3773
+ }).strict();
3774
+
3775
+ // src/graph/types.ts
3776
+ var TERMINAL_NODE_STATUSES = new Set([
3777
+ "succeeded",
3778
+ "cancelled"
3779
+ ]);
3780
+ var TERMINAL_RUN_STATUSES = new Set([
3781
+ "completed",
3782
+ "failed",
3783
+ "cancelled"
3784
+ ]);
3785
+
3786
+ // src/graph/reducer.ts
3787
+ class ReducerError extends Error {
3788
+ code;
3789
+ detail;
3790
+ constructor(code, detail) {
3791
+ super(`${code}: ${detail}`);
3792
+ this.name = "ReducerError";
3793
+ this.code = code;
3794
+ this.detail = detail;
3795
+ }
3796
+ }
3797
+ function initialState(spec) {
3798
+ const nodes = {};
3799
+ for (const node of spec.nodes) {
3800
+ nodes[node.id] = { status: "pending", attempts: 0 };
3801
+ }
3802
+ return { runID: spec.runID, status: "running", seq: -1, nodes };
3803
+ }
3804
+ function withNode(state, nodeID, next) {
3805
+ return {
3806
+ ...state,
3807
+ nodes: { ...state.nodes, [nodeID]: next },
3808
+ seq: state.seq
3809
+ };
3810
+ }
3811
+ function specNode(spec, nodeID) {
3812
+ return spec.nodes.find((node) => node.id === nodeID);
3813
+ }
3814
+ function applyRunStarted(state, seq, specDigest) {
3815
+ if (state.specDigest !== undefined) {
3816
+ if (state.specDigest === specDigest) {
3817
+ return state;
3818
+ }
3819
+ throw new ReducerError("run-started-conflict", specDigest);
3820
+ }
3821
+ return { ...state, specDigest, seq };
3822
+ }
3823
+ function applyDispatched(spec, state, event) {
3824
+ const node = specNode(spec, event.nodeID);
3825
+ const current = state.nodes[event.nodeID];
3826
+ if (node === undefined || current === undefined) {
3827
+ throw new ReducerError("unknown-node", event.nodeID);
3828
+ }
3829
+ for (const dep of node.dependsOn) {
3830
+ if (state.nodes[dep]?.status !== "succeeded") {
3831
+ throw new ReducerError("dependency-not-satisfied", `${event.nodeID} <- ${dep}`);
3832
+ }
3833
+ }
3834
+ const running = {
3835
+ status: "running",
3836
+ attempts: current.attempts + 1,
3837
+ operationID: event.operationID,
3838
+ attemptID: event.attemptID
3839
+ };
3840
+ if (current.status === "pending") {
3841
+ return withNode({ ...state, seq: event.seq }, event.nodeID, running);
3842
+ }
3843
+ if (current.status === "failed") {
3844
+ if (current.operationID !== event.operationID) {
3845
+ throw new ReducerError("dispatch-conflict", event.nodeID);
3846
+ }
3847
+ return withNode({ ...state, seq: event.seq }, event.nodeID, running);
3848
+ }
3849
+ if (current.status === "running") {
3850
+ if (current.operationID === event.operationID && current.attemptID === event.attemptID) {
3851
+ return state;
3852
+ }
3853
+ throw new ReducerError("dispatch-conflict", event.nodeID);
3854
+ }
3855
+ throw new ReducerError("illegal-transition", `dispatch ${current.status}`);
3856
+ }
3857
+ function applySucceeded(state, event) {
3858
+ const current = state.nodes[event.nodeID];
3859
+ if (current === undefined) {
3860
+ throw new ReducerError("unknown-node", event.nodeID);
3861
+ }
3862
+ if (current.status === "succeeded") {
3863
+ if (current.operationID === event.operationID && current.artifactSha256 === event.artifact.sha256) {
3864
+ return state;
3865
+ }
3866
+ throw new ReducerError("receipt-conflict", event.nodeID);
3867
+ }
3868
+ if (current.status !== "running") {
3869
+ throw new ReducerError("illegal-transition", `succeed ${current.status}`);
3870
+ }
3871
+ if (current.operationID !== event.operationID || current.attemptID !== event.attemptID) {
3872
+ throw new ReducerError("receipt-conflict", event.nodeID);
3873
+ }
3874
+ return withNode({ ...state, seq: event.seq }, event.nodeID, {
3875
+ status: "succeeded",
3876
+ attempts: current.attempts,
3877
+ operationID: event.operationID,
3878
+ attemptID: event.attemptID,
3879
+ artifactSha256: event.artifact.sha256
3880
+ });
3881
+ }
3882
+ function applyFailed(state, event) {
3883
+ const current = state.nodes[event.nodeID];
3884
+ if (current === undefined) {
3885
+ throw new ReducerError("unknown-node", event.nodeID);
3886
+ }
3887
+ if (current.status === "failed") {
3888
+ if (current.operationID === event.operationID && current.attemptID === event.attemptID) {
3889
+ return state;
3890
+ }
3891
+ throw new ReducerError("receipt-conflict", event.nodeID);
3892
+ }
3893
+ if (current.status !== "running") {
3894
+ throw new ReducerError("illegal-transition", `fail ${current.status}`);
3895
+ }
3896
+ if (current.operationID !== event.operationID || current.attemptID !== event.attemptID) {
3897
+ throw new ReducerError("receipt-conflict", event.nodeID);
3898
+ }
3899
+ return withNode({ ...state, seq: event.seq }, event.nodeID, {
3900
+ status: "failed",
3901
+ attempts: current.attempts,
3902
+ operationID: event.operationID,
3903
+ attemptID: event.attemptID
3904
+ });
3905
+ }
3906
+ function applyCancelled(state, event) {
3907
+ const current = state.nodes[event.nodeID];
3908
+ if (current === undefined) {
3909
+ throw new ReducerError("unknown-node", event.nodeID);
3910
+ }
3911
+ if (current.status === "cancelled") {
3912
+ return state;
3913
+ }
3914
+ if (current.status === "succeeded") {
3915
+ throw new ReducerError("illegal-transition", "cancel succeeded");
3916
+ }
3917
+ return withNode({ ...state, seq: event.seq }, event.nodeID, {
3918
+ ...current,
3919
+ status: "cancelled"
3920
+ });
3921
+ }
3922
+ function applyRunCompleted(state, seq) {
3923
+ for (const node of Object.values(state.nodes)) {
3924
+ if (node.status !== "succeeded" && node.status !== "cancelled") {
3925
+ throw new ReducerError("incomplete-run", node.status);
3926
+ }
3927
+ }
3928
+ return { ...state, status: "completed", seq };
3929
+ }
3930
+ function applyEvent(spec, state, event) {
3931
+ if (event.runID !== state.runID) {
3932
+ throw new ReducerError("run-mismatch", event.runID);
3933
+ }
3934
+ if (TERMINAL_RUN_STATUSES.has(state.status)) {
3935
+ if (event.type === "run.completed" && state.status === "completed" || event.type === "run.failed" && state.status === "failed" || event.type === "run.cancelled" && state.status === "cancelled") {
3936
+ return state;
3937
+ }
3938
+ throw new ReducerError("post-terminal", event.type);
3939
+ }
3940
+ switch (event.type) {
3941
+ case "run.started":
3942
+ return applyRunStarted(state, event.seq, event.specDigest);
3943
+ case "node.dispatched":
3944
+ return applyDispatched(spec, state, event);
3945
+ case "node.succeeded":
3946
+ return applySucceeded(state, event);
3947
+ case "node.failed":
3948
+ return applyFailed(state, event);
3949
+ case "node.cancelled":
3950
+ return applyCancelled(state, event);
3951
+ case "run.completed":
3952
+ return applyRunCompleted(state, event.seq);
3953
+ case "run.failed":
3954
+ return { ...state, status: "failed", seq: event.seq };
3955
+ case "run.cancelled":
3956
+ return { ...state, status: "cancelled", seq: event.seq };
3957
+ }
3958
+ }
3959
+
3960
+ // src/orchestrator/graphShadow.ts
3961
+ class ShadowMirrorError extends Error {
3962
+ code;
3963
+ detail;
3964
+ constructor(code, detail) {
3965
+ super(`${code}: ${detail}`);
3966
+ this.name = "ShadowMirrorError";
3967
+ this.code = code;
3968
+ this.detail = detail;
3969
+ }
3970
+ }
3971
+ function traceAnchorDigest(trace) {
3972
+ return sha256Hex(JSON.stringify({
3973
+ version: trace.version,
3974
+ runID: trace.runID,
3975
+ nodes: trace.nodes.map((node) => [node.id, node.role])
3976
+ }));
3977
+ }
3978
+ function compileShadowSpec(trace) {
3979
+ const anchor = { taskID: trace.runID, specDigest: traceAnchorDigest(trace) };
3980
+ const nodes = [];
3981
+ let previousID;
3982
+ let lastImplementerID;
3983
+ for (const node of trace.nodes) {
3984
+ const dependsOn = previousID === undefined ? [] : [previousID];
3985
+ if (node.role === "implementer") {
3986
+ nodes.push({ id: node.id, role: "implementer", dependsOn, anchor });
3987
+ lastImplementerID = node.id;
3988
+ } else {
3989
+ nodes.push({
3990
+ id: node.id,
3991
+ role: "reviewer",
3992
+ dependsOn,
3993
+ anchor,
3994
+ ...lastImplementerID === undefined ? {} : { reviews: lastImplementerID }
3995
+ });
3996
+ }
3997
+ previousID = node.id;
3998
+ }
3999
+ let spec;
4000
+ try {
4001
+ spec = GraphSpecV1Schema.parse({ version: 1, runID: trace.runID, nodes });
4002
+ validateGraphSpec(spec);
4003
+ } catch (error) {
4004
+ throw new ShadowMirrorError("invalid-spec", error instanceof Error ? error.message : String(error));
4005
+ }
4006
+ assertReferenceOnly(spec);
4007
+ return spec;
4008
+ }
4009
+ var TERMINAL_EVENT = {
4010
+ completed: "run.completed",
4011
+ failed: "run.failed",
4012
+ cancelled: "run.cancelled"
4013
+ };
4014
+ function shadowArtifact(runID, nodeID) {
4015
+ return {
4016
+ uri: `shadow://${runID}/${nodeID}`,
4017
+ sha256: sha256Hex(`shadow-artifact\x00${runID}\x00${nodeID}`),
4018
+ bytes: 0
4019
+ };
4020
+ }
4021
+ function mirrorJournal(spec, trace) {
4022
+ const runID = trace.runID;
4023
+ const events = [
4024
+ {
4025
+ v: 1,
4026
+ seq: 0,
4027
+ runID,
4028
+ type: "run.started",
4029
+ specDigest: graphSpecDigest(spec)
4030
+ }
4031
+ ];
4032
+ for (const node of trace.nodes) {
4033
+ const operation = operationID(runID, node.id);
4034
+ const attempt = attemptID(operation, 0);
4035
+ events.push({
4036
+ v: 1,
4037
+ seq: events.length,
4038
+ runID,
4039
+ type: "node.dispatched",
4040
+ nodeID: node.id,
4041
+ operationID: operation,
4042
+ attemptID: attempt
4043
+ });
4044
+ if (node.errorClass === undefined) {
4045
+ events.push({
4046
+ v: 1,
4047
+ seq: events.length,
4048
+ runID,
4049
+ type: "node.succeeded",
4050
+ nodeID: node.id,
4051
+ operationID: operation,
4052
+ attemptID: attempt,
4053
+ artifact: shadowArtifact(runID, node.id)
4054
+ });
4055
+ continue;
4056
+ }
4057
+ if (node.errorClass === "cancelled") {
4058
+ events.push({
4059
+ v: 1,
4060
+ seq: events.length,
4061
+ runID,
4062
+ type: "node.cancelled",
4063
+ nodeID: node.id
4064
+ });
4065
+ continue;
4066
+ }
4067
+ events.push({
4068
+ v: 1,
4069
+ seq: events.length,
4070
+ runID,
4071
+ type: "node.failed",
4072
+ nodeID: node.id,
4073
+ operationID: operation,
4074
+ attemptID: attempt,
4075
+ errorClass: node.errorClass
4076
+ });
4077
+ }
4078
+ events.push({
4079
+ v: 1,
4080
+ seq: events.length,
4081
+ runID,
4082
+ type: TERMINAL_EVENT[trace.status]
4083
+ });
4084
+ return events;
4085
+ }
4086
+ function reduceMirror(spec, journal) {
4087
+ let state = initialState(spec);
4088
+ try {
4089
+ for (const event of journal) {
4090
+ state = applyEvent(spec, state, event);
4091
+ }
4092
+ } catch (error) {
4093
+ throw new ShadowMirrorError("illegal-sequence", error instanceof ReducerError ? `${error.code}: ${error.detail}` : String(error));
4094
+ }
4095
+ return state;
4096
+ }
4097
+ function projectLegacySummary(trace) {
4098
+ return {
4099
+ runID: trace.runID,
4100
+ status: trace.status,
4101
+ outcome: trace.outcome,
4102
+ fixes: trace.fixes,
4103
+ nodes: trace.nodes.map((node) => ({
4104
+ id: node.id,
4105
+ role: node.role,
4106
+ model: node.model
4107
+ }))
4108
+ };
4109
+ }
4110
+ function projectShadowSummary(spec, trace) {
4111
+ const implementers = spec.nodes.filter((node) => node.role === "implementer").length;
4112
+ return {
4113
+ runID: spec.runID,
4114
+ status: trace.status,
4115
+ outcome: trace.outcome,
4116
+ fixes: implementers - 1,
4117
+ nodes: spec.nodes.map((node, index) => ({
4118
+ id: node.id,
4119
+ role: node.role,
4120
+ model: trace.nodes[index].model
4121
+ }))
4122
+ };
4123
+ }
4124
+ function mirrorLegacyExecution(trace) {
4125
+ const spec = compileShadowSpec(trace);
4126
+ const journal = mirrorJournal(spec, trace);
4127
+ const state = reduceMirror(spec, journal);
4128
+ const summary = projectShadowSummary(spec, trace);
4129
+ assertReferenceOnly(summary);
4130
+ return {
4131
+ spec,
4132
+ specDigest: graphSpecDigest(spec),
4133
+ journal,
4134
+ state,
4135
+ summary
4136
+ };
4137
+ }
4138
+
4139
+ // src/orchestrator/graphFacade.ts
4140
+ function mirrorLegacyTrace(trace) {
4141
+ try {
4142
+ return {
4143
+ ok: true,
4144
+ mirror: mirrorLegacyExecution(parseLegacyExecutionTrace(trace))
4145
+ };
4146
+ } catch (error) {
4147
+ if (error instanceof ShadowMirrorError) {
4148
+ return { ok: false, code: error.code, detail: error.detail };
4149
+ }
4150
+ if (error instanceof LegacyTraceError) {
4151
+ return {
4152
+ ok: false,
4153
+ code: "invalid-trace",
4154
+ detail: error.issues.join("; ")
4155
+ };
4156
+ }
4157
+ return {
4158
+ ok: false,
4159
+ code: "mirror-error",
4160
+ detail: error instanceof Error ? error.message : String(error)
4161
+ };
4162
+ }
4163
+ }
4164
+
4165
+ // src/orchestrator/shadowComparator.ts
4166
+ function sequenceEqual(a, b) {
4167
+ if (a.length !== b.length) {
4168
+ return false;
4169
+ }
4170
+ return a.every((value, index) => value === b[index]);
4171
+ }
4172
+ function compareShadowRun(legacy, shadow) {
4173
+ if (legacy === undefined || shadow === undefined) {
4174
+ return { verdict: "inconclusive", divergences: [] };
4175
+ }
4176
+ const divergences = [];
4177
+ if (legacy.status !== shadow.status) {
4178
+ divergences.push("status");
4179
+ }
4180
+ if (!sequenceEqual(legacy.nodes.map((node) => node.id), shadow.nodes.map((node) => node.id))) {
4181
+ divergences.push("order");
4182
+ }
4183
+ if (!sequenceEqual(legacy.nodes.map((node) => node.role), shadow.nodes.map((node) => node.role))) {
4184
+ divergences.push("roles");
4185
+ }
4186
+ if (!sequenceEqual(legacy.nodes.map((node) => node.model), shadow.nodes.map((node) => node.model))) {
4187
+ divergences.push("models");
4188
+ }
4189
+ if (legacy.fixes !== shadow.fixes) {
4190
+ divergences.push("fixes");
4191
+ }
4192
+ if (legacy.outcome !== shadow.outcome) {
4193
+ divergences.push("outcome");
4194
+ }
4195
+ return {
4196
+ verdict: divergences.length === 0 ? "match" : "divergent",
4197
+ divergences
4198
+ };
4199
+ }
4200
+
4201
+ // src/storage/graph/soakRecorder.ts
4202
+ async function ensureRecorderId(ports, basePath) {
4203
+ const idPath = `${basePath}/soak-recorder-id`;
4204
+ if (await ports.fs.exists(idPath)) {
4205
+ const id = (await ports.fs.readFile(idPath)).trim();
4206
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(id)) {
4207
+ return id;
4208
+ }
4209
+ }
4210
+ const newId = ports.id.randomUUID();
4211
+ await ports.fs.writeFile(idPath, newId);
4212
+ return newId;
4213
+ }
4214
+ async function readAllChains(ports, evidenceDir) {
4215
+ if (!await ports.fs.exists(evidenceDir)) {
4216
+ return [];
4217
+ }
4218
+ const files = await ports.fs.readDir(evidenceDir);
4219
+ const chains = [];
4220
+ for (const file of files) {
4221
+ if (!file.endsWith(".jsonl")) {
4222
+ continue;
4223
+ }
4224
+ const content = await ports.fs.readFile(`${evidenceDir}/${file}`);
4225
+ const lines = content.split(`
4226
+ `).filter((l) => l.trim().length > 0);
4227
+ const observations = [];
4228
+ for (const line of lines) {
4229
+ const parsed = SoakObservationSchema.safeParse(JSON.parse(line));
4230
+ if (parsed.success) {
4231
+ observations.push(parsed.data);
4232
+ }
4233
+ }
4234
+ if (observations.length > 0) {
4235
+ chains.push(observations);
4236
+ }
4237
+ }
4238
+ return chains;
4239
+ }
4240
+ function canonicalObservationPreimage2(obs) {
4241
+ return JSON.stringify({
4242
+ version: obs.version,
4243
+ seq: obs.seq,
4244
+ timestamp: obs.timestamp,
4245
+ platform: obs.platform,
4246
+ opencodeVersion: obs.opencodeVersion,
4247
+ provenance: obs.provenance,
4248
+ traceDigest: obs.traceDigest,
4249
+ criticalDivergences: obs.criticalDivergences,
4250
+ privacy: obs.privacy,
4251
+ duplicateEffects: obs.duplicateEffects,
4252
+ modelVerification: obs.modelVerification
4253
+ });
4254
+ }
4255
+ async function appendObservation(ports, evidenceDir, recorderId, input) {
4256
+ await ports.fs.mkdir(evidenceDir);
4257
+ const chainPath = `${evidenceDir}/${recorderId}.jsonl`;
4258
+ let seq = 0;
4259
+ let prevChainDigest = "0".repeat(64);
4260
+ if (await ports.fs.exists(chainPath)) {
4261
+ const content = await ports.fs.readFile(chainPath);
4262
+ const lines = content.split(`
4263
+ `).filter((l) => l.trim().length > 0);
4264
+ if (lines.length > 0) {
4265
+ const lastLine = lines[lines.length - 1];
4266
+ let rawParsed;
4267
+ try {
4268
+ rawParsed = JSON.parse(lastLine);
4269
+ } catch {
4270
+ throw new Error("corrupt chain tail: invalid JSON");
4271
+ }
4272
+ const parsed = SoakObservationSchema.safeParse(rawParsed);
4273
+ if (!parsed.success) {
4274
+ const detail = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.code}`).join("; ");
4275
+ throw new Error(`corrupt chain tail: ${detail}`);
4276
+ }
4277
+ seq = parsed.data.seq + 1;
4278
+ prevChainDigest = parsed.data.chainDigest;
4279
+ }
4280
+ }
4281
+ const timestamp = ports.clock.now();
4282
+ const base = {
4283
+ version: 1,
4284
+ seq,
4285
+ timestamp,
4286
+ platform: input.platform,
4287
+ opencodeVersion: input.opencodeVersion,
4288
+ provenance: input.provenance,
4289
+ traceDigest: input.traceDigest,
4290
+ criticalDivergences: input.criticalDivergences,
4291
+ privacy: input.privacy,
4292
+ duplicateEffects: input.duplicateEffects,
4293
+ modelVerification: input.modelVerification
4294
+ };
4295
+ const preimage = canonicalObservationPreimage2(base);
4296
+ const chainDigest = sha256Hex(prevChainDigest + preimage);
4297
+ const observation = { ...base, chainDigest };
4298
+ assertReferenceOnly(observation);
4299
+ const line = JSON.stringify(observation);
4300
+ if (await ports.fs.exists(chainPath)) {
4301
+ const existing = await ports.fs.readFile(chainPath);
4302
+ await ports.fs.writeFile(chainPath, `${existing}
4303
+ ${line}`);
4304
+ } else {
4305
+ await ports.fs.writeFile(chainPath, line);
4306
+ }
4307
+ return observation;
4308
+ }
4309
+
4310
+ // src/plugin/soakObserver.ts
4311
+ function resolvePlatform(platform) {
4312
+ if (platform === "win32")
4313
+ return "win32";
4314
+ if (platform === "darwin")
4315
+ return "darwin";
4316
+ if (platform === "linux")
4317
+ return "linux";
4318
+ return null;
4319
+ }
4320
+ function countCriticalDivergences(parity, criticalCodes) {
4321
+ if (parity === undefined)
4322
+ return null;
4323
+ if (criticalCodes.length === 0)
4324
+ return null;
4325
+ const criticalSet = new Set(criticalCodes);
4326
+ return parity.divergences.filter((code) => criticalSet.has(code)).length;
4327
+ }
4328
+ function scanTracePrivacy(trace) {
4329
+ const result = scanReferenceOnly([trace]);
4330
+ return {
4331
+ surfacesScanned: result.surfacesScanned,
4332
+ rawFindings: result.rawFindings
4333
+ };
4334
+ }
4335
+ function detectDuplicateEffects(trace) {
4336
+ const nodes = trace.nodes;
4337
+ if (nodes.length === 0)
4338
+ return null;
4339
+ const seen = new Set;
4340
+ let duplicates = 0;
4341
+ for (const node of nodes) {
4342
+ if (seen.has(node.id)) {
4343
+ duplicates++;
4344
+ } else {
4345
+ seen.add(node.id);
4346
+ }
4347
+ }
4348
+ return { effectsExamined: nodes.length, duplicatesFound: duplicates };
4349
+ }
4350
+ function traceToObservationInput(trace, opencodeVersion, parity, criticalCodes, modelVerification, platformOverride) {
4351
+ const platform = resolvePlatform(platformOverride ?? process.platform);
4352
+ if (platform === null)
4353
+ return null;
4354
+ const traceDigest = sha256Hex(JSON.stringify(trace));
4355
+ return {
4356
+ platform,
4357
+ opencodeVersion,
4358
+ provenance: "genuine-usage",
4359
+ traceDigest,
4360
+ criticalDivergences: countCriticalDivergences(parity, criticalCodes),
4361
+ privacy: scanTracePrivacy(trace),
4362
+ duplicateEffects: detectDuplicateEffects(trace),
4363
+ modelVerification
4364
+ };
4365
+ }
4366
+ async function recordSoakObservation(ports, evidenceDir, recorderId, trace, opencodeVersion, parity, criticalCodes, modelVerification, platformOverride) {
4367
+ const input = traceToObservationInput(trace, opencodeVersion, parity, criticalCodes, modelVerification, platformOverride);
4368
+ if (input === null)
4369
+ return false;
4370
+ await appendObservation(ports, evidenceDir, recorderId, input);
4371
+ return true;
4372
+ }
4373
+
4374
+ // src/plugin/shadowPipeline.ts
4375
+ function createShadowPipeline(deps) {
4376
+ return async (observation, modelVerification) => {
4377
+ try {
4378
+ const ingested = ingestLegacyExecution(observation, deps.gate);
4379
+ if (!ingested.eligible) {
4380
+ return { kind: "ineligible", reason: ingested.reason };
4381
+ }
4382
+ const mirror = mirrorLegacyTrace(ingested.trace);
4383
+ let parity;
4384
+ if (mirror.ok) {
4385
+ const legacySummary = projectLegacySummary(ingested.trace);
4386
+ const report = compareShadowRun(legacySummary, mirror.mirror.summary);
4387
+ parity = { divergences: [...report.divergences] };
4388
+ }
4389
+ const recorded = await recordSoakObservation(deps.ports, deps.evidenceDir, deps.recorderId, ingested.trace, deps.opencodeVersion, parity, deps.criticalCodes, modelVerification ?? null, deps.platformOverride);
4390
+ if (!recorded) {
4391
+ return {
4392
+ kind: "observation-rejected",
4393
+ reason: "unrecognized-platform"
4394
+ };
4395
+ }
4396
+ return { kind: "observed" };
4397
+ } catch (error) {
4398
+ return {
4399
+ kind: "contained-failure",
4400
+ detail: error instanceof Error ? error.message : String(error)
4401
+ };
4402
+ }
4403
+ };
4404
+ }
4405
+
4406
+ // src/plugin/graphTool.ts
4407
+ function describeDecision(decision2) {
4408
+ if (decision2.route === "graph") {
4409
+ return `graph: la tarea ${decision2.taskID} se enrutaría al runtime activo.`;
4410
+ }
4411
+ switch (decision2.reason) {
4412
+ case "gate-closed":
4413
+ return "legacy: gate-closed — el ingress activo no es alcanzable por configuración en esta versión.";
4414
+ case "kill-switch":
4415
+ return "legacy: kill-switch — apagado de emergencia activo.";
4416
+ case "graph-disabled":
4417
+ return "legacy: graph-disabled — graph.mode no es 'active'.";
4418
+ case "not-allow-listed":
4419
+ return "legacy: not-allow-listed — la tarea no está en la allow-list del ingress.";
4420
+ }
4421
+ }
4422
+ function describeOutcome(outcome) {
4423
+ switch (outcome.kind) {
4424
+ case "legacy":
4425
+ return describeDecision({ route: "legacy", reason: outcome.reason });
4426
+ case "accepted":
4427
+ return `accepted: run ${outcome.runID}, paso ${outcome.step.kind}.`;
4428
+ case "revision":
4429
+ return `revision: run ${outcome.runID}, plan ${outcome.plan.kind}.`;
4430
+ case "reassigned":
4431
+ return `reassigned: run ${outcome.runID}, reviewer ${outcome.reviewerID}.`;
4432
+ case "cancelled":
4433
+ return `cancelled: run ${outcome.runID}, ${outcome.cancelledNodeIDs.length} nodo(s) cancelado(s).`;
4434
+ case "error":
4435
+ return `error: ${outcome.code}.`;
4436
+ }
4437
+ }
4438
+ function unavailableReason(deps, taskID) {
4439
+ const decision2 = decideIngress(deps.policy, taskID);
4440
+ return decision2.route === "legacy" ? describeDecision(decision2) : "unavailable: el ejecutor de efectos del runtime activo no está enchufado.";
4441
+ }
4442
+ function createGraphTool(deps) {
4443
+ const observe = deps.shadow !== undefined ? createShadowPipeline(deps.shadow) : async () => ({
4444
+ kind: "ineligible",
4445
+ reason: "shadow-not-configured"
4446
+ });
4447
+ const definition = tool2({
4448
+ description: "Ingress activo de SDD de openteam (GE-050). En esta versión la puerta interna está cerrada: informa del estado de enrutado y no ejecuta runs. La acción report-run registra una observación shadow de la ejecución legacy.",
4449
+ args: {
4450
+ action: tool2.schema.enum(["status", "start", "cancel", "report-run"]).describe("Acción a ejecutar"),
4451
+ taskID: tool2.schema.string().optional().describe("Identificador de la tarea SDD"),
4452
+ runID: tool2.schema.string().optional().describe("Identificador del run (para action=cancel o report-run)"),
4453
+ status: tool2.schema.enum(["completed", "failed", "cancelled"]).optional().describe("Estado terminal del run (solo action=report-run)"),
4454
+ fixes: tool2.schema.number().optional().describe("Número de iteraciones de fix (solo action=report-run)"),
4455
+ nodes: tool2.schema.array(tool2.schema.object({
4456
+ id: tool2.schema.string().describe("ID del nodo"),
4457
+ role: tool2.schema.enum(["implementer", "reviewer"]).describe("Rol del nodo"),
4458
+ model: tool2.schema.string().describe("Modelo utilizado por el nodo"),
4459
+ ok: tool2.schema.boolean().describe("Éxito terminal del nodo"),
4460
+ sessionRef: tool2.schema.string().optional().describe("ID de sesión de opencode del nodo"),
4461
+ errorClass: tool2.schema.enum(["transient", "permanent", "exhausted"]).optional().describe("Clase de error si el nodo falló")
4462
+ })).optional().describe("Nodos de la ejecución (solo action=report-run)"),
4463
+ parentSessionRef: tool2.schema.string().optional().describe("Sesión padre del run (solo action=report-run)")
4464
+ },
4465
+ async execute(args) {
4466
+ if (args.action === "report-run") {
4467
+ return handleReportRun(args, observe, deps.tracker);
4468
+ }
4469
+ const taskID = args.taskID ?? "";
4470
+ if (args.action === "status") {
4471
+ return describeDecision(decideIngress(deps.policy, taskID));
4472
+ }
4473
+ const ingress = deps.ingress;
4474
+ if (ingress === undefined) {
4475
+ return unavailableReason(deps, taskID);
4476
+ }
4477
+ if (args.action === "cancel") {
4478
+ return describeOutcome(await ingress.cancel(args.runID ?? ""));
4479
+ }
4480
+ return describeOutcome(await ingress.start({
4481
+ id: taskID,
4482
+ implementerRoleID: "linus",
4483
+ title: taskID,
4484
+ brief: taskID,
4485
+ task: {
4486
+ promptChars: 0,
4487
+ hasCode: false,
4488
+ estimatedInputTokens: 0,
4489
+ estimatedOutputTokens: 0
4490
+ }
4491
+ }));
4492
+ }
4493
+ });
4494
+ return { definition, observeLegacyExecution: observe };
4495
+ }
4496
+ async function handleReportRun(args, observe, tracker) {
4497
+ const parsed = ReportRunPayloadSchema.safeParse({
4498
+ runID: args.runID,
4499
+ status: args.status,
4500
+ fixes: args.fixes,
4501
+ nodes: args.nodes,
4502
+ ...args.parentSessionRef !== undefined ? { parentSessionRef: args.parentSessionRef } : {}
4503
+ });
4504
+ if (!parsed.success) {
4505
+ const issues = parsed.error.issues.map((issue) => issue.message).join("; ");
4506
+ return formatResponse({
4507
+ result: "ineligible",
4508
+ detail: `validation: ${issues}`
4509
+ });
4510
+ }
4511
+ const verification = crossVerify(parsed.data, tracker);
4512
+ const warnings = formatWarnings(verification);
4513
+ const verificationPair = {
4514
+ nodesChecked: parsed.data.nodes.length,
4515
+ unverified: verification.unverifiedModels.length,
4516
+ mismatched: verification.modelMismatches.length
4517
+ };
4518
+ const run = buildObservedRun(parsed.data);
4519
+ try {
4520
+ const result = await observe(run, verificationPair);
4521
+ const base = { result: result.kind };
4522
+ const detail = result.kind === "ineligible" ? result.reason : result.kind === "observation-rejected" ? result.reason : result.kind === "contained-failure" ? result.detail : null;
4523
+ return formatResponse({
4524
+ ...base,
4525
+ ...detail !== null ? { detail } : {},
4526
+ ...warnings.length > 0 ? { warnings } : {}
4527
+ });
4528
+ } catch (error) {
4529
+ return formatResponse({
4530
+ result: "contained-failure",
4531
+ detail: error instanceof Error ? error.message : String(error)
4532
+ });
4533
+ }
4534
+ }
4535
+
2537
4536
  // src/plugin/hooks.ts
2538
4537
  var codeSignals = [
2539
4538
  "```",
@@ -2621,6 +4620,12 @@ function createChatMessageHook(config, getAvailable, options = {}) {
2621
4620
  telemetryContext.tokensOut = signals.estimatedOutputTokens;
2622
4621
  }
2623
4622
  emitTelemetry(sink, costRecordToRouteEvent(toCostRecord(decision2, telemetryContext)));
4623
+ if (options.onRoute !== undefined) {
4624
+ const routedSessionID = input.sessionID ?? output.message.sessionID;
4625
+ if (typeof routedSessionID === "string" && routedSessionID.length > 0) {
4626
+ options.onRoute(routedSessionID, `${decision2.selected.providerID}/${decision2.selected.modelID}`);
4627
+ }
4628
+ }
2624
4629
  if (options.injectMemory !== undefined) {
2625
4630
  try {
2626
4631
  const block = await options.injectMemory(prompt);
@@ -2682,54 +4687,54 @@ ${lines.join(`
2682
4687
  }
2683
4688
 
2684
4689
  // src/memory/types.ts
2685
- import { z as z5 } from "zod";
4690
+ import { z as z11 } from "zod";
2686
4691
  var SHARED_OWNER_KEY = "*";
2687
- var OwnerKeySchema = z5.string().min(1);
2688
- var MemoryKindSchema = z5.enum(["fact", "preference", "entity"]);
2689
- var MemoryBaseSchema = z5.object({
2690
- id: z5.string().min(1),
4692
+ var OwnerKeySchema = z11.string().min(1);
4693
+ var MemoryKindSchema = z11.enum(["fact", "preference", "entity"]);
4694
+ var MemoryBaseSchema = z11.object({
4695
+ id: z11.string().min(1),
2691
4696
  ownerKey: OwnerKeySchema,
2692
- confidence: z5.number().min(0).max(1),
2693
- validFrom: z5.number().finite(),
2694
- validUntil: z5.number().finite().nullable().default(null),
2695
- createdAt: z5.number().finite(),
2696
- invalidatedAt: z5.number().finite().nullable().default(null),
2697
- sourceHash: z5.string().min(1),
2698
- supersededBy: z5.string().min(1).nullable().default(null)
4697
+ confidence: z11.number().min(0).max(1),
4698
+ validFrom: z11.number().finite(),
4699
+ validUntil: z11.number().finite().nullable().default(null),
4700
+ createdAt: z11.number().finite(),
4701
+ invalidatedAt: z11.number().finite().nullable().default(null),
4702
+ sourceHash: z11.string().min(1),
4703
+ supersededBy: z11.string().min(1).nullable().default(null)
2699
4704
  });
2700
4705
  var FactSchema = MemoryBaseSchema.extend({
2701
- kind: z5.literal("fact"),
2702
- subject: z5.string().min(1),
2703
- predicate: z5.string().min(1),
2704
- object: z5.string().min(1),
2705
- category: z5.string().min(1).nullable().default(null)
4706
+ kind: z11.literal("fact"),
4707
+ subject: z11.string().min(1),
4708
+ predicate: z11.string().min(1),
4709
+ object: z11.string().min(1),
4710
+ category: z11.string().min(1).nullable().default(null)
2706
4711
  });
2707
4712
  var PreferenceSchema = MemoryBaseSchema.extend({
2708
- kind: z5.literal("preference"),
2709
- category: z5.string().min(1),
2710
- preference: z5.string().min(1),
2711
- context: z5.string().min(1).nullable().default(null),
2712
- lastAccessedAt: z5.number().finite().nullable().default(null),
2713
- accessCount: z5.number().int().min(0).default(0)
4713
+ kind: z11.literal("preference"),
4714
+ category: z11.string().min(1),
4715
+ preference: z11.string().min(1),
4716
+ context: z11.string().min(1).nullable().default(null),
4717
+ lastAccessedAt: z11.number().finite().nullable().default(null),
4718
+ accessCount: z11.number().int().min(0).default(0)
2714
4719
  });
2715
4720
  var EntitySchema = MemoryBaseSchema.extend({
2716
- kind: z5.literal("entity"),
2717
- canonicalName: z5.string().min(1),
2718
- type: z5.string().min(1),
2719
- aliases: z5.array(z5.string().min(1)).default([])
4721
+ kind: z11.literal("entity"),
4722
+ canonicalName: z11.string().min(1),
4723
+ type: z11.string().min(1),
4724
+ aliases: z11.array(z11.string().min(1)).default([])
2720
4725
  });
2721
- var MemoryRecordSchema = z5.discriminatedUnion("kind", [
4726
+ var MemoryRecordSchema = z11.discriminatedUnion("kind", [
2722
4727
  FactSchema,
2723
4728
  PreferenceSchema,
2724
4729
  EntitySchema
2725
4730
  ]);
2726
- var RecallQuerySchema = z5.object({
4731
+ var RecallQuerySchema = z11.object({
2727
4732
  ownerKey: OwnerKeySchema,
2728
- kinds: z5.array(MemoryKindSchema).default(["fact", "preference", "entity"]),
2729
- asOf: z5.number().finite().nullable().default(null),
2730
- limit: z5.number().int().positive().default(8),
2731
- minSimilarity: z5.number().min(0).max(1).default(0.2),
2732
- includeShared: z5.boolean().default(true)
4733
+ kinds: z11.array(MemoryKindSchema).default(["fact", "preference", "entity"]),
4734
+ asOf: z11.number().finite().nullable().default(null),
4735
+ limit: z11.number().int().positive().default(8),
4736
+ minSimilarity: z11.number().min(0).max(1).default(0.2),
4737
+ includeShared: z11.boolean().default(true)
2733
4738
  });
2734
4739
 
2735
4740
  // src/memory/rank.ts
@@ -2878,6 +4883,33 @@ function buildMemoryInjector(deps, policy) {
2878
4883
  };
2879
4884
  }
2880
4885
 
4886
+ // src/plugin/sessionTracker.ts
4887
+ function createSessionTracker() {
4888
+ const knownSessions = new Set;
4889
+ const routedModels = new Map;
4890
+ return {
4891
+ trackSession(sessionID) {
4892
+ knownSessions.add(sessionID);
4893
+ },
4894
+ trackRoutedModel(sessionID, model) {
4895
+ routedModels.set(sessionID, model);
4896
+ },
4897
+ verifyModel(sessionRef, reportedModel) {
4898
+ const routedModel = routedModels.get(sessionRef);
4899
+ if (routedModel === undefined) {
4900
+ return { verified: false };
4901
+ }
4902
+ return { verified: true, match: routedModel === reportedModel };
4903
+ },
4904
+ verifySessionRef(sessionRef) {
4905
+ if (sessionRef === undefined) {
4906
+ return { known: null };
4907
+ }
4908
+ return { known: knownSessions.has(sessionRef) };
4909
+ }
4910
+ };
4911
+ }
4912
+
2881
4913
  // src/plugin/toolcalls.ts
2882
4914
  function key(sessionID, callID) {
2883
4915
  return `${sessionID}\x00${callID}`;
@@ -3337,6 +5369,82 @@ function logTelemetryError(error) {
3337
5369
  const message = error instanceof Error ? error.message : String(error);
3338
5370
  console.warn(`[openteam] telemetry write failed: ${message}`);
3339
5371
  }
5372
+ var SHADOW_CERT_PATH = "artifacts/graph-shadow-certificate.json";
5373
+ var RELEASE_CERT_PATH = "artifacts/graph-release-certificate.json";
5374
+ var SOAK_CERT_PATH = "artifacts/graph-soak-certificate.json";
5375
+ var SOAK_EVIDENCE_DIR = ".opencode/soak";
5376
+ async function readAndParseCertificate(storage, path, parse, digestExtractor) {
5377
+ try {
5378
+ const content = await storage.read(path);
5379
+ if (content === undefined) {
5380
+ return { status: "absent" };
5381
+ }
5382
+ const raw = JSON.parse(content);
5383
+ const cert = parse(raw);
5384
+ return { status: "valid", digest: digestExtractor(cert) };
5385
+ } catch (error) {
5386
+ if (isMissingFile(error)) {
5387
+ return { status: "absent" };
5388
+ }
5389
+ if (error instanceof ShadowCertificateError || error instanceof ReleaseCertificateError || error instanceof SoakCertificateError) {
5390
+ return { status: "invalid", code: error.code, detail: error.detail };
5391
+ }
5392
+ const msg = error instanceof Error ? error.message : String(error);
5393
+ return { status: "invalid", code: "read-error", detail: msg };
5394
+ }
5395
+ }
5396
+ async function readOpencodeVersion(serverUrl) {
5397
+ try {
5398
+ const url = new URL("/global/health", serverUrl);
5399
+ const res = await fetch(url);
5400
+ if (!res.ok) {
5401
+ return "unknown";
5402
+ }
5403
+ const body = await res.json();
5404
+ if (typeof body === "object" && body !== null && "version" in body && typeof body.version === "string" && body.version.length > 0) {
5405
+ return body.version;
5406
+ }
5407
+ return "unknown";
5408
+ } catch {
5409
+ return "unknown";
5410
+ }
5411
+ }
5412
+ async function buildGateInput(config, storage, opencodeVersion) {
5413
+ const shadow = await readAndParseCertificate(storage, SHADOW_CERT_PATH, (raw) => parseShadowCertificate(raw, opencodeVersion), (cert) => cert.digest);
5414
+ const shadowDigest = shadow.status === "valid" ? shadow.digest : "0".repeat(64);
5415
+ const release = await readAndParseCertificate(storage, RELEASE_CERT_PATH, (raw) => parseReleaseCertificate(raw, opencodeVersion, shadowDigest), (cert) => cert.digest);
5416
+ let soakLedgerDigest = "0".repeat(64);
5417
+ try {
5418
+ const recorderPorts = recorderPortsFromStorage(storage);
5419
+ const chains = await readAllChains(recorderPorts, SOAK_EVIDENCE_DIR);
5420
+ if (chains.length > 0) {
5421
+ soakLedgerDigest = canonicalLedger(chains);
5422
+ }
5423
+ } catch {}
5424
+ const soak = await readAndParseCertificate(storage, SOAK_CERT_PATH, (raw) => parseSoakCertificate(raw, DEFAULT_SOAK_POLICY, soakLedgerDigest), (cert) => cert.digest);
5425
+ const migration = "complete";
5426
+ const operatorApproval = config.graph?.operatorApproval ?? false;
5427
+ return { shadow, release, soak, migration, operatorApproval };
5428
+ }
5429
+ function recorderPortsFromStorage(storage) {
5430
+ return {
5431
+ fs: {
5432
+ readFile: async (path) => {
5433
+ const content = await storage.read(path);
5434
+ if (content === undefined) {
5435
+ throw Object.assign(new Error(`ENOENT: ${path}`), { code: "ENOENT" });
5436
+ }
5437
+ return content;
5438
+ },
5439
+ writeFile: (path, content) => storage.write(path, content),
5440
+ exists: (path) => storage.exists(path),
5441
+ readDir: (path) => storage.list(path),
5442
+ mkdir: (path) => storage.mkdir(path)
5443
+ },
5444
+ clock: { now: () => new Date().toISOString() },
5445
+ id: { randomUUID: () => crypto.randomUUID() }
5446
+ };
5447
+ }
3340
5448
  async function buildMemoryInjectorFromConfig(config, storage) {
3341
5449
  const runtime = await buildMemoryRuntimeFromConfig(config, { storage }, { requireInjectionEnabled: true });
3342
5450
  if (runtime === undefined) {
@@ -3436,7 +5544,15 @@ var server = async (ctx, rawOptions) => {
3436
5544
  const storage = createFsStorageProvider(process.cwd());
3437
5545
  const sink = createEventSink(rawOptions, storage);
3438
5546
  const injectMemory = await buildMemoryInjectorFromConfig(config, storage);
3439
- const hooks = createHooks(config, cache.get, injectMemory === undefined ? { sink } : { sink, injectMemory });
5547
+ const sessionTracker = createSessionTracker();
5548
+ const hooks = createHooks(config, cache.get, injectMemory === undefined ? {
5549
+ sink,
5550
+ onRoute: (sid, model) => sessionTracker.trackRoutedModel(sid, model)
5551
+ } : {
5552
+ sink,
5553
+ injectMemory,
5554
+ onRoute: (sid, model) => sessionTracker.trackRoutedModel(sid, model)
5555
+ });
3440
5556
  const cliDeps = createCliDeps(config, registry, telemetryPath, sessionsDir, storage);
3441
5557
  const toolcalls = createToolcallTracker({ sink });
3442
5558
  const now = () => Date.now();
@@ -3454,10 +5570,54 @@ var server = async (ctx, rawOptions) => {
3454
5570
  sink.emit(endpoint);
3455
5571
  }
3456
5572
  };
5573
+ let graphConfig = resolveGraphConfig(config, "file");
5574
+ const opencodeVersion = graphConfig.effectiveMode !== "off" ? await readOpencodeVersion(ctx.serverUrl) : undefined;
5575
+ if (isGateApplicable(graphConfig.effectiveMode)) {
5576
+ if (opencodeVersion === undefined) {
5577
+ throw new Error("[openteam] BUG: gate applicable but opencodeVersion was not read");
5578
+ }
5579
+ const gateInput = await buildGateInput(config, storage, opencodeVersion);
5580
+ const gateResult = evaluateGraphGate(gateInput);
5581
+ graphConfig = applyGraphGate(graphConfig, gateResult);
5582
+ if (graphConfig.gateDenied === true && graphConfig.gateViolations !== undefined) {
5583
+ console.warn("[openteam] ⛔ cutover gate denied active mode — degrading to off");
5584
+ for (const v of graphConfig.gateViolations) {
5585
+ console.warn(`[openteam] • ${v.code}: ${v.detail}`);
5586
+ }
5587
+ }
5588
+ }
5589
+ let shadowDeps;
5590
+ if (graphConfig.effectiveMode !== "off") {
5591
+ if (opencodeVersion === undefined) {
5592
+ throw new Error("[openteam] BUG: shadow pipeline active but opencodeVersion was not read");
5593
+ }
5594
+ const recorderPorts = recorderPortsFromStorage(storage);
5595
+ const recorderId = await ensureRecorderId(recorderPorts, ".opencode");
5596
+ shadowDeps = {
5597
+ gate: {
5598
+ mode: graphConfig.configuredMode,
5599
+ killSwitch: graphConfig.killSwitch
5600
+ },
5601
+ ports: recorderPorts,
5602
+ evidenceDir: ".opencode/soak",
5603
+ recorderId,
5604
+ opencodeVersion,
5605
+ criticalCodes: DEFAULT_SOAK_POLICY.criticalDivergenceCodes
5606
+ };
5607
+ }
5608
+ const graphSurface = createGraphTool({
5609
+ policy: {
5610
+ graph: graphConfig,
5611
+ allowList: []
5612
+ },
5613
+ shadow: shadowDeps,
5614
+ tracker: sessionTracker
5615
+ });
3457
5616
  return {
3458
5617
  ...hooks,
3459
5618
  tool: {
3460
- openteam: createCommandTool(cliDeps)
5619
+ openteam: createCommandTool(cliDeps),
5620
+ "openteam-graph": graphSurface.definition
3461
5621
  },
3462
5622
  "tool.execute.before": async (input) => {
3463
5623
  toolcalls.before(input);
@@ -3472,6 +5632,7 @@ var server = async (ctx, rawOptions) => {
3472
5632
  const activity = activityEventFrom(event, now);
3473
5633
  if (activity !== undefined) {
3474
5634
  announceEndpoint(activity.sessionID);
5635
+ sessionTracker.trackSession(activity.sessionID);
3475
5636
  sink.emit(activity);
3476
5637
  }
3477
5638
  if (event.type === "message.updated") {
@@ -3479,6 +5640,7 @@ var server = async (ctx, rawOptions) => {
3479
5640
  const message = info === undefined ? undefined : messageEventFrom(info, now);
3480
5641
  if (message !== undefined) {
3481
5642
  announceEndpoint(message.sessionID);
5643
+ sessionTracker.trackSession(message.sessionID);
3482
5644
  sink.emit(message);
3483
5645
  }
3484
5646
  }
@@ -3492,10 +5654,13 @@ var plugin = {
3492
5654
  var src_default = plugin;
3493
5655
  export {
3494
5656
  server,
5657
+ recorderPortsFromStorage,
5658
+ readOpencodeVersion,
3495
5659
  logTelemetryError,
3496
5660
  logAvailabilityRefreshError,
3497
5661
  isMissingFile,
3498
5662
  src_default as default,
3499
5663
  createEventSink,
3500
- createCliDeps
5664
+ createCliDeps,
5665
+ buildGateInput
3501
5666
  };