@jmanuelcorral/openteam 0.1.42 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.opencode/openteam.example.json +6 -1
- package/README.es.md +5 -5
- package/README.md +5 -5
- package/dist/capabilities/types.d.ts +4 -4
- package/dist/classifier/types.d.ts +3 -3
- package/dist/cli.js +11 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/graph.d.ts +28 -2
- package/dist/commands/graph.d.ts.map +1 -1
- package/dist/config/graphFeatureGate.d.ts +101 -0
- package/dist/config/graphFeatureGate.d.ts.map +1 -0
- package/dist/config/migrations.d.ts.map +1 -1
- package/dist/config/resolve.d.ts +20 -1
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/schema.d.ts +22 -19
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/console/render.d.ts.map +1 -1
- package/dist/context/redaction.d.ts +23 -0
- package/dist/context/redaction.d.ts.map +1 -1
- package/dist/context/schema.d.ts +3 -3
- package/dist/contract/opencode.d.ts +2 -2
- package/dist/contract/opencode.d.ts.map +1 -1
- package/dist/graph/certificate.d.ts +515 -0
- package/dist/graph/certificate.d.ts.map +1 -0
- package/dist/graph/ids.d.ts +9 -0
- package/dist/graph/ids.d.ts.map +1 -1
- package/dist/graph/privacyPolicy.d.ts.map +1 -1
- package/dist/graph/reducer.d.ts.map +1 -1
- package/dist/graph/replay.d.ts.map +1 -1
- package/dist/graph/reviewerPolicy.d.ts.map +1 -1
- package/dist/graph/schema.d.ts +33 -33
- package/dist/graph/schema.d.ts.map +1 -1
- package/dist/graph/soakLedger.d.ts +167 -0
- package/dist/graph/soakLedger.d.ts.map +1 -0
- package/dist/graph/types.d.ts +2 -0
- package/dist/graph/types.d.ts.map +1 -1
- package/dist/graph/validate.d.ts.map +1 -1
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2452 -279
- package/dist/local/registry.d.ts.map +1 -1
- package/dist/memory/types.d.ts +2 -2
- package/dist/orchestrator/graphFacade.d.ts +39 -0
- package/dist/orchestrator/graphFacade.d.ts.map +1 -0
- package/dist/orchestrator/graphIngress.d.ts +136 -0
- package/dist/orchestrator/graphIngress.d.ts.map +1 -0
- package/dist/orchestrator/graphShadow.d.ts +121 -0
- package/dist/orchestrator/graphShadow.d.ts.map +1 -0
- package/dist/orchestrator/ledger.d.ts +11 -30
- package/dist/orchestrator/ledger.d.ts.map +1 -1
- package/dist/orchestrator/opencodeSessionAdapter.d.ts.map +1 -1
- package/dist/orchestrator/roles.d.ts +3 -3
- package/dist/orchestrator/sddCompiler.d.ts +31 -3
- package/dist/orchestrator/sddCompiler.d.ts.map +1 -1
- package/dist/orchestrator/worktreeAdapter.d.ts.map +1 -1
- package/dist/orchestrator/worktreeDispatch.d.ts.map +1 -1
- package/dist/plugin/graphShadowIngress.d.ts +165 -0
- package/dist/plugin/graphShadowIngress.d.ts.map +1 -0
- package/dist/plugin/graphTool.d.ts +63 -0
- package/dist/plugin/graphTool.d.ts.map +1 -0
- package/dist/plugin/hooks.d.ts +6 -0
- package/dist/plugin/hooks.d.ts.map +1 -1
- package/dist/plugin/legacyRunProducer.d.ts +60 -0
- package/dist/plugin/legacyRunProducer.d.ts.map +1 -0
- package/dist/plugin/reportRunSchema.d.ts +60 -0
- package/dist/plugin/reportRunSchema.d.ts.map +1 -0
- package/dist/plugin/sessionTracker.d.ts +42 -0
- package/dist/plugin/sessionTracker.d.ts.map +1 -0
- package/dist/plugin/shadowPipeline.d.ts +49 -0
- package/dist/plugin/shadowPipeline.d.ts.map +1 -0
- package/dist/plugin/soakObserver.d.ts +73 -0
- package/dist/plugin/soakObserver.d.ts.map +1 -0
- package/dist/storage/graph/codec.d.ts.map +1 -1
- package/dist/storage/graph/migrateLegacyBriefs.d.ts.map +1 -1
- package/dist/storage/graph/migration.d.ts +91 -0
- package/dist/storage/graph/migration.d.ts.map +1 -0
- package/dist/storage/graph/provider.d.ts +1 -0
- package/dist/storage/graph/provider.d.ts.map +1 -1
- package/dist/storage/graph/recovery.d.ts.map +1 -1
- package/dist/storage/graph/runRegistry.d.ts.map +1 -1
- package/dist/storage/graph/soakRecorder.d.ts +59 -0
- package/dist/storage/graph/soakRecorder.d.ts.map +1 -0
- package/dist/storage/graph/workspaceLease.d.ts.map +1 -1
- package/dist/storage/graph/worktreeRegistry.d.ts.map +1 -1
- package/dist/storage/graph/writer.d.ts +15 -0
- package/dist/storage/graph/writer.d.ts.map +1 -1
- package/dist/storage/path.d.ts.map +1 -1
- package/dist/telemetry/events.d.ts +12 -12
- package/dist/telemetry/graphRuntimeHealth.d.ts +53 -0
- package/dist/telemetry/graphRuntimeHealth.d.ts.map +1 -0
- package/dist/telemetry/graphView.d.ts +1 -0
- package/dist/telemetry/graphView.d.ts.map +1 -1
- package/dist/telemetry/types.d.ts +4 -4
- package/package.json +7 -5
- package/dist/orchestrator/sdd.d.ts +0 -126
- package/dist/orchestrator/sdd.d.ts.map +0 -1
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,733 @@ 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/contract/opencode.ts
|
|
696
|
+
var SUPPORTED_OPENCODE_VERSIONS = ["1.17.13", "1.18.18"];
|
|
697
|
+
|
|
698
|
+
// src/graph/soakLedger.ts
|
|
699
|
+
import { z as z2 } from "zod";
|
|
700
|
+
var Sha256Schema = z2.string().regex(/^[0-9a-f]{64}$/);
|
|
701
|
+
var SoakPlatformSchema = z2.enum(["linux", "win32", "darwin"]);
|
|
702
|
+
var PrivacyPairSchema = z2.object({
|
|
703
|
+
surfacesScanned: z2.number().int().nonnegative(),
|
|
704
|
+
rawFindings: z2.number().int().nonnegative()
|
|
705
|
+
}).strict();
|
|
706
|
+
var DuplicateEffectPairSchema = z2.object({
|
|
707
|
+
effectsExamined: z2.number().int().nonnegative(),
|
|
708
|
+
duplicatesFound: z2.number().int().nonnegative()
|
|
709
|
+
}).strict();
|
|
710
|
+
var ModelVerificationPairSchema = z2.object({
|
|
711
|
+
nodesChecked: z2.number().int().nonnegative(),
|
|
712
|
+
unverified: z2.number().int().nonnegative(),
|
|
713
|
+
mismatched: z2.number().int().nonnegative()
|
|
714
|
+
}).strict();
|
|
715
|
+
var SoakObservationSchema = z2.object({
|
|
716
|
+
version: z2.literal(1),
|
|
717
|
+
seq: z2.number().int().nonnegative(),
|
|
718
|
+
timestamp: z2.string().datetime(),
|
|
719
|
+
platform: SoakPlatformSchema,
|
|
720
|
+
opencodeVersion: z2.string().min(1),
|
|
721
|
+
provenance: z2.enum(["genuine-usage", "ci-synthetic"]),
|
|
722
|
+
traceDigest: Sha256Schema,
|
|
723
|
+
criticalDivergences: z2.number().int().nonnegative().nullable(),
|
|
724
|
+
privacy: PrivacyPairSchema.nullable(),
|
|
725
|
+
duplicateEffects: DuplicateEffectPairSchema.nullable(),
|
|
726
|
+
modelVerification: ModelVerificationPairSchema.nullable(),
|
|
727
|
+
chainDigest: Sha256Schema
|
|
728
|
+
}).strict();
|
|
729
|
+
function canonicalObservationPreimage(obs) {
|
|
730
|
+
return JSON.stringify({
|
|
731
|
+
version: obs.version,
|
|
732
|
+
seq: obs.seq,
|
|
733
|
+
timestamp: obs.timestamp,
|
|
734
|
+
platform: obs.platform,
|
|
735
|
+
opencodeVersion: obs.opencodeVersion,
|
|
736
|
+
provenance: obs.provenance,
|
|
737
|
+
traceDigest: obs.traceDigest,
|
|
738
|
+
criticalDivergences: obs.criticalDivergences,
|
|
739
|
+
privacy: obs.privacy,
|
|
740
|
+
duplicateEffects: obs.duplicateEffects,
|
|
741
|
+
modelVerification: obs.modelVerification
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
function validateChain(observations) {
|
|
745
|
+
if (observations.length === 0) {
|
|
746
|
+
return { valid: true, observations: [] };
|
|
747
|
+
}
|
|
748
|
+
let prevChainDigest = "0".repeat(64);
|
|
749
|
+
for (let i = 0;i < observations.length; i++) {
|
|
750
|
+
const obs = observations[i];
|
|
751
|
+
if (obs.seq !== i) {
|
|
752
|
+
return { valid: false, observations };
|
|
753
|
+
}
|
|
754
|
+
if (i > 0) {
|
|
755
|
+
const prev = observations[i - 1];
|
|
756
|
+
if (obs.timestamp <= prev.timestamp) {
|
|
757
|
+
return { valid: false, observations };
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
const preimage = canonicalObservationPreimage(obs);
|
|
761
|
+
const expectedDigest = sha256Hex(prevChainDigest + preimage);
|
|
762
|
+
if (obs.chainDigest !== expectedDigest) {
|
|
763
|
+
return { valid: false, observations };
|
|
764
|
+
}
|
|
765
|
+
prevChainDigest = obs.chainDigest;
|
|
766
|
+
}
|
|
767
|
+
return { valid: true, observations };
|
|
768
|
+
}
|
|
769
|
+
function canonicalLedger(chains) {
|
|
770
|
+
const validChains = [];
|
|
771
|
+
for (const chain of chains) {
|
|
772
|
+
const result = validateChain(chain);
|
|
773
|
+
if (result.valid && chain.length > 0) {
|
|
774
|
+
validChains.push(chain);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
validChains.sort((a, b) => {
|
|
778
|
+
const aDigest = a[0].chainDigest;
|
|
779
|
+
const bDigest = b[0].chainDigest;
|
|
780
|
+
return aDigest < bDigest ? -1 : aDigest > bDigest ? 1 : 0;
|
|
781
|
+
});
|
|
782
|
+
const lines = [];
|
|
783
|
+
for (const chain of validChains) {
|
|
784
|
+
for (const obs of chain) {
|
|
785
|
+
lines.push(JSON.stringify(obs));
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return sha256Hex(lines.join(`
|
|
789
|
+
`));
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// src/graph/certificate.ts
|
|
793
|
+
var SHADOW_CERTIFICATE_MIN_RUNS = 1000;
|
|
794
|
+
var DivergenceCodeSchema = z3.enum([
|
|
795
|
+
"status",
|
|
796
|
+
"order",
|
|
797
|
+
"roles",
|
|
798
|
+
"models",
|
|
799
|
+
"fixes",
|
|
800
|
+
"outcome"
|
|
801
|
+
]);
|
|
802
|
+
var ProvenanceSchema = z3.enum([
|
|
803
|
+
"recomputed",
|
|
804
|
+
"compiled",
|
|
805
|
+
"validated",
|
|
806
|
+
"carried"
|
|
807
|
+
]);
|
|
808
|
+
var EvidenceSchema = z3.object({
|
|
809
|
+
opencodeVersion: z3.string().min(1),
|
|
810
|
+
parity: z3.object({
|
|
811
|
+
runs: z3.number().int().nonnegative(),
|
|
812
|
+
match: z3.number().int().nonnegative(),
|
|
813
|
+
divergent: z3.number().int().nonnegative(),
|
|
814
|
+
inconclusive: z3.number().int().nonnegative(),
|
|
815
|
+
divergences: z3.array(DivergenceCodeSchema)
|
|
816
|
+
}).strict(),
|
|
817
|
+
legacy: z3.object({
|
|
818
|
+
observed: z3.number().int().nonnegative(),
|
|
819
|
+
duplicated: z3.number().int().nonnegative()
|
|
820
|
+
}).strict(),
|
|
821
|
+
privacy: z3.object({
|
|
822
|
+
surfacesScanned: z3.number().int().nonnegative(),
|
|
823
|
+
rawFindings: z3.number().int().nonnegative()
|
|
824
|
+
}).strict(),
|
|
825
|
+
overhead: z3.object({
|
|
826
|
+
p95Millis: z3.number().nonnegative(),
|
|
827
|
+
budgetMillis: z3.number().positive(),
|
|
828
|
+
samples: z3.number().int().nonnegative()
|
|
829
|
+
}).strict(),
|
|
830
|
+
provenance: z3.record(DivergenceCodeSchema, ProvenanceSchema)
|
|
831
|
+
}).strict();
|
|
832
|
+
var CertificateSchema = z3.object({
|
|
833
|
+
version: z3.literal(1),
|
|
834
|
+
certificate: z3.literal("graph-shadow"),
|
|
835
|
+
opencodeVersion: z3.string().min(1),
|
|
836
|
+
evidence: EvidenceSchema,
|
|
837
|
+
verdict: z3.enum(["pass", "fail"]),
|
|
838
|
+
failedGates: z3.array(z3.enum([
|
|
839
|
+
"sample",
|
|
840
|
+
"parity",
|
|
841
|
+
"one-legacy-execution",
|
|
842
|
+
"privacy",
|
|
843
|
+
"overhead"
|
|
844
|
+
])),
|
|
845
|
+
digest: z3.string().regex(/^[0-9a-f]{64}$/)
|
|
846
|
+
}).strict();
|
|
847
|
+
|
|
848
|
+
class ShadowCertificateError extends Error {
|
|
849
|
+
code;
|
|
850
|
+
detail;
|
|
851
|
+
constructor(code, detail) {
|
|
852
|
+
super(`${code}: ${detail}`);
|
|
853
|
+
this.name = "ShadowCertificateError";
|
|
854
|
+
this.code = code;
|
|
855
|
+
this.detail = detail;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
function evaluateGates(evidence) {
|
|
859
|
+
const failed = [];
|
|
860
|
+
if (evidence.parity.runs < SHADOW_CERTIFICATE_MIN_RUNS) {
|
|
861
|
+
failed.push("sample");
|
|
862
|
+
}
|
|
863
|
+
if (evidence.parity.divergent > 0 || evidence.parity.inconclusive > 0 || evidence.parity.divergences.length > 0 || evidence.parity.match !== evidence.parity.runs) {
|
|
864
|
+
failed.push("parity");
|
|
865
|
+
}
|
|
866
|
+
if (evidence.legacy.duplicated > 0 || evidence.legacy.observed < 1) {
|
|
867
|
+
failed.push("one-legacy-execution");
|
|
868
|
+
}
|
|
869
|
+
if (evidence.privacy.rawFindings > 0 || evidence.privacy.surfacesScanned < 1) {
|
|
870
|
+
failed.push("privacy");
|
|
871
|
+
}
|
|
872
|
+
if (evidence.overhead.samples < 1 || evidence.overhead.p95Millis > evidence.overhead.budgetMillis) {
|
|
873
|
+
failed.push("overhead");
|
|
874
|
+
}
|
|
875
|
+
return failed;
|
|
876
|
+
}
|
|
877
|
+
function canonicalCertificate(certificate) {
|
|
878
|
+
const evidence = certificate.evidence;
|
|
879
|
+
return JSON.stringify({
|
|
880
|
+
version: certificate.version,
|
|
881
|
+
certificate: certificate.certificate,
|
|
882
|
+
opencodeVersion: certificate.opencodeVersion,
|
|
883
|
+
verdict: certificate.verdict,
|
|
884
|
+
failedGates: [...certificate.failedGates],
|
|
885
|
+
evidence: {
|
|
886
|
+
opencodeVersion: evidence.opencodeVersion,
|
|
887
|
+
parity: {
|
|
888
|
+
runs: evidence.parity.runs,
|
|
889
|
+
match: evidence.parity.match,
|
|
890
|
+
divergent: evidence.parity.divergent,
|
|
891
|
+
inconclusive: evidence.parity.inconclusive,
|
|
892
|
+
divergences: [...evidence.parity.divergences].sort()
|
|
893
|
+
},
|
|
894
|
+
legacy: {
|
|
895
|
+
observed: evidence.legacy.observed,
|
|
896
|
+
duplicated: evidence.legacy.duplicated
|
|
897
|
+
},
|
|
898
|
+
privacy: {
|
|
899
|
+
surfacesScanned: evidence.privacy.surfacesScanned,
|
|
900
|
+
rawFindings: evidence.privacy.rawFindings
|
|
901
|
+
},
|
|
902
|
+
overhead: {
|
|
903
|
+
p95Millis: evidence.overhead.p95Millis,
|
|
904
|
+
budgetMillis: evidence.overhead.budgetMillis,
|
|
905
|
+
samples: evidence.overhead.samples
|
|
906
|
+
},
|
|
907
|
+
provenance: Object.fromEntries(Object.entries(evidence.provenance).sort(([a], [b]) => a < b ? -1 : 1))
|
|
908
|
+
}
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
function sameGates(a, b) {
|
|
912
|
+
return a.length === b.length && a.every((gate, index) => gate === b[index]);
|
|
913
|
+
}
|
|
914
|
+
function parseShadowCertificate(value, opencodeVersion) {
|
|
915
|
+
const parsed = CertificateSchema.safeParse(value);
|
|
916
|
+
if (!parsed.success) {
|
|
917
|
+
const detail = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.code}`).join("; ");
|
|
918
|
+
throw new ShadowCertificateError("invalid-certificate", detail);
|
|
919
|
+
}
|
|
920
|
+
const certificate = parsed.data;
|
|
921
|
+
const failedGates = evaluateGates(certificate.evidence);
|
|
922
|
+
const verdict = failedGates.length === 0 ? "pass" : "fail";
|
|
923
|
+
if (certificate.verdict !== verdict || !sameGates(certificate.failedGates, failedGates)) {
|
|
924
|
+
throw new ShadowCertificateError("verdict-mismatch", `evidence yields "${verdict}" (${failedGates.join(",") || "no failed gate"})`);
|
|
925
|
+
}
|
|
926
|
+
const { digest, ...unsigned } = certificate;
|
|
927
|
+
if (sha256Hex(canonicalCertificate(unsigned)) !== digest) {
|
|
928
|
+
throw new ShadowCertificateError("digest-mismatch", "certificate digest does not cover the presented evidence");
|
|
929
|
+
}
|
|
930
|
+
if (certificate.opencodeVersion !== opencodeVersion) {
|
|
931
|
+
throw new ShadowCertificateError("opencode-mismatch", `certified for "${certificate.opencodeVersion}", expected "${opencodeVersion}"`);
|
|
932
|
+
}
|
|
933
|
+
return certificate;
|
|
934
|
+
}
|
|
935
|
+
var ReleaseDeterminismSchema = z3.object({ runs: z3.number().int().nonnegative(), allMatch: z3.boolean() }).strict();
|
|
936
|
+
var ReleaseCrashRecoverySchema = z3.object({
|
|
937
|
+
scenarios: z3.number().int().nonnegative(),
|
|
938
|
+
allConverge: z3.boolean()
|
|
939
|
+
}).strict();
|
|
940
|
+
var ReleaseReplayEquivalenceSchema = z3.object({
|
|
941
|
+
checks: z3.number().int().nonnegative(),
|
|
942
|
+
allEquivalent: z3.boolean()
|
|
943
|
+
}).strict();
|
|
944
|
+
var ReleasePrivacySchema = z3.object({
|
|
945
|
+
surfacesScanned: z3.number().int().nonnegative(),
|
|
946
|
+
rawFindings: z3.number().int().nonnegative()
|
|
947
|
+
}).strict();
|
|
948
|
+
var ReleaseContractSchema = z3.object({
|
|
949
|
+
roundTrips: z3.number().int().nonnegative(),
|
|
950
|
+
allSettled: z3.boolean()
|
|
951
|
+
}).strict();
|
|
952
|
+
var ReleaseMigrationSchema = z3.object({
|
|
953
|
+
cases: z3.number().int().nonnegative(),
|
|
954
|
+
allSucceeded: z3.boolean()
|
|
955
|
+
}).strict();
|
|
956
|
+
var ReleaseHealthSchema = z3.object({
|
|
957
|
+
samples: z3.number().int().nonnegative(),
|
|
958
|
+
bounded: z3.boolean(),
|
|
959
|
+
idempotent: z3.boolean()
|
|
960
|
+
}).strict();
|
|
961
|
+
var ReleasePerformanceSchema = z3.object({
|
|
962
|
+
p95Millis: z3.number().nonnegative(),
|
|
963
|
+
budgetMillis: z3.number().positive(),
|
|
964
|
+
samples: z3.number().int().nonnegative()
|
|
965
|
+
}).strict();
|
|
966
|
+
var ReleaseEvidenceSchema = z3.object({
|
|
967
|
+
opencodeVersion: z3.string().min(1),
|
|
968
|
+
platform: z3.enum(["linux", "win32"]),
|
|
969
|
+
shadowCertificateDigest: z3.string().regex(/^[0-9a-f]{64}$/),
|
|
970
|
+
determinism: ReleaseDeterminismSchema,
|
|
971
|
+
crashRecovery: ReleaseCrashRecoverySchema,
|
|
972
|
+
replayEquivalence: ReleaseReplayEquivalenceSchema,
|
|
973
|
+
privacy: ReleasePrivacySchema,
|
|
974
|
+
contract: ReleaseContractSchema,
|
|
975
|
+
migration: ReleaseMigrationSchema,
|
|
976
|
+
health: ReleaseHealthSchema,
|
|
977
|
+
performance: ReleasePerformanceSchema
|
|
978
|
+
}).strict();
|
|
979
|
+
var ReleaseCertificateSchema = z3.object({
|
|
980
|
+
version: z3.literal(1),
|
|
981
|
+
certificate: z3.literal("graph-release"),
|
|
982
|
+
platform: z3.enum(["linux", "win32"]),
|
|
983
|
+
opencodeVersion: z3.string().min(1),
|
|
984
|
+
shadowCertificateDigest: z3.string().regex(/^[0-9a-f]{64}$/),
|
|
985
|
+
evidence: ReleaseEvidenceSchema,
|
|
986
|
+
verdict: z3.enum(["pass", "fail"]),
|
|
987
|
+
failedGates: z3.array(z3.enum([
|
|
988
|
+
"shadow-valid",
|
|
989
|
+
"determinism",
|
|
990
|
+
"crash-recovery",
|
|
991
|
+
"replay-equivalence",
|
|
992
|
+
"privacy",
|
|
993
|
+
"contract",
|
|
994
|
+
"migration",
|
|
995
|
+
"health",
|
|
996
|
+
"performance"
|
|
997
|
+
])),
|
|
998
|
+
digest: z3.string().regex(/^[0-9a-f]{64}$/)
|
|
999
|
+
}).strict();
|
|
1000
|
+
|
|
1001
|
+
class ReleaseCertificateError extends Error {
|
|
1002
|
+
code;
|
|
1003
|
+
detail;
|
|
1004
|
+
constructor(code, detail) {
|
|
1005
|
+
super(`${code}: ${detail}`);
|
|
1006
|
+
this.name = "ReleaseCertificateError";
|
|
1007
|
+
this.code = code;
|
|
1008
|
+
this.detail = detail;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
function evaluateReleaseGates(evidence, expectedShadowDigest) {
|
|
1012
|
+
const failed = [];
|
|
1013
|
+
if (evidence.shadowCertificateDigest !== expectedShadowDigest) {
|
|
1014
|
+
failed.push("shadow-valid");
|
|
1015
|
+
}
|
|
1016
|
+
if (evidence.determinism.runs < 1 || !evidence.determinism.allMatch) {
|
|
1017
|
+
failed.push("determinism");
|
|
1018
|
+
}
|
|
1019
|
+
if (evidence.crashRecovery.scenarios < 1 || !evidence.crashRecovery.allConverge) {
|
|
1020
|
+
failed.push("crash-recovery");
|
|
1021
|
+
}
|
|
1022
|
+
if (evidence.replayEquivalence.checks < 1 || !evidence.replayEquivalence.allEquivalent) {
|
|
1023
|
+
failed.push("replay-equivalence");
|
|
1024
|
+
}
|
|
1025
|
+
if (evidence.privacy.surfacesScanned < 1 || evidence.privacy.rawFindings > 0) {
|
|
1026
|
+
failed.push("privacy");
|
|
1027
|
+
}
|
|
1028
|
+
if (evidence.contract.roundTrips < 1 || !evidence.contract.allSettled) {
|
|
1029
|
+
failed.push("contract");
|
|
1030
|
+
}
|
|
1031
|
+
if (evidence.migration.cases < 1 || !evidence.migration.allSucceeded) {
|
|
1032
|
+
failed.push("migration");
|
|
1033
|
+
}
|
|
1034
|
+
if (evidence.health.samples < 1 || !evidence.health.bounded || !evidence.health.idempotent) {
|
|
1035
|
+
failed.push("health");
|
|
1036
|
+
}
|
|
1037
|
+
if (evidence.performance.samples < 1 || evidence.performance.p95Millis > evidence.performance.budgetMillis) {
|
|
1038
|
+
failed.push("performance");
|
|
1039
|
+
}
|
|
1040
|
+
return failed;
|
|
1041
|
+
}
|
|
1042
|
+
function canonicalReleaseCertificate(certificate) {
|
|
1043
|
+
const ev = certificate.evidence;
|
|
1044
|
+
return JSON.stringify({
|
|
1045
|
+
version: certificate.version,
|
|
1046
|
+
certificate: certificate.certificate,
|
|
1047
|
+
platform: certificate.platform,
|
|
1048
|
+
opencodeVersion: certificate.opencodeVersion,
|
|
1049
|
+
shadowCertificateDigest: certificate.shadowCertificateDigest,
|
|
1050
|
+
verdict: certificate.verdict,
|
|
1051
|
+
failedGates: [...certificate.failedGates],
|
|
1052
|
+
evidence: {
|
|
1053
|
+
opencodeVersion: ev.opencodeVersion,
|
|
1054
|
+
platform: ev.platform,
|
|
1055
|
+
shadowCertificateDigest: ev.shadowCertificateDigest,
|
|
1056
|
+
determinism: {
|
|
1057
|
+
runs: ev.determinism.runs,
|
|
1058
|
+
allMatch: ev.determinism.allMatch
|
|
1059
|
+
},
|
|
1060
|
+
crashRecovery: {
|
|
1061
|
+
scenarios: ev.crashRecovery.scenarios,
|
|
1062
|
+
allConverge: ev.crashRecovery.allConverge
|
|
1063
|
+
},
|
|
1064
|
+
replayEquivalence: {
|
|
1065
|
+
checks: ev.replayEquivalence.checks,
|
|
1066
|
+
allEquivalent: ev.replayEquivalence.allEquivalent
|
|
1067
|
+
},
|
|
1068
|
+
privacy: {
|
|
1069
|
+
surfacesScanned: ev.privacy.surfacesScanned,
|
|
1070
|
+
rawFindings: ev.privacy.rawFindings
|
|
1071
|
+
},
|
|
1072
|
+
contract: {
|
|
1073
|
+
roundTrips: ev.contract.roundTrips,
|
|
1074
|
+
allSettled: ev.contract.allSettled
|
|
1075
|
+
},
|
|
1076
|
+
migration: {
|
|
1077
|
+
cases: ev.migration.cases,
|
|
1078
|
+
allSucceeded: ev.migration.allSucceeded
|
|
1079
|
+
},
|
|
1080
|
+
health: {
|
|
1081
|
+
samples: ev.health.samples,
|
|
1082
|
+
bounded: ev.health.bounded,
|
|
1083
|
+
idempotent: ev.health.idempotent
|
|
1084
|
+
},
|
|
1085
|
+
performance: {
|
|
1086
|
+
p95Millis: ev.performance.p95Millis,
|
|
1087
|
+
budgetMillis: ev.performance.budgetMillis,
|
|
1088
|
+
samples: ev.performance.samples
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
function sameReleaseGates(a, b) {
|
|
1094
|
+
return a.length === b.length && a.every((gate, index) => gate === b[index]);
|
|
1095
|
+
}
|
|
1096
|
+
function parseReleaseCertificate(value, opencodeVersion, expectedShadowDigest) {
|
|
1097
|
+
const parsed = ReleaseCertificateSchema.safeParse(value);
|
|
1098
|
+
if (!parsed.success) {
|
|
1099
|
+
const detail = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.code}`).join("; ");
|
|
1100
|
+
throw new ReleaseCertificateError("invalid-certificate", detail);
|
|
1101
|
+
}
|
|
1102
|
+
const certificate = parsed.data;
|
|
1103
|
+
const inconsistencies = [];
|
|
1104
|
+
if (certificate.platform !== certificate.evidence.platform) {
|
|
1105
|
+
inconsistencies.push("platform");
|
|
1106
|
+
}
|
|
1107
|
+
if (certificate.opencodeVersion !== certificate.evidence.opencodeVersion) {
|
|
1108
|
+
inconsistencies.push("opencodeVersion");
|
|
1109
|
+
}
|
|
1110
|
+
if (certificate.shadowCertificateDigest !== certificate.evidence.shadowCertificateDigest) {
|
|
1111
|
+
inconsistencies.push("shadowCertificateDigest");
|
|
1112
|
+
}
|
|
1113
|
+
if (inconsistencies.length > 0) {
|
|
1114
|
+
throw new ReleaseCertificateError("field-inconsistency", `top-level/evidence divergence: ${inconsistencies.join(", ")}`);
|
|
1115
|
+
}
|
|
1116
|
+
const failedGates = evaluateReleaseGates(certificate.evidence, expectedShadowDigest);
|
|
1117
|
+
const verdict = failedGates.length === 0 ? "pass" : "fail";
|
|
1118
|
+
if (certificate.verdict !== verdict || !sameReleaseGates(certificate.failedGates, failedGates)) {
|
|
1119
|
+
throw new ReleaseCertificateError("verdict-mismatch", `evidence yields "${verdict}" (${failedGates.join(",") || "no failed gate"})`);
|
|
1120
|
+
}
|
|
1121
|
+
const { digest, ...unsigned } = certificate;
|
|
1122
|
+
if (sha256Hex(canonicalReleaseCertificate(unsigned)) !== digest) {
|
|
1123
|
+
throw new ReleaseCertificateError("digest-mismatch", "certificate digest does not cover the presented evidence");
|
|
1124
|
+
}
|
|
1125
|
+
if (certificate.opencodeVersion !== opencodeVersion) {
|
|
1126
|
+
throw new ReleaseCertificateError("opencode-mismatch", `certified for "${certificate.opencodeVersion}", expected "${opencodeVersion}"`);
|
|
1127
|
+
}
|
|
1128
|
+
return certificate;
|
|
1129
|
+
}
|
|
1130
|
+
var ShadowDivergenceCodeSchema = z3.enum([
|
|
1131
|
+
"status",
|
|
1132
|
+
"order",
|
|
1133
|
+
"roles",
|
|
1134
|
+
"models",
|
|
1135
|
+
"fixes",
|
|
1136
|
+
"outcome"
|
|
1137
|
+
]);
|
|
1138
|
+
var RECOMPUTED_DIVERGENCE_CODES = ["fixes"];
|
|
1139
|
+
var RecomputedDivergenceCodeSchema = z3.enum(RECOMPUTED_DIVERGENCE_CODES);
|
|
1140
|
+
var SoakPolicySchema = z3.object({
|
|
1141
|
+
minimumGenuineObservations: z3.number().int().positive(),
|
|
1142
|
+
minimumDistinctDays: z3.number().int().positive(),
|
|
1143
|
+
requireGenuineUsageOnAllPlatforms: z3.boolean(),
|
|
1144
|
+
supportedOpencodeVersions: z3.array(z3.string().min(1)).min(1),
|
|
1145
|
+
criticalDivergenceCodes: z3.array(RecomputedDivergenceCodeSchema).min(1)
|
|
1146
|
+
}).strict();
|
|
1147
|
+
var DEFAULT_SOAK_POLICY = {
|
|
1148
|
+
minimumGenuineObservations: 100,
|
|
1149
|
+
minimumDistinctDays: 14,
|
|
1150
|
+
requireGenuineUsageOnAllPlatforms: true,
|
|
1151
|
+
supportedOpencodeVersions: [...SUPPORTED_OPENCODE_VERSIONS],
|
|
1152
|
+
criticalDivergenceCodes: ["fixes"]
|
|
1153
|
+
};
|
|
1154
|
+
var SoakPlatformEnumSchema = z3.enum(["linux", "win32", "darwin"]);
|
|
1155
|
+
var SoakPrivacyPairEvidenceSchema = z3.object({
|
|
1156
|
+
surfacesScanned: z3.number().int().nonnegative(),
|
|
1157
|
+
rawFindings: z3.number().int().nonnegative()
|
|
1158
|
+
}).strict();
|
|
1159
|
+
var SoakDuplicateEffectPairEvidenceSchema = z3.object({
|
|
1160
|
+
effectsExamined: z3.number().int().nonnegative(),
|
|
1161
|
+
duplicatesFound: z3.number().int().nonnegative()
|
|
1162
|
+
}).strict();
|
|
1163
|
+
var SoakModelVerificationPairEvidenceSchema = z3.object({
|
|
1164
|
+
nodesChecked: z3.number().int().nonnegative(),
|
|
1165
|
+
unverified: z3.number().int().nonnegative(),
|
|
1166
|
+
mismatched: z3.number().int().nonnegative()
|
|
1167
|
+
}).strict();
|
|
1168
|
+
var SoakEvidenceSchema = z3.object({
|
|
1169
|
+
totalObservations: z3.number().int().nonnegative(),
|
|
1170
|
+
genuineUsageObservations: z3.number().int().nonnegative(),
|
|
1171
|
+
ciSyntheticObservations: z3.number().int().nonnegative(),
|
|
1172
|
+
recorderCount: z3.number().int().nonnegative(),
|
|
1173
|
+
ineligibleObservations: z3.number().int().nonnegative(),
|
|
1174
|
+
firstTimestamp: z3.string().datetime().nullable(),
|
|
1175
|
+
lastTimestamp: z3.string().datetime().nullable(),
|
|
1176
|
+
distinctDays: z3.number().int().nonnegative(),
|
|
1177
|
+
criticalDivergences: z3.number().int().nonnegative().nullable(),
|
|
1178
|
+
privacy: SoakPrivacyPairEvidenceSchema.nullable(),
|
|
1179
|
+
duplicateEffects: SoakDuplicateEffectPairEvidenceSchema.nullable(),
|
|
1180
|
+
modelVerification: SoakModelVerificationPairEvidenceSchema.nullable(),
|
|
1181
|
+
platformsGenuine: z3.array(SoakPlatformEnumSchema),
|
|
1182
|
+
platformsSynthetic: z3.array(SoakPlatformEnumSchema),
|
|
1183
|
+
opencodeVersionsObserved: z3.array(z3.string().min(1)),
|
|
1184
|
+
invalidChains: z3.number().int().nonnegative()
|
|
1185
|
+
}).strict();
|
|
1186
|
+
var SoakCertificateSchema = z3.object({
|
|
1187
|
+
version: z3.literal(1),
|
|
1188
|
+
certificate: z3.literal("graph-soak"),
|
|
1189
|
+
trustBoundary: z3.literal("contributors"),
|
|
1190
|
+
policy: SoakPolicySchema,
|
|
1191
|
+
evidence: SoakEvidenceSchema,
|
|
1192
|
+
verdict: z3.enum(["pass", "fail"]),
|
|
1193
|
+
failedGates: z3.array(z3.enum([
|
|
1194
|
+
"minimum-observations",
|
|
1195
|
+
"minimum-duration",
|
|
1196
|
+
"chain-integrity",
|
|
1197
|
+
"zero-critical-divergence",
|
|
1198
|
+
"zero-privacy-breach",
|
|
1199
|
+
"zero-duplicate-effect",
|
|
1200
|
+
"zero-model-mismatch",
|
|
1201
|
+
"os-diversity",
|
|
1202
|
+
"opencode-pin"
|
|
1203
|
+
])),
|
|
1204
|
+
ledgerDigest: z3.string().regex(/^[0-9a-f]{64}$/),
|
|
1205
|
+
digest: z3.string().regex(/^[0-9a-f]{64}$/)
|
|
1206
|
+
}).strict();
|
|
1207
|
+
|
|
1208
|
+
class SoakCertificateError extends Error {
|
|
1209
|
+
code;
|
|
1210
|
+
detail;
|
|
1211
|
+
constructor(code, detail) {
|
|
1212
|
+
super(`${code}: ${detail}`);
|
|
1213
|
+
this.name = "SoakCertificateError";
|
|
1214
|
+
this.code = code;
|
|
1215
|
+
this.detail = detail;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
function evaluateSoakGates(evidence, policy) {
|
|
1219
|
+
const failed = [];
|
|
1220
|
+
if (evidence.genuineUsageObservations < policy.minimumGenuineObservations) {
|
|
1221
|
+
failed.push("minimum-observations");
|
|
1222
|
+
}
|
|
1223
|
+
if (evidence.distinctDays < policy.minimumDistinctDays) {
|
|
1224
|
+
failed.push("minimum-duration");
|
|
1225
|
+
}
|
|
1226
|
+
if (evidence.invalidChains > 0) {
|
|
1227
|
+
failed.push("chain-integrity");
|
|
1228
|
+
}
|
|
1229
|
+
if (evidence.criticalDivergences === null || evidence.criticalDivergences > 0) {
|
|
1230
|
+
failed.push("zero-critical-divergence");
|
|
1231
|
+
}
|
|
1232
|
+
if (evidence.privacy === null || evidence.privacy.surfacesScanned < 1 || evidence.privacy.rawFindings > 0) {
|
|
1233
|
+
failed.push("zero-privacy-breach");
|
|
1234
|
+
}
|
|
1235
|
+
if (evidence.duplicateEffects === null || evidence.duplicateEffects.effectsExamined < 1 || evidence.duplicateEffects.duplicatesFound > 0) {
|
|
1236
|
+
failed.push("zero-duplicate-effect");
|
|
1237
|
+
}
|
|
1238
|
+
if (evidence.modelVerification === null || evidence.modelVerification.nodesChecked < 1 || evidence.modelVerification.mismatched > 0) {
|
|
1239
|
+
failed.push("zero-model-mismatch");
|
|
1240
|
+
}
|
|
1241
|
+
if (policy.requireGenuineUsageOnAllPlatforms) {
|
|
1242
|
+
if (!evidence.platformsGenuine.includes("linux") || !evidence.platformsGenuine.includes("win32")) {
|
|
1243
|
+
failed.push("os-diversity");
|
|
1244
|
+
}
|
|
1245
|
+
} else {
|
|
1246
|
+
const allPlatforms = new Set([
|
|
1247
|
+
...evidence.platformsGenuine,
|
|
1248
|
+
...evidence.platformsSynthetic
|
|
1249
|
+
]);
|
|
1250
|
+
if (!allPlatforms.has("linux") || !allPlatforms.has("win32") || evidence.platformsGenuine.length < 1) {
|
|
1251
|
+
failed.push("os-diversity");
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
const supported = new Set(policy.supportedOpencodeVersions);
|
|
1255
|
+
if (evidence.opencodeVersionsObserved.length === 0 || !evidence.opencodeVersionsObserved.every((v) => supported.has(v))) {
|
|
1256
|
+
failed.push("opencode-pin");
|
|
1257
|
+
}
|
|
1258
|
+
return failed;
|
|
1259
|
+
}
|
|
1260
|
+
function canonicalSoakCertificate(certificate) {
|
|
1261
|
+
const ev = certificate.evidence;
|
|
1262
|
+
const pol = certificate.policy;
|
|
1263
|
+
return JSON.stringify({
|
|
1264
|
+
version: certificate.version,
|
|
1265
|
+
certificate: certificate.certificate,
|
|
1266
|
+
trustBoundary: certificate.trustBoundary,
|
|
1267
|
+
policy: {
|
|
1268
|
+
minimumGenuineObservations: pol.minimumGenuineObservations,
|
|
1269
|
+
minimumDistinctDays: pol.minimumDistinctDays,
|
|
1270
|
+
requireGenuineUsageOnAllPlatforms: pol.requireGenuineUsageOnAllPlatforms,
|
|
1271
|
+
supportedOpencodeVersions: [...pol.supportedOpencodeVersions].sort(),
|
|
1272
|
+
criticalDivergenceCodes: [...pol.criticalDivergenceCodes].sort()
|
|
1273
|
+
},
|
|
1274
|
+
verdict: certificate.verdict,
|
|
1275
|
+
failedGates: [...certificate.failedGates],
|
|
1276
|
+
ledgerDigest: certificate.ledgerDigest,
|
|
1277
|
+
evidence: {
|
|
1278
|
+
totalObservations: ev.totalObservations,
|
|
1279
|
+
genuineUsageObservations: ev.genuineUsageObservations,
|
|
1280
|
+
ciSyntheticObservations: ev.ciSyntheticObservations,
|
|
1281
|
+
recorderCount: ev.recorderCount,
|
|
1282
|
+
ineligibleObservations: ev.ineligibleObservations,
|
|
1283
|
+
firstTimestamp: ev.firstTimestamp,
|
|
1284
|
+
lastTimestamp: ev.lastTimestamp,
|
|
1285
|
+
distinctDays: ev.distinctDays,
|
|
1286
|
+
criticalDivergences: ev.criticalDivergences,
|
|
1287
|
+
privacy: ev.privacy,
|
|
1288
|
+
duplicateEffects: ev.duplicateEffects,
|
|
1289
|
+
modelVerification: ev.modelVerification,
|
|
1290
|
+
platformsGenuine: [...ev.platformsGenuine].sort(),
|
|
1291
|
+
platformsSynthetic: [...ev.platformsSynthetic].sort(),
|
|
1292
|
+
opencodeVersionsObserved: [...ev.opencodeVersionsObserved].sort(),
|
|
1293
|
+
invalidChains: ev.invalidChains
|
|
1294
|
+
}
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
function sameSoakGates(a, b) {
|
|
1298
|
+
return a.length === b.length && a.every((gate, index) => gate === b[index]);
|
|
1299
|
+
}
|
|
1300
|
+
function sameSoakPolicy(a, b) {
|
|
1301
|
+
const aVersions = [...a.supportedOpencodeVersions].sort();
|
|
1302
|
+
const bVersions = [...b.supportedOpencodeVersions].sort();
|
|
1303
|
+
return a.minimumGenuineObservations === b.minimumGenuineObservations && a.minimumDistinctDays === b.minimumDistinctDays && a.requireGenuineUsageOnAllPlatforms === b.requireGenuineUsageOnAllPlatforms && aVersions.length === bVersions.length && aVersions.every((v, i) => v === bVersions[i]) && a.criticalDivergenceCodes.length === b.criticalDivergenceCodes.length && [...a.criticalDivergenceCodes].sort().every((code, i) => code === [...b.criticalDivergenceCodes].sort()[i]);
|
|
1304
|
+
}
|
|
1305
|
+
function parseSoakCertificate(value, expectedPolicy, expectedLedgerDigest) {
|
|
1306
|
+
const parsed = SoakCertificateSchema.safeParse(value);
|
|
1307
|
+
if (!parsed.success) {
|
|
1308
|
+
const detail = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.code}`).join("; ");
|
|
1309
|
+
throw new SoakCertificateError("invalid-certificate", detail);
|
|
1310
|
+
}
|
|
1311
|
+
const certificate = parsed.data;
|
|
1312
|
+
if (!sameSoakPolicy(certificate.policy, expectedPolicy)) {
|
|
1313
|
+
throw new SoakCertificateError("policy-mismatch", "embedded policy diverges from the expected policy");
|
|
1314
|
+
}
|
|
1315
|
+
const failedGates = evaluateSoakGates(certificate.evidence, certificate.policy);
|
|
1316
|
+
const verdict = failedGates.length === 0 ? "pass" : "fail";
|
|
1317
|
+
if (certificate.verdict !== verdict || !sameSoakGates(certificate.failedGates, failedGates)) {
|
|
1318
|
+
throw new SoakCertificateError("verdict-mismatch", `evidence yields "${verdict}" (${failedGates.join(",") || "no failed gate"})`);
|
|
1319
|
+
}
|
|
1320
|
+
const { digest, ...unsigned } = certificate;
|
|
1321
|
+
if (sha256Hex(canonicalSoakCertificate(unsigned)) !== digest) {
|
|
1322
|
+
throw new SoakCertificateError("digest-mismatch", "certificate digest does not cover the presented evidence");
|
|
1323
|
+
}
|
|
1324
|
+
if (certificate.ledgerDigest !== expectedLedgerDigest) {
|
|
1325
|
+
throw new SoakCertificateError("ledger-mismatch", "ledger digest does not match the evidence the caller holds");
|
|
1326
|
+
}
|
|
1327
|
+
return certificate;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
549
1330
|
// src/local/openai-compatible.ts
|
|
550
1331
|
function normalizeBaseURL(baseURL) {
|
|
551
1332
|
return baseURL.replace(/\/+$/, "");
|
|
@@ -562,8 +1343,8 @@ function errorMessage(error) {
|
|
|
562
1343
|
}
|
|
563
1344
|
return String(error);
|
|
564
1345
|
}
|
|
565
|
-
async function listOpenAICompatibleModels(baseURL,
|
|
566
|
-
const response = await
|
|
1346
|
+
async function listOpenAICompatibleModels(baseURL, fetch2) {
|
|
1347
|
+
const response = await fetch2(modelsEndpoint(baseURL), {
|
|
567
1348
|
method: "GET",
|
|
568
1349
|
headers: { accept: "application/json" }
|
|
569
1350
|
});
|
|
@@ -986,123 +1767,123 @@ function createAvailabilityCache(options) {
|
|
|
986
1767
|
}
|
|
987
1768
|
|
|
988
1769
|
// src/telemetry/events.ts
|
|
989
|
-
import { z as
|
|
1770
|
+
import { z as z5 } from "zod";
|
|
990
1771
|
|
|
991
1772
|
// src/capabilities/types.ts
|
|
992
|
-
import { z as
|
|
993
|
-
var CapabilityTierSchema =
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1773
|
+
import { z as z4 } from "zod";
|
|
1774
|
+
var CapabilityTierSchema = z4.union([
|
|
1775
|
+
z4.literal(0),
|
|
1776
|
+
z4.literal(1),
|
|
1777
|
+
z4.literal(2),
|
|
1778
|
+
z4.literal(3),
|
|
1779
|
+
z4.literal(4),
|
|
1780
|
+
z4.literal(5)
|
|
1000
1781
|
]);
|
|
1001
|
-
var ComplexityTierSchema =
|
|
1782
|
+
var ComplexityTierSchema = z4.enum([
|
|
1002
1783
|
"trivial",
|
|
1003
1784
|
"simple",
|
|
1004
1785
|
"moderate",
|
|
1005
1786
|
"hard"
|
|
1006
1787
|
]);
|
|
1007
|
-
var ModelCapabilityProfileSchema =
|
|
1008
|
-
ref:
|
|
1009
|
-
providerID:
|
|
1010
|
-
modelID:
|
|
1788
|
+
var ModelCapabilityProfileSchema = z4.object({
|
|
1789
|
+
ref: z4.object({
|
|
1790
|
+
providerID: z4.string().min(1),
|
|
1791
|
+
modelID: z4.string().min(1)
|
|
1011
1792
|
}),
|
|
1012
|
-
kind:
|
|
1013
|
-
contextWindow:
|
|
1014
|
-
maxOutputTokens:
|
|
1015
|
-
supportsToolCalling:
|
|
1016
|
-
supportsVision:
|
|
1793
|
+
kind: z4.enum(["local", "frontier", "router"]),
|
|
1794
|
+
contextWindow: z4.number().int().positive(),
|
|
1795
|
+
maxOutputTokens: z4.number().int().positive(),
|
|
1796
|
+
supportsToolCalling: z4.boolean(),
|
|
1797
|
+
supportsVision: z4.boolean(),
|
|
1017
1798
|
reasoningTier: CapabilityTierSchema,
|
|
1018
1799
|
codeQualityTier: CapabilityTierSchema,
|
|
1019
|
-
costPer1M:
|
|
1020
|
-
inputUSD:
|
|
1021
|
-
outputUSD:
|
|
1800
|
+
costPer1M: z4.object({
|
|
1801
|
+
inputUSD: z4.number().min(0),
|
|
1802
|
+
outputUSD: z4.number().min(0)
|
|
1022
1803
|
}),
|
|
1023
|
-
availability:
|
|
1804
|
+
availability: z4.enum(["available", "degraded", "unavailable"])
|
|
1024
1805
|
});
|
|
1025
1806
|
|
|
1026
1807
|
// src/telemetry/events.ts
|
|
1027
1808
|
var EVENT_SCHEMA_VERSION = 1;
|
|
1028
|
-
var EventBaseSchema =
|
|
1029
|
-
v:
|
|
1030
|
-
ts:
|
|
1031
|
-
sessionID:
|
|
1809
|
+
var EventBaseSchema = z5.object({
|
|
1810
|
+
v: z5.literal(EVENT_SCHEMA_VERSION),
|
|
1811
|
+
ts: z5.number().finite(),
|
|
1812
|
+
sessionID: z5.string().min(1)
|
|
1032
1813
|
});
|
|
1033
1814
|
var RouteEventSchema = EventBaseSchema.extend({
|
|
1034
|
-
type:
|
|
1035
|
-
promptHash:
|
|
1036
|
-
promptChars:
|
|
1815
|
+
type: z5.literal("route"),
|
|
1816
|
+
promptHash: z5.string().min(1),
|
|
1817
|
+
promptChars: z5.number().int().min(0),
|
|
1037
1818
|
tier: ComplexityTierSchema,
|
|
1038
|
-
routeKind:
|
|
1819
|
+
routeKind: z5.enum(["local", "frontier"]),
|
|
1039
1820
|
selected: ModelRefSchema,
|
|
1040
|
-
rationale:
|
|
1041
|
-
estimatedCostUSD:
|
|
1042
|
-
baselineCostUSD:
|
|
1043
|
-
estimatedSavingsUSD:
|
|
1044
|
-
budgetAction:
|
|
1045
|
-
tokensIn:
|
|
1046
|
-
tokensOut:
|
|
1047
|
-
decisionID:
|
|
1048
|
-
agent:
|
|
1049
|
-
success:
|
|
1050
|
-
batchID:
|
|
1051
|
-
taskID:
|
|
1052
|
-
failureReason:
|
|
1053
|
-
failureStage:
|
|
1821
|
+
rationale: z5.string(),
|
|
1822
|
+
estimatedCostUSD: z5.number().finite().min(0),
|
|
1823
|
+
baselineCostUSD: z5.number().finite().min(0),
|
|
1824
|
+
estimatedSavingsUSD: z5.number().finite(),
|
|
1825
|
+
budgetAction: z5.string().min(1),
|
|
1826
|
+
tokensIn: z5.number().int().min(0).optional(),
|
|
1827
|
+
tokensOut: z5.number().int().min(0).optional(),
|
|
1828
|
+
decisionID: z5.string().min(1).optional(),
|
|
1829
|
+
agent: z5.string().min(1).optional(),
|
|
1830
|
+
success: z5.boolean().optional(),
|
|
1831
|
+
batchID: z5.string().min(1).optional(),
|
|
1832
|
+
taskID: z5.string().min(1).optional(),
|
|
1833
|
+
failureReason: z5.string().min(1).optional(),
|
|
1834
|
+
failureStage: z5.string().min(1).optional()
|
|
1054
1835
|
});
|
|
1055
1836
|
var MessageEventSchema = EventBaseSchema.extend({
|
|
1056
|
-
type:
|
|
1057
|
-
providerID:
|
|
1058
|
-
modelID:
|
|
1059
|
-
agent:
|
|
1060
|
-
mode:
|
|
1061
|
-
messageID:
|
|
1062
|
-
costUSD:
|
|
1063
|
-
tokensIn:
|
|
1064
|
-
tokensOut:
|
|
1065
|
-
tokensReasoning:
|
|
1066
|
-
tokensCacheRead:
|
|
1067
|
-
tokensCacheWrite:
|
|
1068
|
-
durationMs:
|
|
1837
|
+
type: z5.literal("message"),
|
|
1838
|
+
providerID: z5.string().min(1),
|
|
1839
|
+
modelID: z5.string().min(1),
|
|
1840
|
+
agent: z5.string().min(1).optional(),
|
|
1841
|
+
mode: z5.string().min(1).optional(),
|
|
1842
|
+
messageID: z5.string().min(1).optional(),
|
|
1843
|
+
costUSD: z5.number().finite().min(0),
|
|
1844
|
+
tokensIn: z5.number().int().min(0),
|
|
1845
|
+
tokensOut: z5.number().int().min(0),
|
|
1846
|
+
tokensReasoning: z5.number().int().min(0),
|
|
1847
|
+
tokensCacheRead: z5.number().int().min(0),
|
|
1848
|
+
tokensCacheWrite: z5.number().int().min(0),
|
|
1849
|
+
durationMs: z5.number().int().min(0)
|
|
1069
1850
|
});
|
|
1070
1851
|
var ToolcallEventSchema = EventBaseSchema.extend({
|
|
1071
|
-
type:
|
|
1072
|
-
tool:
|
|
1073
|
-
callID:
|
|
1074
|
-
agent:
|
|
1075
|
-
durationMs:
|
|
1076
|
-
ok:
|
|
1077
|
-
title:
|
|
1852
|
+
type: z5.literal("toolcall"),
|
|
1853
|
+
tool: z5.string().min(1),
|
|
1854
|
+
callID: z5.string().min(1),
|
|
1855
|
+
agent: z5.string().min(1).optional(),
|
|
1856
|
+
durationMs: z5.number().int().min(0),
|
|
1857
|
+
ok: z5.boolean(),
|
|
1858
|
+
title: z5.string().optional()
|
|
1078
1859
|
});
|
|
1079
1860
|
var MeetingEventSchema = EventBaseSchema.extend({
|
|
1080
|
-
type:
|
|
1081
|
-
batchID:
|
|
1082
|
-
purpose:
|
|
1083
|
-
roles:
|
|
1084
|
-
decisionIDs:
|
|
1861
|
+
type: z5.literal("meeting"),
|
|
1862
|
+
batchID: z5.string().min(1),
|
|
1863
|
+
purpose: z5.string().optional(),
|
|
1864
|
+
roles: z5.array(z5.string().min(1)),
|
|
1865
|
+
decisionIDs: z5.array(z5.string().min(1)).optional()
|
|
1085
1866
|
});
|
|
1086
1867
|
var DecisionEventSchema = EventBaseSchema.extend({
|
|
1087
|
-
type:
|
|
1088
|
-
agent:
|
|
1089
|
-
summary:
|
|
1090
|
-
tags:
|
|
1868
|
+
type: z5.literal("decision"),
|
|
1869
|
+
agent: z5.string().min(1).optional(),
|
|
1870
|
+
summary: z5.string().min(1),
|
|
1871
|
+
tags: z5.array(z5.string().min(1)).optional()
|
|
1091
1872
|
});
|
|
1092
1873
|
var ActivityEventSchema = EventBaseSchema.extend({
|
|
1093
|
-
type:
|
|
1094
|
-
kind:
|
|
1095
|
-
agent:
|
|
1096
|
-
summary:
|
|
1874
|
+
type: z5.literal("activity"),
|
|
1875
|
+
kind: z5.enum(["route", "agent", "decision", "commit"]),
|
|
1876
|
+
agent: z5.string().min(1).optional(),
|
|
1877
|
+
summary: z5.string().min(1)
|
|
1097
1878
|
});
|
|
1098
1879
|
var SessionEndpointEventSchema = EventBaseSchema.extend({
|
|
1099
|
-
type:
|
|
1100
|
-
url:
|
|
1101
|
-
directory:
|
|
1102
|
-
worktree:
|
|
1103
|
-
title:
|
|
1880
|
+
type: z5.literal("session-endpoint"),
|
|
1881
|
+
url: z5.string().url(),
|
|
1882
|
+
directory: z5.string().min(1).optional(),
|
|
1883
|
+
worktree: z5.string().min(1).optional(),
|
|
1884
|
+
title: z5.string().min(1).optional()
|
|
1104
1885
|
});
|
|
1105
|
-
var OpenTeamEventSchema =
|
|
1886
|
+
var OpenTeamEventSchema = z5.discriminatedUnion("type", [
|
|
1106
1887
|
RouteEventSchema,
|
|
1107
1888
|
MessageEventSchema,
|
|
1108
1889
|
ToolcallEventSchema,
|
|
@@ -1479,6 +2260,15 @@ function renderDoctor(input) {
|
|
|
1479
2260
|
if (reachable === 0) {
|
|
1480
2261
|
lines.push(" ⚠ Ningún runtime local alcanzable: todo el tráfico irá a frontier.");
|
|
1481
2262
|
}
|
|
2263
|
+
if (input.graphHealth !== undefined) {
|
|
2264
|
+
const h = input.graphHealth;
|
|
2265
|
+
lines.push(" graph health:");
|
|
2266
|
+
lines.push(` recuperación requerida: ${h.recoveryRequired ? "sí" : "no"}`);
|
|
2267
|
+
lines.push(` efectos desconocidos: ${h.unknownEffects}`);
|
|
2268
|
+
lines.push(` completaciones tardías: ${h.staleCompletions}`);
|
|
2269
|
+
lines.push(` conflictos: ${h.conflicts}`);
|
|
2270
|
+
lines.push(` kill switch: ${h.killSwitch ? "activo" : "inactivo"}`);
|
|
2271
|
+
}
|
|
1482
2272
|
return lines.join(`
|
|
1483
2273
|
`);
|
|
1484
2274
|
}
|
|
@@ -1757,174 +2547,1391 @@ ${HELP}`
|
|
|
1757
2547
|
if (outcome.config !== undefined) {
|
|
1758
2548
|
await deps.saveConfig(outcome.config, configPath);
|
|
1759
2549
|
}
|
|
1760
|
-
return { exitCode: 0, stdout: outcome.message };
|
|
2550
|
+
return { exitCode: 0, stdout: outcome.message };
|
|
2551
|
+
}
|
|
2552
|
+
async function runYolo(positionals, deps, opencodeConfigPath) {
|
|
2553
|
+
const sub = positionals[1] ?? "status";
|
|
2554
|
+
if (sub !== "status" && sub !== "on" && sub !== "off") {
|
|
2555
|
+
return {
|
|
2556
|
+
exitCode: 1,
|
|
2557
|
+
stdout: `Subcomando de yolo desconocido: ${sub}
|
|
2558
|
+
|
|
2559
|
+
${HELP}`
|
|
2560
|
+
};
|
|
2561
|
+
}
|
|
2562
|
+
const config = await deps.readOpencodeConfig(opencodeConfigPath);
|
|
2563
|
+
if (config === undefined) {
|
|
2564
|
+
return {
|
|
2565
|
+
exitCode: 1,
|
|
2566
|
+
stdout: `No existe ${opencodeConfigPath}. Ejecuta 'openteam setup' primero.`
|
|
2567
|
+
};
|
|
2568
|
+
}
|
|
2569
|
+
if (sub === "status") {
|
|
2570
|
+
return { exitCode: 0, stdout: renderYoloStatus(isYoloEnabled(config)) };
|
|
2571
|
+
}
|
|
2572
|
+
const enable = sub === "on";
|
|
2573
|
+
if (isYoloEnabled(config) === enable) {
|
|
2574
|
+
return {
|
|
2575
|
+
exitCode: 0,
|
|
2576
|
+
stdout: `Sin cambios. ${renderYoloStatus(enable)}`
|
|
2577
|
+
};
|
|
2578
|
+
}
|
|
2579
|
+
const next = applyYolo(config, enable);
|
|
2580
|
+
await deps.writeOpencodeConfig(next, opencodeConfigPath);
|
|
2581
|
+
const agentNote = await regenerateOrchestratorAgent(deps, config, enable);
|
|
2582
|
+
return {
|
|
2583
|
+
exitCode: 0,
|
|
2584
|
+
stdout: `${opencodeConfigPath} actualizado. ${renderYoloStatus(enable)}${agentNote}${enable ? `
|
|
2585
|
+
Nota: YOLO no desactiva la privacidad de openteam; los prompts sensibles siguen sin salir a frontier con forceLocalOnSensitive.` : ""}
|
|
2586
|
+
Reinicia opencode (o recarga) para que los agentes tomen los nuevos permisos.`
|
|
2587
|
+
};
|
|
2588
|
+
}
|
|
2589
|
+
async function regenerateOrchestratorAgent(deps, config, enable) {
|
|
2590
|
+
const model = config.model;
|
|
2591
|
+
if (typeof model !== "string" || model.trim() === "") {
|
|
2592
|
+
return `
|
|
2593
|
+
Aviso: no pude regenerar ${deps.orchestratorAgentPath} (falta 'model' en opencode.json); ajusta su permission a mano.`;
|
|
2594
|
+
}
|
|
2595
|
+
try {
|
|
2596
|
+
const frontier = parseModelRef(model);
|
|
2597
|
+
const contents = buildOrchestratorAgent(frontier, { yolo: enable });
|
|
2598
|
+
await deps.writeOrchestratorAgent(contents, deps.orchestratorAgentPath);
|
|
2599
|
+
return `
|
|
2600
|
+
${deps.orchestratorAgentPath} regenerado (permission acorde a YOLO).`;
|
|
2601
|
+
} catch {
|
|
2602
|
+
return `
|
|
2603
|
+
Aviso: no pude regenerar ${deps.orchestratorAgentPath} (model inválido en opencode.json); ajusta su permission a mano.`;
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
async function runAgents(deps, configPath, opencodeConfigPath) {
|
|
2607
|
+
const [config, opencodeConfig, files] = await Promise.all([
|
|
2608
|
+
deps.loadConfig(configPath),
|
|
2609
|
+
deps.readOpencodeConfig(opencodeConfigPath),
|
|
2610
|
+
deps.listAgentFiles(deps.agentDir)
|
|
2611
|
+
]);
|
|
2612
|
+
const localProviders = localProviderIDs(config);
|
|
2613
|
+
const rawDefault = opencodeConfig?.model;
|
|
2614
|
+
let defaultModel;
|
|
2615
|
+
if (typeof rawDefault === "string" && rawDefault.trim() !== "") {
|
|
2616
|
+
try {
|
|
2617
|
+
defaultModel = parseModelRef(rawDefault);
|
|
2618
|
+
} catch {
|
|
2619
|
+
defaultModel = undefined;
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
const context = defaultModel === undefined ? { localProviders } : { localProviders, defaultModel };
|
|
2623
|
+
const classifications = classifyAgents(files, context);
|
|
2624
|
+
return { exitCode: 0, stdout: renderAgentList(classifications, context) };
|
|
2625
|
+
}
|
|
2626
|
+
async function runCli(argv, deps) {
|
|
2627
|
+
const parsed = parseArgs(argv);
|
|
2628
|
+
const command = parsed.positionals[0];
|
|
2629
|
+
const configPath = parsed.configPath ?? deps.configPath;
|
|
2630
|
+
const telemetryPath = parsed.telemetryPath ?? deps.telemetryPath;
|
|
2631
|
+
const opencodeConfigPath = parsed.opencodeConfigPath ?? deps.opencodeConfigPath;
|
|
2632
|
+
try {
|
|
2633
|
+
if (command === "baseline") {
|
|
2634
|
+
return await runBaseline(parsed.positionals, deps, configPath);
|
|
2635
|
+
}
|
|
2636
|
+
if (command === "local") {
|
|
2637
|
+
return await runLocal(parsed.positionals, deps, configPath);
|
|
2638
|
+
}
|
|
2639
|
+
if (command === "yolo") {
|
|
2640
|
+
return await runYolo(parsed.positionals, deps, opencodeConfigPath);
|
|
2641
|
+
}
|
|
2642
|
+
if (command === "doctor") {
|
|
2643
|
+
const config = await deps.loadConfig(configPath);
|
|
2644
|
+
const [snapshots, records] = await Promise.all([
|
|
2645
|
+
deps.probe(config),
|
|
2646
|
+
deps.readTelemetry(telemetryPath)
|
|
2647
|
+
]);
|
|
2648
|
+
return {
|
|
2649
|
+
exitCode: 0,
|
|
2650
|
+
stdout: renderDoctor({
|
|
2651
|
+
config,
|
|
2652
|
+
snapshots,
|
|
2653
|
+
telemetryPath,
|
|
2654
|
+
telemetryRecords: records.length
|
|
2655
|
+
})
|
|
2656
|
+
};
|
|
2657
|
+
}
|
|
2658
|
+
if (command === "report") {
|
|
2659
|
+
const records = await deps.readTelemetry(telemetryPath);
|
|
2660
|
+
return {
|
|
2661
|
+
exitCode: 0,
|
|
2662
|
+
stdout: renderReport(summarizeCostRecords(records))
|
|
2663
|
+
};
|
|
2664
|
+
}
|
|
2665
|
+
if (command === "agents") {
|
|
2666
|
+
return await runAgents(deps, configPath, opencodeConfigPath);
|
|
2667
|
+
}
|
|
2668
|
+
if (command === "console") {
|
|
2669
|
+
const config = await deps.loadConfig(configPath);
|
|
2670
|
+
return { exitCode: 0, stdout: renderConsoleStatus(config.console) };
|
|
2671
|
+
}
|
|
2672
|
+
if (command === undefined || command === "help" || command === "--help") {
|
|
2673
|
+
return { exitCode: 0, stdout: HELP };
|
|
2674
|
+
}
|
|
2675
|
+
return {
|
|
2676
|
+
exitCode: 1,
|
|
2677
|
+
stdout: `Comando desconocido: ${command}
|
|
2678
|
+
|
|
2679
|
+
${HELP}`
|
|
2680
|
+
};
|
|
2681
|
+
} catch (error) {
|
|
2682
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2683
|
+
return { exitCode: 1, stdout: `Error: ${message}` };
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
// src/plugin/commandTool.ts
|
|
2688
|
+
function commandArgv(action, model) {
|
|
2689
|
+
switch (action) {
|
|
2690
|
+
case "show":
|
|
2691
|
+
return ["baseline", "show"];
|
|
2692
|
+
case "auto":
|
|
2693
|
+
return ["baseline", "auto"];
|
|
2694
|
+
case "set":
|
|
2695
|
+
return model === undefined ? ["baseline", "set"] : ["baseline", "set", model];
|
|
2696
|
+
case "doctor":
|
|
2697
|
+
return ["doctor"];
|
|
2698
|
+
case "report":
|
|
2699
|
+
return ["report"];
|
|
2700
|
+
case "agents":
|
|
2701
|
+
return ["agents"];
|
|
2702
|
+
case "console":
|
|
2703
|
+
return ["console"];
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
function createCommandTool(deps) {
|
|
2707
|
+
return tool({
|
|
2708
|
+
description: "Comandos runtime de openteam: baseline (show/set/auto), doctor, report, agents (LLM por agente: local/frontier + suscripción) y console (estado/URL de la Console web).",
|
|
2709
|
+
args: {
|
|
2710
|
+
action: tool.schema.enum(["show", "set", "auto", "doctor", "report", "agents", "console"]).describe("Acción a ejecutar"),
|
|
2711
|
+
model: tool.schema.string().optional().describe("Modelo provider/model (solo para action=set)")
|
|
2712
|
+
},
|
|
2713
|
+
async execute(args) {
|
|
2714
|
+
const result = await runCli(commandArgv(args.action, args.model), deps);
|
|
2715
|
+
return result.stdout;
|
|
2716
|
+
}
|
|
2717
|
+
});
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2720
|
+
// src/plugin/graphTool.ts
|
|
2721
|
+
import { tool as tool2 } from "@opencode-ai/plugin";
|
|
2722
|
+
|
|
2723
|
+
// src/graph/schema.ts
|
|
2724
|
+
import { z as z7 } from "zod";
|
|
2725
|
+
|
|
2726
|
+
// src/context/schema.ts
|
|
2727
|
+
import { z as z6 } from "zod";
|
|
2728
|
+
var PrivacyClassSchema = z6.enum(["public", "internal", "sensitive"]);
|
|
2729
|
+
var Sha256Schema2 = z6.string().regex(/^[0-9a-f]{64}$/, "expected lowercase hex sha-256");
|
|
2730
|
+
var IdentifierSchema = z6.string().min(1).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/, "invalid identifier");
|
|
2731
|
+
var TaskAnchorSchema = z6.object({
|
|
2732
|
+
taskID: IdentifierSchema,
|
|
2733
|
+
specDigest: Sha256Schema2
|
|
2734
|
+
}).strict();
|
|
2735
|
+
var ContextRefSchema = z6.object({
|
|
2736
|
+
uri: z6.string().min(1).max(1024),
|
|
2737
|
+
sha256: Sha256Schema2,
|
|
2738
|
+
bytes: z6.number().int().nonnegative(),
|
|
2739
|
+
mediaType: z6.string().min(1).max(128).optional()
|
|
2740
|
+
}).strict();
|
|
2741
|
+
var ManifestEntrySchema = z6.object({
|
|
2742
|
+
label: IdentifierSchema,
|
|
2743
|
+
sha256: Sha256Schema2,
|
|
2744
|
+
bytes: z6.number().int().nonnegative(),
|
|
2745
|
+
ref: ContextRefSchema.optional()
|
|
2746
|
+
}).strict();
|
|
2747
|
+
var ArtifactManifestSchema = z6.object({
|
|
2748
|
+
version: z6.literal(1),
|
|
2749
|
+
taskID: IdentifierSchema,
|
|
2750
|
+
privacyClass: PrivacyClassSchema,
|
|
2751
|
+
entries: z6.array(ManifestEntrySchema)
|
|
2752
|
+
}).strict();
|
|
2753
|
+
var NodeContextContractSchema = z6.object({
|
|
2754
|
+
version: z6.literal(1),
|
|
2755
|
+
refs: z6.array(ContextRefSchema),
|
|
2756
|
+
privacyClass: PrivacyClassSchema,
|
|
2757
|
+
maxInputTokens: z6.number().int().positive().optional()
|
|
2758
|
+
}).strict();
|
|
2759
|
+
|
|
2760
|
+
// src/graph/schema.ts
|
|
2761
|
+
var GRAPH_SPEC_VERSION = 1;
|
|
2762
|
+
var GRAPH_EVENT_VERSION = 1;
|
|
2763
|
+
var NodeIDSchema = z7.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/, "invalid node id");
|
|
2764
|
+
var NodeRoleSchema = z7.enum(["implementer", "reviewer"]);
|
|
2765
|
+
var OperationIDSchema = z7.string().regex(/^[0-9a-f]{8,64}$/, "invalid operation id");
|
|
2766
|
+
var AttemptIDSchema = z7.string().regex(/^[0-9a-f]{8,64}$/, "invalid attempt id");
|
|
2767
|
+
var ErrorClassSchema = z7.enum([
|
|
2768
|
+
"transient",
|
|
2769
|
+
"permanent",
|
|
2770
|
+
"timeout",
|
|
2771
|
+
"cancelled",
|
|
2772
|
+
"unknown"
|
|
2773
|
+
]);
|
|
2774
|
+
var GraphNodeSchema = z7.object({
|
|
2775
|
+
id: NodeIDSchema,
|
|
2776
|
+
role: NodeRoleSchema,
|
|
2777
|
+
dependsOn: z7.array(NodeIDSchema).default([]),
|
|
2778
|
+
anchor: TaskAnchorSchema,
|
|
2779
|
+
reviews: NodeIDSchema.optional()
|
|
2780
|
+
}).strict();
|
|
2781
|
+
var GraphSpecV1Schema = z7.object({
|
|
2782
|
+
version: z7.literal(GRAPH_SPEC_VERSION),
|
|
2783
|
+
runID: NodeIDSchema,
|
|
2784
|
+
nodes: z7.array(GraphNodeSchema).min(1)
|
|
2785
|
+
}).strict();
|
|
2786
|
+
var eventEnvelope = {
|
|
2787
|
+
v: z7.literal(GRAPH_EVENT_VERSION),
|
|
2788
|
+
seq: z7.number().int().nonnegative(),
|
|
2789
|
+
runID: NodeIDSchema
|
|
2790
|
+
};
|
|
2791
|
+
var RunStartedEventSchema = z7.object({
|
|
2792
|
+
...eventEnvelope,
|
|
2793
|
+
type: z7.literal("run.started"),
|
|
2794
|
+
specDigest: Sha256Schema2
|
|
2795
|
+
}).strict();
|
|
2796
|
+
var NodeDispatchedEventSchema = z7.object({
|
|
2797
|
+
...eventEnvelope,
|
|
2798
|
+
type: z7.literal("node.dispatched"),
|
|
2799
|
+
nodeID: NodeIDSchema,
|
|
2800
|
+
operationID: OperationIDSchema,
|
|
2801
|
+
attemptID: AttemptIDSchema
|
|
2802
|
+
}).strict();
|
|
2803
|
+
var NodeSucceededEventSchema = z7.object({
|
|
2804
|
+
...eventEnvelope,
|
|
2805
|
+
type: z7.literal("node.succeeded"),
|
|
2806
|
+
nodeID: NodeIDSchema,
|
|
2807
|
+
operationID: OperationIDSchema,
|
|
2808
|
+
attemptID: AttemptIDSchema,
|
|
2809
|
+
artifact: ContextRefSchema
|
|
2810
|
+
}).strict();
|
|
2811
|
+
var NodeFailedEventSchema = z7.object({
|
|
2812
|
+
...eventEnvelope,
|
|
2813
|
+
type: z7.literal("node.failed"),
|
|
2814
|
+
nodeID: NodeIDSchema,
|
|
2815
|
+
operationID: OperationIDSchema,
|
|
2816
|
+
attemptID: AttemptIDSchema,
|
|
2817
|
+
errorClass: ErrorClassSchema
|
|
2818
|
+
}).strict();
|
|
2819
|
+
var NodeCancelledEventSchema = z7.object({
|
|
2820
|
+
...eventEnvelope,
|
|
2821
|
+
type: z7.literal("node.cancelled"),
|
|
2822
|
+
nodeID: NodeIDSchema
|
|
2823
|
+
}).strict();
|
|
2824
|
+
var RunCompletedEventSchema = z7.object({ ...eventEnvelope, type: z7.literal("run.completed") }).strict();
|
|
2825
|
+
var RunFailedEventSchema = z7.object({ ...eventEnvelope, type: z7.literal("run.failed") }).strict();
|
|
2826
|
+
var RunCancelledEventSchema = z7.object({ ...eventEnvelope, type: z7.literal("run.cancelled") }).strict();
|
|
2827
|
+
var GraphEventV1Schema = z7.discriminatedUnion("type", [
|
|
2828
|
+
RunStartedEventSchema,
|
|
2829
|
+
NodeDispatchedEventSchema,
|
|
2830
|
+
NodeSucceededEventSchema,
|
|
2831
|
+
NodeFailedEventSchema,
|
|
2832
|
+
NodeCancelledEventSchema,
|
|
2833
|
+
RunCompletedEventSchema,
|
|
2834
|
+
RunFailedEventSchema,
|
|
2835
|
+
RunCancelledEventSchema
|
|
2836
|
+
]);
|
|
2837
|
+
|
|
2838
|
+
// src/graph/ids.ts
|
|
2839
|
+
var NUL = "\x00";
|
|
2840
|
+
function operationID(runID, nodeID, revision = 0) {
|
|
2841
|
+
return sha256Hex(`op${NUL}${runID}${NUL}${nodeID}${NUL}${revision}`).slice(0, 32);
|
|
2842
|
+
}
|
|
2843
|
+
function attemptID(operation, attempt) {
|
|
2844
|
+
return sha256Hex(`att${NUL}${operation}${NUL}${attempt}`).slice(0, 32);
|
|
2845
|
+
}
|
|
2846
|
+
function canonicalSpec(spec) {
|
|
2847
|
+
const nodes = spec.nodes.map((node) => ({
|
|
2848
|
+
id: node.id,
|
|
2849
|
+
role: node.role,
|
|
2850
|
+
dependsOn: [...node.dependsOn],
|
|
2851
|
+
anchor: { taskID: node.anchor.taskID, specDigest: node.anchor.specDigest },
|
|
2852
|
+
...node.reviews === undefined ? {} : { reviews: node.reviews }
|
|
2853
|
+
}));
|
|
2854
|
+
return JSON.stringify({
|
|
2855
|
+
version: spec.version,
|
|
2856
|
+
runID: spec.runID,
|
|
2857
|
+
nodes
|
|
2858
|
+
});
|
|
2859
|
+
}
|
|
2860
|
+
function graphSpecDigest(spec) {
|
|
2861
|
+
return sha256Hex(canonicalSpec(spec));
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2864
|
+
// src/graph/validate.ts
|
|
2865
|
+
class GraphValidationError extends Error {
|
|
2866
|
+
code;
|
|
2867
|
+
detail;
|
|
2868
|
+
constructor(code, detail) {
|
|
2869
|
+
super(`${code}: ${detail}`);
|
|
2870
|
+
this.name = "GraphValidationError";
|
|
2871
|
+
this.code = code;
|
|
2872
|
+
this.detail = detail;
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
function indexNodes(nodes) {
|
|
2876
|
+
const byID = new Map;
|
|
2877
|
+
for (const node of nodes) {
|
|
2878
|
+
if (byID.has(node.id)) {
|
|
2879
|
+
throw new GraphValidationError("duplicate-node", node.id);
|
|
2880
|
+
}
|
|
2881
|
+
byID.set(node.id, node);
|
|
2882
|
+
}
|
|
2883
|
+
return byID;
|
|
2884
|
+
}
|
|
2885
|
+
function assertDependenciesExist(nodes, byID) {
|
|
2886
|
+
for (const node of nodes) {
|
|
2887
|
+
for (const dep of node.dependsOn) {
|
|
2888
|
+
if (!byID.has(dep)) {
|
|
2889
|
+
throw new GraphValidationError("missing-dependency", `${node.id} -> ${dep}`);
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
function assertAcyclic(nodes, byID) {
|
|
2895
|
+
const state = new Map;
|
|
2896
|
+
const visit = (id) => {
|
|
2897
|
+
const current = state.get(id);
|
|
2898
|
+
if (current === "done") {
|
|
2899
|
+
return;
|
|
2900
|
+
}
|
|
2901
|
+
if (current === "visiting") {
|
|
2902
|
+
throw new GraphValidationError("cycle", id);
|
|
2903
|
+
}
|
|
2904
|
+
state.set(id, "visiting");
|
|
2905
|
+
const node = byID.get(id);
|
|
2906
|
+
for (const dep of node.dependsOn) {
|
|
2907
|
+
visit(dep);
|
|
2908
|
+
}
|
|
2909
|
+
state.set(id, "done");
|
|
2910
|
+
};
|
|
2911
|
+
for (const node of nodes) {
|
|
2912
|
+
visit(node.id);
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
function assertReviewRules(nodes, byID) {
|
|
2916
|
+
for (const node of nodes) {
|
|
2917
|
+
if (node.role === "implementer") {
|
|
2918
|
+
if (node.reviews !== undefined) {
|
|
2919
|
+
throw new GraphValidationError("implementer-has-review", node.id);
|
|
2920
|
+
}
|
|
2921
|
+
continue;
|
|
2922
|
+
}
|
|
2923
|
+
if (node.reviews === undefined) {
|
|
2924
|
+
throw new GraphValidationError("reviewer-missing-review", node.id);
|
|
2925
|
+
}
|
|
2926
|
+
if (node.reviews === node.id) {
|
|
2927
|
+
throw new GraphValidationError("self-review", node.id);
|
|
2928
|
+
}
|
|
2929
|
+
const target = byID.get(node.reviews);
|
|
2930
|
+
if (target === undefined || target.role !== "implementer") {
|
|
2931
|
+
throw new GraphValidationError("invalid-review-target", `${node.id} -> ${node.reviews}`);
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
function assertConnected(nodes) {
|
|
2936
|
+
const adjacency = new Map;
|
|
2937
|
+
for (const node of nodes) {
|
|
2938
|
+
adjacency.set(node.id, adjacency.get(node.id) ?? new Set);
|
|
2939
|
+
for (const dep of node.dependsOn) {
|
|
2940
|
+
adjacency.get(node.id)?.add(dep);
|
|
2941
|
+
const back = adjacency.get(dep) ?? new Set;
|
|
2942
|
+
back.add(node.id);
|
|
2943
|
+
adjacency.set(dep, back);
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
const root = nodes[0];
|
|
2947
|
+
const seen = new Set([root.id]);
|
|
2948
|
+
const queue = [root.id];
|
|
2949
|
+
while (queue.length > 0) {
|
|
2950
|
+
const id = queue.shift();
|
|
2951
|
+
for (const neighbor of adjacency.get(id) ?? new Set) {
|
|
2952
|
+
if (!seen.has(neighbor)) {
|
|
2953
|
+
seen.add(neighbor);
|
|
2954
|
+
queue.push(neighbor);
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
if (seen.size !== nodes.length) {
|
|
2959
|
+
throw new GraphValidationError("disconnected", `${seen.size}/${nodes.length} reachable`);
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2962
|
+
function validateGraphSpec(spec) {
|
|
2963
|
+
const byID = indexNodes(spec.nodes);
|
|
2964
|
+
assertDependenciesExist(spec.nodes, byID);
|
|
2965
|
+
assertAcyclic(spec.nodes, byID);
|
|
2966
|
+
assertReviewRules(spec.nodes, byID);
|
|
2967
|
+
assertConnected(spec.nodes);
|
|
2968
|
+
}
|
|
2969
|
+
|
|
2970
|
+
// src/orchestrator/graphIngress.ts
|
|
2971
|
+
var ACTIVE_INGRESS_BRAND = Symbol("openteam.activeIngress");
|
|
2972
|
+
function gateReason(policy) {
|
|
2973
|
+
if (policy.capability === undefined) {
|
|
2974
|
+
return "gate-closed";
|
|
2975
|
+
}
|
|
2976
|
+
if (policy.graph.killSwitch) {
|
|
2977
|
+
return "kill-switch";
|
|
2978
|
+
}
|
|
2979
|
+
return policy.graph.configuredMode === "active" ? undefined : "graph-disabled";
|
|
2980
|
+
}
|
|
2981
|
+
function decideIngress(policy, taskID) {
|
|
2982
|
+
const blocked = gateReason(policy);
|
|
2983
|
+
if (blocked !== undefined) {
|
|
2984
|
+
return { route: "legacy", reason: blocked };
|
|
2985
|
+
}
|
|
2986
|
+
if (!policy.allowList.includes(taskID)) {
|
|
2987
|
+
return { route: "legacy", reason: "not-allow-listed" };
|
|
2988
|
+
}
|
|
2989
|
+
return { route: "graph", taskID };
|
|
2990
|
+
}
|
|
2991
|
+
|
|
2992
|
+
// src/plugin/legacyRunProducer.ts
|
|
2993
|
+
function toObservedNode(node) {
|
|
2994
|
+
return {
|
|
2995
|
+
id: node.id,
|
|
2996
|
+
role: node.role,
|
|
2997
|
+
model: node.model,
|
|
2998
|
+
ok: node.ok,
|
|
2999
|
+
...node.sessionRef !== undefined ? { sessionRef: node.sessionRef } : {},
|
|
3000
|
+
...node.errorClass !== undefined ? { errorClass: node.errorClass } : {}
|
|
3001
|
+
};
|
|
3002
|
+
}
|
|
3003
|
+
function buildObservedRun(payload) {
|
|
3004
|
+
return {
|
|
3005
|
+
runID: payload.runID,
|
|
3006
|
+
executed: true,
|
|
3007
|
+
status: payload.status,
|
|
3008
|
+
fixes: payload.fixes,
|
|
3009
|
+
nodes: payload.nodes.map(toObservedNode),
|
|
3010
|
+
...payload.parentSessionRef !== undefined ? { parentSessionRef: payload.parentSessionRef } : {}
|
|
3011
|
+
};
|
|
3012
|
+
}
|
|
3013
|
+
function crossVerify(payload, tracker) {
|
|
3014
|
+
const unverifiedModels = [];
|
|
3015
|
+
const modelMismatches = [];
|
|
3016
|
+
const unknownSessions = [];
|
|
3017
|
+
for (const node of payload.nodes) {
|
|
3018
|
+
if (node.sessionRef !== undefined) {
|
|
3019
|
+
const mv = tracker.verifyModel(node.sessionRef, node.model);
|
|
3020
|
+
if (!mv.verified) {
|
|
3021
|
+
unverifiedModels.push(node.id);
|
|
3022
|
+
} else if (!mv.match) {
|
|
3023
|
+
modelMismatches.push(node.id);
|
|
3024
|
+
}
|
|
3025
|
+
const sv = tracker.verifySessionRef(node.sessionRef);
|
|
3026
|
+
if (sv.known === false) {
|
|
3027
|
+
unknownSessions.push(node.id);
|
|
3028
|
+
}
|
|
3029
|
+
} else {
|
|
3030
|
+
unverifiedModels.push(node.id);
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
return { unverifiedModels, modelMismatches, unknownSessions };
|
|
3034
|
+
}
|
|
3035
|
+
function formatWarnings(verification) {
|
|
3036
|
+
const warnings = [];
|
|
3037
|
+
if (verification.unverifiedModels.length > 0) {
|
|
3038
|
+
warnings.push(`model not verifiable for nodes: ${verification.unverifiedModels.join(", ")}`);
|
|
3039
|
+
}
|
|
3040
|
+
if (verification.modelMismatches.length > 0) {
|
|
3041
|
+
warnings.push(`model mismatch for nodes: ${verification.modelMismatches.join(", ")}`);
|
|
3042
|
+
}
|
|
3043
|
+
if (verification.unknownSessions.length > 0) {
|
|
3044
|
+
warnings.push(`unknown sessionRef for nodes: ${verification.unknownSessions.join(", ")}`);
|
|
3045
|
+
}
|
|
3046
|
+
return warnings;
|
|
3047
|
+
}
|
|
3048
|
+
function formatResponse(response) {
|
|
3049
|
+
const parts = [`report-run: ${response.result}`];
|
|
3050
|
+
if (response.detail !== undefined) {
|
|
3051
|
+
parts.push(response.detail);
|
|
3052
|
+
}
|
|
3053
|
+
if (response.warnings !== undefined && response.warnings.length > 0) {
|
|
3054
|
+
parts.push(`warnings: ${response.warnings.join("; ")}`);
|
|
3055
|
+
}
|
|
3056
|
+
return parts.join(" — ");
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
// src/plugin/reportRunSchema.ts
|
|
3060
|
+
import { z as z9 } from "zod";
|
|
3061
|
+
|
|
3062
|
+
// src/plugin/graphShadowIngress.ts
|
|
3063
|
+
import { z as z8 } from "zod";
|
|
3064
|
+
var LEGACY_EXECUTION_TRACE_VERSION = 1;
|
|
3065
|
+
var LegacyExecutionStatusSchema = z8.enum([
|
|
3066
|
+
"completed",
|
|
3067
|
+
"failed",
|
|
3068
|
+
"cancelled"
|
|
3069
|
+
]);
|
|
3070
|
+
var LegacyReviewOutcomeSchema = z8.enum([
|
|
3071
|
+
"approved",
|
|
3072
|
+
"rejected",
|
|
3073
|
+
"inconclusive"
|
|
3074
|
+
]);
|
|
3075
|
+
var LegacyTraceNodeV1Schema = z8.object({
|
|
3076
|
+
id: NodeIDSchema,
|
|
3077
|
+
role: NodeRoleSchema,
|
|
3078
|
+
model: z8.string().min(1),
|
|
3079
|
+
sessionRef: z8.string().min(1).optional(),
|
|
3080
|
+
errorClass: ErrorClassSchema.optional()
|
|
3081
|
+
}).strict();
|
|
3082
|
+
var LegacyExecutionTraceV1Schema = z8.object({
|
|
3083
|
+
version: z8.literal(LEGACY_EXECUTION_TRACE_VERSION),
|
|
3084
|
+
runID: NodeIDSchema,
|
|
3085
|
+
status: LegacyExecutionStatusSchema,
|
|
3086
|
+
outcome: LegacyReviewOutcomeSchema,
|
|
3087
|
+
fixes: z8.number().int().nonnegative(),
|
|
3088
|
+
nodes: z8.array(LegacyTraceNodeV1Schema).min(1),
|
|
3089
|
+
parentSessionRef: z8.string().min(1).optional()
|
|
3090
|
+
}).strict();
|
|
3091
|
+
function gateReason2(gate) {
|
|
3092
|
+
if (gate === undefined || gate.mode === "off") {
|
|
3093
|
+
return "graph-disabled";
|
|
3094
|
+
}
|
|
3095
|
+
if (gate.killSwitch) {
|
|
3096
|
+
return "kill-switch";
|
|
3097
|
+
}
|
|
3098
|
+
return;
|
|
3099
|
+
}
|
|
3100
|
+
function deriveOutcome(nodes) {
|
|
3101
|
+
for (let index = nodes.length - 1;index >= 0; index -= 1) {
|
|
3102
|
+
const node = nodes[index];
|
|
3103
|
+
if (node !== undefined && node.role === "reviewer") {
|
|
3104
|
+
return node.ok ? "approved" : "rejected";
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
return "inconclusive";
|
|
3108
|
+
}
|
|
3109
|
+
function toTraceNode(node) {
|
|
3110
|
+
return {
|
|
3111
|
+
id: node.id,
|
|
3112
|
+
role: node.role,
|
|
3113
|
+
model: node.model,
|
|
3114
|
+
...node.sessionRef !== undefined ? { sessionRef: node.sessionRef } : {},
|
|
3115
|
+
...node.errorClass !== undefined ? { errorClass: node.errorClass } : {}
|
|
3116
|
+
};
|
|
3117
|
+
}
|
|
3118
|
+
function ingestLegacyExecution(observation, gate) {
|
|
3119
|
+
const gated = gateReason2(gate);
|
|
3120
|
+
if (gated !== undefined) {
|
|
3121
|
+
return { eligible: false, reason: gated };
|
|
3122
|
+
}
|
|
3123
|
+
if (!observation.executed) {
|
|
3124
|
+
return { eligible: false, reason: "not-executed" };
|
|
3125
|
+
}
|
|
3126
|
+
if (observation.nodes.length === 0) {
|
|
3127
|
+
return { eligible: false, reason: "no-observation" };
|
|
3128
|
+
}
|
|
3129
|
+
const trace = {
|
|
3130
|
+
version: LEGACY_EXECUTION_TRACE_VERSION,
|
|
3131
|
+
runID: observation.runID,
|
|
3132
|
+
status: observation.status,
|
|
3133
|
+
outcome: deriveOutcome(observation.nodes),
|
|
3134
|
+
fixes: Math.max(0, Math.trunc(observation.fixes)),
|
|
3135
|
+
nodes: observation.nodes.map(toTraceNode),
|
|
3136
|
+
...observation.parentSessionRef !== undefined ? { parentSessionRef: observation.parentSessionRef } : {}
|
|
3137
|
+
};
|
|
3138
|
+
assertReferenceOnly(trace);
|
|
3139
|
+
return { eligible: true, trace };
|
|
3140
|
+
}
|
|
3141
|
+
|
|
3142
|
+
class LegacyTraceError extends Error {
|
|
3143
|
+
code = "invalid-legacy-trace";
|
|
3144
|
+
issues;
|
|
3145
|
+
constructor(error) {
|
|
3146
|
+
super(`invalid-legacy-trace: ${error.issues.map((issue) => issue.message).join("; ")}`);
|
|
3147
|
+
this.name = "LegacyTraceError";
|
|
3148
|
+
this.issues = error.issues.map((issue) => issue.message);
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
function parseLegacyExecutionTrace(input) {
|
|
3152
|
+
const result = LegacyExecutionTraceV1Schema.safeParse(input);
|
|
3153
|
+
if (!result.success) {
|
|
3154
|
+
throw new LegacyTraceError(result.error);
|
|
3155
|
+
}
|
|
3156
|
+
return result.data;
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
// src/plugin/reportRunSchema.ts
|
|
3160
|
+
var ReportRunNodeSchema = z9.object({
|
|
3161
|
+
id: NodeIDSchema,
|
|
3162
|
+
role: NodeRoleSchema,
|
|
3163
|
+
model: z9.string().min(1),
|
|
3164
|
+
ok: z9.boolean(),
|
|
3165
|
+
sessionRef: z9.string().min(1).optional(),
|
|
3166
|
+
errorClass: ErrorClassSchema.optional()
|
|
3167
|
+
}).strict();
|
|
3168
|
+
var ReportRunPayloadSchema = z9.object({
|
|
3169
|
+
runID: NodeIDSchema,
|
|
3170
|
+
status: LegacyExecutionStatusSchema,
|
|
3171
|
+
fixes: z9.number().int().nonnegative(),
|
|
3172
|
+
nodes: z9.array(ReportRunNodeSchema).min(1),
|
|
3173
|
+
parentSessionRef: z9.string().min(1).optional()
|
|
3174
|
+
}).strict();
|
|
3175
|
+
|
|
3176
|
+
// src/graph/types.ts
|
|
3177
|
+
var TERMINAL_NODE_STATUSES = new Set([
|
|
3178
|
+
"succeeded",
|
|
3179
|
+
"cancelled"
|
|
3180
|
+
]);
|
|
3181
|
+
var TERMINAL_RUN_STATUSES = new Set([
|
|
3182
|
+
"completed",
|
|
3183
|
+
"failed",
|
|
3184
|
+
"cancelled"
|
|
3185
|
+
]);
|
|
3186
|
+
|
|
3187
|
+
// src/graph/reducer.ts
|
|
3188
|
+
class ReducerError extends Error {
|
|
3189
|
+
code;
|
|
3190
|
+
detail;
|
|
3191
|
+
constructor(code, detail) {
|
|
3192
|
+
super(`${code}: ${detail}`);
|
|
3193
|
+
this.name = "ReducerError";
|
|
3194
|
+
this.code = code;
|
|
3195
|
+
this.detail = detail;
|
|
3196
|
+
}
|
|
3197
|
+
}
|
|
3198
|
+
function initialState(spec) {
|
|
3199
|
+
const nodes = {};
|
|
3200
|
+
for (const node of spec.nodes) {
|
|
3201
|
+
nodes[node.id] = { status: "pending", attempts: 0 };
|
|
3202
|
+
}
|
|
3203
|
+
return { runID: spec.runID, status: "running", seq: -1, nodes };
|
|
3204
|
+
}
|
|
3205
|
+
function withNode(state, nodeID, next) {
|
|
3206
|
+
return {
|
|
3207
|
+
...state,
|
|
3208
|
+
nodes: { ...state.nodes, [nodeID]: next },
|
|
3209
|
+
seq: state.seq
|
|
3210
|
+
};
|
|
3211
|
+
}
|
|
3212
|
+
function specNode(spec, nodeID) {
|
|
3213
|
+
return spec.nodes.find((node) => node.id === nodeID);
|
|
3214
|
+
}
|
|
3215
|
+
function applyRunStarted(state, seq, specDigest) {
|
|
3216
|
+
if (state.specDigest !== undefined) {
|
|
3217
|
+
if (state.specDigest === specDigest) {
|
|
3218
|
+
return state;
|
|
3219
|
+
}
|
|
3220
|
+
throw new ReducerError("run-started-conflict", specDigest);
|
|
3221
|
+
}
|
|
3222
|
+
return { ...state, specDigest, seq };
|
|
3223
|
+
}
|
|
3224
|
+
function applyDispatched(spec, state, event) {
|
|
3225
|
+
const node = specNode(spec, event.nodeID);
|
|
3226
|
+
const current = state.nodes[event.nodeID];
|
|
3227
|
+
if (node === undefined || current === undefined) {
|
|
3228
|
+
throw new ReducerError("unknown-node", event.nodeID);
|
|
3229
|
+
}
|
|
3230
|
+
for (const dep of node.dependsOn) {
|
|
3231
|
+
if (state.nodes[dep]?.status !== "succeeded") {
|
|
3232
|
+
throw new ReducerError("dependency-not-satisfied", `${event.nodeID} <- ${dep}`);
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
const running = {
|
|
3236
|
+
status: "running",
|
|
3237
|
+
attempts: current.attempts + 1,
|
|
3238
|
+
operationID: event.operationID,
|
|
3239
|
+
attemptID: event.attemptID
|
|
3240
|
+
};
|
|
3241
|
+
if (current.status === "pending") {
|
|
3242
|
+
return withNode({ ...state, seq: event.seq }, event.nodeID, running);
|
|
3243
|
+
}
|
|
3244
|
+
if (current.status === "failed") {
|
|
3245
|
+
if (current.operationID !== event.operationID) {
|
|
3246
|
+
throw new ReducerError("dispatch-conflict", event.nodeID);
|
|
3247
|
+
}
|
|
3248
|
+
return withNode({ ...state, seq: event.seq }, event.nodeID, running);
|
|
3249
|
+
}
|
|
3250
|
+
if (current.status === "running") {
|
|
3251
|
+
if (current.operationID === event.operationID && current.attemptID === event.attemptID) {
|
|
3252
|
+
return state;
|
|
3253
|
+
}
|
|
3254
|
+
throw new ReducerError("dispatch-conflict", event.nodeID);
|
|
3255
|
+
}
|
|
3256
|
+
throw new ReducerError("illegal-transition", `dispatch ${current.status}`);
|
|
3257
|
+
}
|
|
3258
|
+
function applySucceeded(state, event) {
|
|
3259
|
+
const current = state.nodes[event.nodeID];
|
|
3260
|
+
if (current === undefined) {
|
|
3261
|
+
throw new ReducerError("unknown-node", event.nodeID);
|
|
3262
|
+
}
|
|
3263
|
+
if (current.status === "succeeded") {
|
|
3264
|
+
if (current.operationID === event.operationID && current.artifactSha256 === event.artifact.sha256) {
|
|
3265
|
+
return state;
|
|
3266
|
+
}
|
|
3267
|
+
throw new ReducerError("receipt-conflict", event.nodeID);
|
|
3268
|
+
}
|
|
3269
|
+
if (current.status !== "running") {
|
|
3270
|
+
throw new ReducerError("illegal-transition", `succeed ${current.status}`);
|
|
3271
|
+
}
|
|
3272
|
+
if (current.operationID !== event.operationID || current.attemptID !== event.attemptID) {
|
|
3273
|
+
throw new ReducerError("receipt-conflict", event.nodeID);
|
|
3274
|
+
}
|
|
3275
|
+
return withNode({ ...state, seq: event.seq }, event.nodeID, {
|
|
3276
|
+
status: "succeeded",
|
|
3277
|
+
attempts: current.attempts,
|
|
3278
|
+
operationID: event.operationID,
|
|
3279
|
+
attemptID: event.attemptID,
|
|
3280
|
+
artifactSha256: event.artifact.sha256
|
|
3281
|
+
});
|
|
3282
|
+
}
|
|
3283
|
+
function applyFailed(state, event) {
|
|
3284
|
+
const current = state.nodes[event.nodeID];
|
|
3285
|
+
if (current === undefined) {
|
|
3286
|
+
throw new ReducerError("unknown-node", event.nodeID);
|
|
3287
|
+
}
|
|
3288
|
+
if (current.status === "failed") {
|
|
3289
|
+
if (current.operationID === event.operationID && current.attemptID === event.attemptID) {
|
|
3290
|
+
return state;
|
|
3291
|
+
}
|
|
3292
|
+
throw new ReducerError("receipt-conflict", event.nodeID);
|
|
3293
|
+
}
|
|
3294
|
+
if (current.status !== "running") {
|
|
3295
|
+
throw new ReducerError("illegal-transition", `fail ${current.status}`);
|
|
3296
|
+
}
|
|
3297
|
+
if (current.operationID !== event.operationID || current.attemptID !== event.attemptID) {
|
|
3298
|
+
throw new ReducerError("receipt-conflict", event.nodeID);
|
|
3299
|
+
}
|
|
3300
|
+
return withNode({ ...state, seq: event.seq }, event.nodeID, {
|
|
3301
|
+
status: "failed",
|
|
3302
|
+
attempts: current.attempts,
|
|
3303
|
+
operationID: event.operationID,
|
|
3304
|
+
attemptID: event.attemptID
|
|
3305
|
+
});
|
|
3306
|
+
}
|
|
3307
|
+
function applyCancelled(state, event) {
|
|
3308
|
+
const current = state.nodes[event.nodeID];
|
|
3309
|
+
if (current === undefined) {
|
|
3310
|
+
throw new ReducerError("unknown-node", event.nodeID);
|
|
3311
|
+
}
|
|
3312
|
+
if (current.status === "cancelled") {
|
|
3313
|
+
return state;
|
|
3314
|
+
}
|
|
3315
|
+
if (current.status === "succeeded") {
|
|
3316
|
+
throw new ReducerError("illegal-transition", "cancel succeeded");
|
|
3317
|
+
}
|
|
3318
|
+
return withNode({ ...state, seq: event.seq }, event.nodeID, {
|
|
3319
|
+
...current,
|
|
3320
|
+
status: "cancelled"
|
|
3321
|
+
});
|
|
3322
|
+
}
|
|
3323
|
+
function applyRunCompleted(state, seq) {
|
|
3324
|
+
for (const node of Object.values(state.nodes)) {
|
|
3325
|
+
if (node.status !== "succeeded" && node.status !== "cancelled") {
|
|
3326
|
+
throw new ReducerError("incomplete-run", node.status);
|
|
3327
|
+
}
|
|
3328
|
+
}
|
|
3329
|
+
return { ...state, status: "completed", seq };
|
|
3330
|
+
}
|
|
3331
|
+
function applyEvent(spec, state, event) {
|
|
3332
|
+
if (event.runID !== state.runID) {
|
|
3333
|
+
throw new ReducerError("run-mismatch", event.runID);
|
|
3334
|
+
}
|
|
3335
|
+
if (TERMINAL_RUN_STATUSES.has(state.status)) {
|
|
3336
|
+
if (event.type === "run.completed" && state.status === "completed" || event.type === "run.failed" && state.status === "failed" || event.type === "run.cancelled" && state.status === "cancelled") {
|
|
3337
|
+
return state;
|
|
3338
|
+
}
|
|
3339
|
+
throw new ReducerError("post-terminal", event.type);
|
|
3340
|
+
}
|
|
3341
|
+
switch (event.type) {
|
|
3342
|
+
case "run.started":
|
|
3343
|
+
return applyRunStarted(state, event.seq, event.specDigest);
|
|
3344
|
+
case "node.dispatched":
|
|
3345
|
+
return applyDispatched(spec, state, event);
|
|
3346
|
+
case "node.succeeded":
|
|
3347
|
+
return applySucceeded(state, event);
|
|
3348
|
+
case "node.failed":
|
|
3349
|
+
return applyFailed(state, event);
|
|
3350
|
+
case "node.cancelled":
|
|
3351
|
+
return applyCancelled(state, event);
|
|
3352
|
+
case "run.completed":
|
|
3353
|
+
return applyRunCompleted(state, event.seq);
|
|
3354
|
+
case "run.failed":
|
|
3355
|
+
return { ...state, status: "failed", seq: event.seq };
|
|
3356
|
+
case "run.cancelled":
|
|
3357
|
+
return { ...state, status: "cancelled", seq: event.seq };
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3361
|
+
// src/orchestrator/graphShadow.ts
|
|
3362
|
+
class ShadowMirrorError extends Error {
|
|
3363
|
+
code;
|
|
3364
|
+
detail;
|
|
3365
|
+
constructor(code, detail) {
|
|
3366
|
+
super(`${code}: ${detail}`);
|
|
3367
|
+
this.name = "ShadowMirrorError";
|
|
3368
|
+
this.code = code;
|
|
3369
|
+
this.detail = detail;
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
function traceAnchorDigest(trace) {
|
|
3373
|
+
return sha256Hex(JSON.stringify({
|
|
3374
|
+
version: trace.version,
|
|
3375
|
+
runID: trace.runID,
|
|
3376
|
+
nodes: trace.nodes.map((node) => [node.id, node.role])
|
|
3377
|
+
}));
|
|
3378
|
+
}
|
|
3379
|
+
function compileShadowSpec(trace) {
|
|
3380
|
+
const anchor = { taskID: trace.runID, specDigest: traceAnchorDigest(trace) };
|
|
3381
|
+
const nodes = [];
|
|
3382
|
+
let previousID;
|
|
3383
|
+
let lastImplementerID;
|
|
3384
|
+
for (const node of trace.nodes) {
|
|
3385
|
+
const dependsOn = previousID === undefined ? [] : [previousID];
|
|
3386
|
+
if (node.role === "implementer") {
|
|
3387
|
+
nodes.push({ id: node.id, role: "implementer", dependsOn, anchor });
|
|
3388
|
+
lastImplementerID = node.id;
|
|
3389
|
+
} else {
|
|
3390
|
+
nodes.push({
|
|
3391
|
+
id: node.id,
|
|
3392
|
+
role: "reviewer",
|
|
3393
|
+
dependsOn,
|
|
3394
|
+
anchor,
|
|
3395
|
+
...lastImplementerID === undefined ? {} : { reviews: lastImplementerID }
|
|
3396
|
+
});
|
|
3397
|
+
}
|
|
3398
|
+
previousID = node.id;
|
|
3399
|
+
}
|
|
3400
|
+
let spec;
|
|
3401
|
+
try {
|
|
3402
|
+
spec = GraphSpecV1Schema.parse({ version: 1, runID: trace.runID, nodes });
|
|
3403
|
+
validateGraphSpec(spec);
|
|
3404
|
+
} catch (error) {
|
|
3405
|
+
throw new ShadowMirrorError("invalid-spec", error instanceof Error ? error.message : String(error));
|
|
3406
|
+
}
|
|
3407
|
+
assertReferenceOnly(spec);
|
|
3408
|
+
return spec;
|
|
3409
|
+
}
|
|
3410
|
+
var TERMINAL_EVENT = {
|
|
3411
|
+
completed: "run.completed",
|
|
3412
|
+
failed: "run.failed",
|
|
3413
|
+
cancelled: "run.cancelled"
|
|
3414
|
+
};
|
|
3415
|
+
function shadowArtifact(runID, nodeID) {
|
|
3416
|
+
return {
|
|
3417
|
+
uri: `shadow://${runID}/${nodeID}`,
|
|
3418
|
+
sha256: sha256Hex(`shadow-artifact\x00${runID}\x00${nodeID}`),
|
|
3419
|
+
bytes: 0
|
|
3420
|
+
};
|
|
3421
|
+
}
|
|
3422
|
+
function mirrorJournal(spec, trace) {
|
|
3423
|
+
const runID = trace.runID;
|
|
3424
|
+
const events = [
|
|
3425
|
+
{
|
|
3426
|
+
v: 1,
|
|
3427
|
+
seq: 0,
|
|
3428
|
+
runID,
|
|
3429
|
+
type: "run.started",
|
|
3430
|
+
specDigest: graphSpecDigest(spec)
|
|
3431
|
+
}
|
|
3432
|
+
];
|
|
3433
|
+
for (const node of trace.nodes) {
|
|
3434
|
+
const operation = operationID(runID, node.id);
|
|
3435
|
+
const attempt = attemptID(operation, 0);
|
|
3436
|
+
events.push({
|
|
3437
|
+
v: 1,
|
|
3438
|
+
seq: events.length,
|
|
3439
|
+
runID,
|
|
3440
|
+
type: "node.dispatched",
|
|
3441
|
+
nodeID: node.id,
|
|
3442
|
+
operationID: operation,
|
|
3443
|
+
attemptID: attempt
|
|
3444
|
+
});
|
|
3445
|
+
if (node.errorClass === undefined) {
|
|
3446
|
+
events.push({
|
|
3447
|
+
v: 1,
|
|
3448
|
+
seq: events.length,
|
|
3449
|
+
runID,
|
|
3450
|
+
type: "node.succeeded",
|
|
3451
|
+
nodeID: node.id,
|
|
3452
|
+
operationID: operation,
|
|
3453
|
+
attemptID: attempt,
|
|
3454
|
+
artifact: shadowArtifact(runID, node.id)
|
|
3455
|
+
});
|
|
3456
|
+
continue;
|
|
3457
|
+
}
|
|
3458
|
+
if (node.errorClass === "cancelled") {
|
|
3459
|
+
events.push({
|
|
3460
|
+
v: 1,
|
|
3461
|
+
seq: events.length,
|
|
3462
|
+
runID,
|
|
3463
|
+
type: "node.cancelled",
|
|
3464
|
+
nodeID: node.id
|
|
3465
|
+
});
|
|
3466
|
+
continue;
|
|
3467
|
+
}
|
|
3468
|
+
events.push({
|
|
3469
|
+
v: 1,
|
|
3470
|
+
seq: events.length,
|
|
3471
|
+
runID,
|
|
3472
|
+
type: "node.failed",
|
|
3473
|
+
nodeID: node.id,
|
|
3474
|
+
operationID: operation,
|
|
3475
|
+
attemptID: attempt,
|
|
3476
|
+
errorClass: node.errorClass
|
|
3477
|
+
});
|
|
3478
|
+
}
|
|
3479
|
+
events.push({
|
|
3480
|
+
v: 1,
|
|
3481
|
+
seq: events.length,
|
|
3482
|
+
runID,
|
|
3483
|
+
type: TERMINAL_EVENT[trace.status]
|
|
3484
|
+
});
|
|
3485
|
+
return events;
|
|
3486
|
+
}
|
|
3487
|
+
function reduceMirror(spec, journal) {
|
|
3488
|
+
let state = initialState(spec);
|
|
3489
|
+
try {
|
|
3490
|
+
for (const event of journal) {
|
|
3491
|
+
state = applyEvent(spec, state, event);
|
|
3492
|
+
}
|
|
3493
|
+
} catch (error) {
|
|
3494
|
+
throw new ShadowMirrorError("illegal-sequence", error instanceof ReducerError ? `${error.code}: ${error.detail}` : String(error));
|
|
3495
|
+
}
|
|
3496
|
+
return state;
|
|
3497
|
+
}
|
|
3498
|
+
function projectLegacySummary(trace) {
|
|
3499
|
+
return {
|
|
3500
|
+
runID: trace.runID,
|
|
3501
|
+
status: trace.status,
|
|
3502
|
+
outcome: trace.outcome,
|
|
3503
|
+
fixes: trace.fixes,
|
|
3504
|
+
nodes: trace.nodes.map((node) => ({
|
|
3505
|
+
id: node.id,
|
|
3506
|
+
role: node.role,
|
|
3507
|
+
model: node.model
|
|
3508
|
+
}))
|
|
3509
|
+
};
|
|
3510
|
+
}
|
|
3511
|
+
function projectShadowSummary(spec, trace) {
|
|
3512
|
+
const implementers = spec.nodes.filter((node) => node.role === "implementer").length;
|
|
3513
|
+
return {
|
|
3514
|
+
runID: spec.runID,
|
|
3515
|
+
status: trace.status,
|
|
3516
|
+
outcome: trace.outcome,
|
|
3517
|
+
fixes: implementers - 1,
|
|
3518
|
+
nodes: spec.nodes.map((node, index) => ({
|
|
3519
|
+
id: node.id,
|
|
3520
|
+
role: node.role,
|
|
3521
|
+
model: trace.nodes[index].model
|
|
3522
|
+
}))
|
|
3523
|
+
};
|
|
3524
|
+
}
|
|
3525
|
+
function mirrorLegacyExecution(trace) {
|
|
3526
|
+
const spec = compileShadowSpec(trace);
|
|
3527
|
+
const journal = mirrorJournal(spec, trace);
|
|
3528
|
+
const state = reduceMirror(spec, journal);
|
|
3529
|
+
const summary = projectShadowSummary(spec, trace);
|
|
3530
|
+
assertReferenceOnly(summary);
|
|
3531
|
+
return {
|
|
3532
|
+
spec,
|
|
3533
|
+
specDigest: graphSpecDigest(spec),
|
|
3534
|
+
journal,
|
|
3535
|
+
state,
|
|
3536
|
+
summary
|
|
3537
|
+
};
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3540
|
+
// src/orchestrator/graphFacade.ts
|
|
3541
|
+
function mirrorLegacyTrace(trace) {
|
|
3542
|
+
try {
|
|
3543
|
+
return {
|
|
3544
|
+
ok: true,
|
|
3545
|
+
mirror: mirrorLegacyExecution(parseLegacyExecutionTrace(trace))
|
|
3546
|
+
};
|
|
3547
|
+
} catch (error) {
|
|
3548
|
+
if (error instanceof ShadowMirrorError) {
|
|
3549
|
+
return { ok: false, code: error.code, detail: error.detail };
|
|
3550
|
+
}
|
|
3551
|
+
if (error instanceof LegacyTraceError) {
|
|
3552
|
+
return {
|
|
3553
|
+
ok: false,
|
|
3554
|
+
code: "invalid-trace",
|
|
3555
|
+
detail: error.issues.join("; ")
|
|
3556
|
+
};
|
|
3557
|
+
}
|
|
3558
|
+
return {
|
|
3559
|
+
ok: false,
|
|
3560
|
+
code: "mirror-error",
|
|
3561
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
3562
|
+
};
|
|
3563
|
+
}
|
|
1761
3564
|
}
|
|
1762
|
-
async function runYolo(positionals, deps, opencodeConfigPath) {
|
|
1763
|
-
const sub = positionals[1] ?? "status";
|
|
1764
|
-
if (sub !== "status" && sub !== "on" && sub !== "off") {
|
|
1765
|
-
return {
|
|
1766
|
-
exitCode: 1,
|
|
1767
|
-
stdout: `Subcomando de yolo desconocido: ${sub}
|
|
1768
3565
|
|
|
1769
|
-
|
|
1770
|
-
|
|
3566
|
+
// src/orchestrator/shadowComparator.ts
|
|
3567
|
+
function sequenceEqual(a, b) {
|
|
3568
|
+
if (a.length !== b.length) {
|
|
3569
|
+
return false;
|
|
1771
3570
|
}
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
};
|
|
3571
|
+
return a.every((value, index) => value === b[index]);
|
|
3572
|
+
}
|
|
3573
|
+
function compareShadowRun(legacy, shadow) {
|
|
3574
|
+
if (legacy === undefined || shadow === undefined) {
|
|
3575
|
+
return { verdict: "inconclusive", divergences: [] };
|
|
1778
3576
|
}
|
|
1779
|
-
|
|
1780
|
-
|
|
3577
|
+
const divergences = [];
|
|
3578
|
+
if (legacy.status !== shadow.status) {
|
|
3579
|
+
divergences.push("status");
|
|
1781
3580
|
}
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
return {
|
|
1785
|
-
exitCode: 0,
|
|
1786
|
-
stdout: `Sin cambios. ${renderYoloStatus(enable)}`
|
|
1787
|
-
};
|
|
3581
|
+
if (!sequenceEqual(legacy.nodes.map((node) => node.id), shadow.nodes.map((node) => node.id))) {
|
|
3582
|
+
divergences.push("order");
|
|
1788
3583
|
}
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
const agentNote = await regenerateOrchestratorAgent(deps, config, enable);
|
|
1792
|
-
return {
|
|
1793
|
-
exitCode: 0,
|
|
1794
|
-
stdout: `${opencodeConfigPath} actualizado. ${renderYoloStatus(enable)}${agentNote}${enable ? `
|
|
1795
|
-
Nota: YOLO no desactiva la privacidad de openteam; los prompts sensibles siguen sin salir a frontier con forceLocalOnSensitive.` : ""}
|
|
1796
|
-
Reinicia opencode (o recarga) para que los agentes tomen los nuevos permisos.`
|
|
1797
|
-
};
|
|
1798
|
-
}
|
|
1799
|
-
async function regenerateOrchestratorAgent(deps, config, enable) {
|
|
1800
|
-
const model = config.model;
|
|
1801
|
-
if (typeof model !== "string" || model.trim() === "") {
|
|
1802
|
-
return `
|
|
1803
|
-
Aviso: no pude regenerar ${deps.orchestratorAgentPath} (falta 'model' en opencode.json); ajusta su permission a mano.`;
|
|
3584
|
+
if (!sequenceEqual(legacy.nodes.map((node) => node.role), shadow.nodes.map((node) => node.role))) {
|
|
3585
|
+
divergences.push("roles");
|
|
1804
3586
|
}
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
Aviso: no pude regenerar ${deps.orchestratorAgentPath} (model inválido en opencode.json); ajusta su permission a mano.`;
|
|
3587
|
+
if (!sequenceEqual(legacy.nodes.map((node) => node.model), shadow.nodes.map((node) => node.model))) {
|
|
3588
|
+
divergences.push("models");
|
|
3589
|
+
}
|
|
3590
|
+
if (legacy.fixes !== shadow.fixes) {
|
|
3591
|
+
divergences.push("fixes");
|
|
3592
|
+
}
|
|
3593
|
+
if (legacy.outcome !== shadow.outcome) {
|
|
3594
|
+
divergences.push("outcome");
|
|
1814
3595
|
}
|
|
3596
|
+
return {
|
|
3597
|
+
verdict: divergences.length === 0 ? "match" : "divergent",
|
|
3598
|
+
divergences
|
|
3599
|
+
};
|
|
1815
3600
|
}
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
let defaultModel;
|
|
1825
|
-
if (typeof rawDefault === "string" && rawDefault.trim() !== "") {
|
|
1826
|
-
try {
|
|
1827
|
-
defaultModel = parseModelRef(rawDefault);
|
|
1828
|
-
} catch {
|
|
1829
|
-
defaultModel = undefined;
|
|
3601
|
+
|
|
3602
|
+
// src/storage/graph/soakRecorder.ts
|
|
3603
|
+
async function ensureRecorderId(ports, basePath) {
|
|
3604
|
+
const idPath = `${basePath}/soak-recorder-id`;
|
|
3605
|
+
if (await ports.fs.exists(idPath)) {
|
|
3606
|
+
const id = (await ports.fs.readFile(idPath)).trim();
|
|
3607
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(id)) {
|
|
3608
|
+
return id;
|
|
1830
3609
|
}
|
|
1831
3610
|
}
|
|
1832
|
-
const
|
|
1833
|
-
|
|
1834
|
-
return
|
|
3611
|
+
const newId = ports.id.randomUUID();
|
|
3612
|
+
await ports.fs.writeFile(idPath, newId);
|
|
3613
|
+
return newId;
|
|
1835
3614
|
}
|
|
1836
|
-
async function
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
const
|
|
1841
|
-
const
|
|
1842
|
-
|
|
1843
|
-
if (
|
|
1844
|
-
|
|
1845
|
-
}
|
|
1846
|
-
if (command === "local") {
|
|
1847
|
-
return await runLocal(parsed.positionals, deps, configPath);
|
|
1848
|
-
}
|
|
1849
|
-
if (command === "yolo") {
|
|
1850
|
-
return await runYolo(parsed.positionals, deps, opencodeConfigPath);
|
|
1851
|
-
}
|
|
1852
|
-
if (command === "doctor") {
|
|
1853
|
-
const config = await deps.loadConfig(configPath);
|
|
1854
|
-
const [snapshots, records] = await Promise.all([
|
|
1855
|
-
deps.probe(config),
|
|
1856
|
-
deps.readTelemetry(telemetryPath)
|
|
1857
|
-
]);
|
|
1858
|
-
return {
|
|
1859
|
-
exitCode: 0,
|
|
1860
|
-
stdout: renderDoctor({
|
|
1861
|
-
config,
|
|
1862
|
-
snapshots,
|
|
1863
|
-
telemetryPath,
|
|
1864
|
-
telemetryRecords: records.length
|
|
1865
|
-
})
|
|
1866
|
-
};
|
|
1867
|
-
}
|
|
1868
|
-
if (command === "report") {
|
|
1869
|
-
const records = await deps.readTelemetry(telemetryPath);
|
|
1870
|
-
return {
|
|
1871
|
-
exitCode: 0,
|
|
1872
|
-
stdout: renderReport(summarizeCostRecords(records))
|
|
1873
|
-
};
|
|
3615
|
+
async function readAllChains(ports, evidenceDir) {
|
|
3616
|
+
if (!await ports.fs.exists(evidenceDir)) {
|
|
3617
|
+
return [];
|
|
3618
|
+
}
|
|
3619
|
+
const files = await ports.fs.readDir(evidenceDir);
|
|
3620
|
+
const chains = [];
|
|
3621
|
+
for (const file of files) {
|
|
3622
|
+
if (!file.endsWith(".jsonl")) {
|
|
3623
|
+
continue;
|
|
1874
3624
|
}
|
|
1875
|
-
|
|
1876
|
-
|
|
3625
|
+
const content = await ports.fs.readFile(`${evidenceDir}/${file}`);
|
|
3626
|
+
const lines = content.split(`
|
|
3627
|
+
`).filter((l) => l.trim().length > 0);
|
|
3628
|
+
const observations = [];
|
|
3629
|
+
for (const line of lines) {
|
|
3630
|
+
const parsed = SoakObservationSchema.safeParse(JSON.parse(line));
|
|
3631
|
+
if (parsed.success) {
|
|
3632
|
+
observations.push(parsed.data);
|
|
3633
|
+
}
|
|
1877
3634
|
}
|
|
1878
|
-
if (
|
|
1879
|
-
|
|
1880
|
-
return { exitCode: 0, stdout: renderConsoleStatus(config.console) };
|
|
3635
|
+
if (observations.length > 0) {
|
|
3636
|
+
chains.push(observations);
|
|
1881
3637
|
}
|
|
1882
|
-
|
|
1883
|
-
|
|
3638
|
+
}
|
|
3639
|
+
return chains;
|
|
3640
|
+
}
|
|
3641
|
+
function canonicalObservationPreimage2(obs) {
|
|
3642
|
+
return JSON.stringify({
|
|
3643
|
+
version: obs.version,
|
|
3644
|
+
seq: obs.seq,
|
|
3645
|
+
timestamp: obs.timestamp,
|
|
3646
|
+
platform: obs.platform,
|
|
3647
|
+
opencodeVersion: obs.opencodeVersion,
|
|
3648
|
+
provenance: obs.provenance,
|
|
3649
|
+
traceDigest: obs.traceDigest,
|
|
3650
|
+
criticalDivergences: obs.criticalDivergences,
|
|
3651
|
+
privacy: obs.privacy,
|
|
3652
|
+
duplicateEffects: obs.duplicateEffects,
|
|
3653
|
+
modelVerification: obs.modelVerification
|
|
3654
|
+
});
|
|
3655
|
+
}
|
|
3656
|
+
async function appendObservation(ports, evidenceDir, recorderId, input) {
|
|
3657
|
+
await ports.fs.mkdir(evidenceDir);
|
|
3658
|
+
const chainPath = `${evidenceDir}/${recorderId}.jsonl`;
|
|
3659
|
+
let seq = 0;
|
|
3660
|
+
let prevChainDigest = "0".repeat(64);
|
|
3661
|
+
if (await ports.fs.exists(chainPath)) {
|
|
3662
|
+
const content = await ports.fs.readFile(chainPath);
|
|
3663
|
+
const lines = content.split(`
|
|
3664
|
+
`).filter((l) => l.trim().length > 0);
|
|
3665
|
+
if (lines.length > 0) {
|
|
3666
|
+
const lastLine = lines[lines.length - 1];
|
|
3667
|
+
let rawParsed;
|
|
3668
|
+
try {
|
|
3669
|
+
rawParsed = JSON.parse(lastLine);
|
|
3670
|
+
} catch {
|
|
3671
|
+
throw new Error("corrupt chain tail: invalid JSON");
|
|
3672
|
+
}
|
|
3673
|
+
const parsed = SoakObservationSchema.safeParse(rawParsed);
|
|
3674
|
+
if (!parsed.success) {
|
|
3675
|
+
const detail = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.code}`).join("; ");
|
|
3676
|
+
throw new Error(`corrupt chain tail: ${detail}`);
|
|
3677
|
+
}
|
|
3678
|
+
seq = parsed.data.seq + 1;
|
|
3679
|
+
prevChainDigest = parsed.data.chainDigest;
|
|
1884
3680
|
}
|
|
1885
|
-
return {
|
|
1886
|
-
exitCode: 1,
|
|
1887
|
-
stdout: `Comando desconocido: ${command}
|
|
1888
|
-
|
|
1889
|
-
${HELP}`
|
|
1890
|
-
};
|
|
1891
|
-
} catch (error) {
|
|
1892
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1893
|
-
return { exitCode: 1, stdout: `Error: ${message}` };
|
|
1894
3681
|
}
|
|
3682
|
+
const timestamp = ports.clock.now();
|
|
3683
|
+
const base = {
|
|
3684
|
+
version: 1,
|
|
3685
|
+
seq,
|
|
3686
|
+
timestamp,
|
|
3687
|
+
platform: input.platform,
|
|
3688
|
+
opencodeVersion: input.opencodeVersion,
|
|
3689
|
+
provenance: input.provenance,
|
|
3690
|
+
traceDigest: input.traceDigest,
|
|
3691
|
+
criticalDivergences: input.criticalDivergences,
|
|
3692
|
+
privacy: input.privacy,
|
|
3693
|
+
duplicateEffects: input.duplicateEffects,
|
|
3694
|
+
modelVerification: input.modelVerification
|
|
3695
|
+
};
|
|
3696
|
+
const preimage = canonicalObservationPreimage2(base);
|
|
3697
|
+
const chainDigest = sha256Hex(prevChainDigest + preimage);
|
|
3698
|
+
const observation = { ...base, chainDigest };
|
|
3699
|
+
assertReferenceOnly(observation);
|
|
3700
|
+
const line = JSON.stringify(observation);
|
|
3701
|
+
if (await ports.fs.exists(chainPath)) {
|
|
3702
|
+
const existing = await ports.fs.readFile(chainPath);
|
|
3703
|
+
await ports.fs.writeFile(chainPath, `${existing}
|
|
3704
|
+
${line}`);
|
|
3705
|
+
} else {
|
|
3706
|
+
await ports.fs.writeFile(chainPath, line);
|
|
3707
|
+
}
|
|
3708
|
+
return observation;
|
|
1895
3709
|
}
|
|
1896
3710
|
|
|
1897
|
-
// src/plugin/
|
|
1898
|
-
function
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
3711
|
+
// src/plugin/soakObserver.ts
|
|
3712
|
+
function resolvePlatform(platform) {
|
|
3713
|
+
if (platform === "win32")
|
|
3714
|
+
return "win32";
|
|
3715
|
+
if (platform === "darwin")
|
|
3716
|
+
return "darwin";
|
|
3717
|
+
if (platform === "linux")
|
|
3718
|
+
return "linux";
|
|
3719
|
+
return null;
|
|
3720
|
+
}
|
|
3721
|
+
function countCriticalDivergences(parity, criticalCodes) {
|
|
3722
|
+
if (parity === undefined)
|
|
3723
|
+
return null;
|
|
3724
|
+
if (criticalCodes.length === 0)
|
|
3725
|
+
return null;
|
|
3726
|
+
const criticalSet = new Set(criticalCodes);
|
|
3727
|
+
return parity.divergences.filter((code) => criticalSet.has(code)).length;
|
|
3728
|
+
}
|
|
3729
|
+
function scanTracePrivacy(trace) {
|
|
3730
|
+
const result = scanReferenceOnly([trace]);
|
|
3731
|
+
return {
|
|
3732
|
+
surfacesScanned: result.surfacesScanned,
|
|
3733
|
+
rawFindings: result.rawFindings
|
|
3734
|
+
};
|
|
3735
|
+
}
|
|
3736
|
+
function detectDuplicateEffects(trace) {
|
|
3737
|
+
const nodes = trace.nodes;
|
|
3738
|
+
if (nodes.length === 0)
|
|
3739
|
+
return null;
|
|
3740
|
+
const seen = new Set;
|
|
3741
|
+
let duplicates = 0;
|
|
3742
|
+
for (const node of nodes) {
|
|
3743
|
+
if (seen.has(node.id)) {
|
|
3744
|
+
duplicates++;
|
|
3745
|
+
} else {
|
|
3746
|
+
seen.add(node.id);
|
|
3747
|
+
}
|
|
1914
3748
|
}
|
|
3749
|
+
return { effectsExamined: nodes.length, duplicatesFound: duplicates };
|
|
1915
3750
|
}
|
|
1916
|
-
function
|
|
1917
|
-
|
|
1918
|
-
|
|
3751
|
+
function traceToObservationInput(trace, opencodeVersion, parity, criticalCodes, modelVerification, platformOverride) {
|
|
3752
|
+
const platform = resolvePlatform(platformOverride ?? process.platform);
|
|
3753
|
+
if (platform === null)
|
|
3754
|
+
return null;
|
|
3755
|
+
const traceDigest = sha256Hex(JSON.stringify(trace));
|
|
3756
|
+
return {
|
|
3757
|
+
platform,
|
|
3758
|
+
opencodeVersion,
|
|
3759
|
+
provenance: "genuine-usage",
|
|
3760
|
+
traceDigest,
|
|
3761
|
+
criticalDivergences: countCriticalDivergences(parity, criticalCodes),
|
|
3762
|
+
privacy: scanTracePrivacy(trace),
|
|
3763
|
+
duplicateEffects: detectDuplicateEffects(trace),
|
|
3764
|
+
modelVerification
|
|
3765
|
+
};
|
|
3766
|
+
}
|
|
3767
|
+
async function recordSoakObservation(ports, evidenceDir, recorderId, trace, opencodeVersion, parity, criticalCodes, modelVerification, platformOverride) {
|
|
3768
|
+
const input = traceToObservationInput(trace, opencodeVersion, parity, criticalCodes, modelVerification, platformOverride);
|
|
3769
|
+
if (input === null)
|
|
3770
|
+
return false;
|
|
3771
|
+
await appendObservation(ports, evidenceDir, recorderId, input);
|
|
3772
|
+
return true;
|
|
3773
|
+
}
|
|
3774
|
+
|
|
3775
|
+
// src/plugin/shadowPipeline.ts
|
|
3776
|
+
function createShadowPipeline(deps) {
|
|
3777
|
+
return async (observation, modelVerification) => {
|
|
3778
|
+
try {
|
|
3779
|
+
const ingested = ingestLegacyExecution(observation, deps.gate);
|
|
3780
|
+
if (!ingested.eligible) {
|
|
3781
|
+
return { kind: "ineligible", reason: ingested.reason };
|
|
3782
|
+
}
|
|
3783
|
+
const mirror = mirrorLegacyTrace(ingested.trace);
|
|
3784
|
+
let parity;
|
|
3785
|
+
if (mirror.ok) {
|
|
3786
|
+
const legacySummary = projectLegacySummary(ingested.trace);
|
|
3787
|
+
const report = compareShadowRun(legacySummary, mirror.mirror.summary);
|
|
3788
|
+
parity = { divergences: [...report.divergences] };
|
|
3789
|
+
}
|
|
3790
|
+
const recorded = await recordSoakObservation(deps.ports, deps.evidenceDir, deps.recorderId, ingested.trace, deps.opencodeVersion, parity, deps.criticalCodes, modelVerification ?? null, deps.platformOverride);
|
|
3791
|
+
if (!recorded) {
|
|
3792
|
+
return {
|
|
3793
|
+
kind: "observation-rejected",
|
|
3794
|
+
reason: "unrecognized-platform"
|
|
3795
|
+
};
|
|
3796
|
+
}
|
|
3797
|
+
return { kind: "observed" };
|
|
3798
|
+
} catch (error) {
|
|
3799
|
+
return {
|
|
3800
|
+
kind: "contained-failure",
|
|
3801
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
3802
|
+
};
|
|
3803
|
+
}
|
|
3804
|
+
};
|
|
3805
|
+
}
|
|
3806
|
+
|
|
3807
|
+
// src/plugin/graphTool.ts
|
|
3808
|
+
function describeDecision(decision) {
|
|
3809
|
+
if (decision.route === "graph") {
|
|
3810
|
+
return `graph: la tarea ${decision.taskID} se enrutaría al runtime activo.`;
|
|
3811
|
+
}
|
|
3812
|
+
switch (decision.reason) {
|
|
3813
|
+
case "gate-closed":
|
|
3814
|
+
return "legacy: gate-closed — el ingress activo no es alcanzable por configuración en esta versión.";
|
|
3815
|
+
case "kill-switch":
|
|
3816
|
+
return "legacy: kill-switch — apagado de emergencia activo.";
|
|
3817
|
+
case "graph-disabled":
|
|
3818
|
+
return "legacy: graph-disabled — graph.mode no es 'active'.";
|
|
3819
|
+
case "not-allow-listed":
|
|
3820
|
+
return "legacy: not-allow-listed — la tarea no está en la allow-list del ingress.";
|
|
3821
|
+
}
|
|
3822
|
+
}
|
|
3823
|
+
function describeOutcome(outcome) {
|
|
3824
|
+
switch (outcome.kind) {
|
|
3825
|
+
case "legacy":
|
|
3826
|
+
return describeDecision({ route: "legacy", reason: outcome.reason });
|
|
3827
|
+
case "accepted":
|
|
3828
|
+
return `accepted: run ${outcome.runID}, paso ${outcome.step.kind}.`;
|
|
3829
|
+
case "revision":
|
|
3830
|
+
return `revision: run ${outcome.runID}, plan ${outcome.plan.kind}.`;
|
|
3831
|
+
case "reassigned":
|
|
3832
|
+
return `reassigned: run ${outcome.runID}, reviewer ${outcome.reviewerID}.`;
|
|
3833
|
+
case "cancelled":
|
|
3834
|
+
return `cancelled: run ${outcome.runID}, ${outcome.cancelledNodeIDs.length} nodo(s) cancelado(s).`;
|
|
3835
|
+
case "error":
|
|
3836
|
+
return `error: ${outcome.code}.`;
|
|
3837
|
+
}
|
|
3838
|
+
}
|
|
3839
|
+
function unavailableReason(deps, taskID) {
|
|
3840
|
+
const decision = decideIngress(deps.policy, taskID);
|
|
3841
|
+
return decision.route === "legacy" ? describeDecision(decision) : "unavailable: el ejecutor de efectos del runtime activo no está enchufado.";
|
|
3842
|
+
}
|
|
3843
|
+
function createGraphTool(deps) {
|
|
3844
|
+
const observe = deps.shadow !== undefined ? createShadowPipeline(deps.shadow) : async () => ({
|
|
3845
|
+
kind: "ineligible",
|
|
3846
|
+
reason: "shadow-not-configured"
|
|
3847
|
+
});
|
|
3848
|
+
const definition = tool2({
|
|
3849
|
+
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.",
|
|
1919
3850
|
args: {
|
|
1920
|
-
action:
|
|
1921
|
-
|
|
3851
|
+
action: tool2.schema.enum(["status", "start", "cancel", "report-run"]).describe("Acción a ejecutar"),
|
|
3852
|
+
taskID: tool2.schema.string().optional().describe("Identificador de la tarea SDD"),
|
|
3853
|
+
runID: tool2.schema.string().optional().describe("Identificador del run (para action=cancel o report-run)"),
|
|
3854
|
+
status: tool2.schema.enum(["completed", "failed", "cancelled"]).optional().describe("Estado terminal del run (solo action=report-run)"),
|
|
3855
|
+
fixes: tool2.schema.number().optional().describe("Número de iteraciones de fix (solo action=report-run)"),
|
|
3856
|
+
nodes: tool2.schema.array(tool2.schema.object({
|
|
3857
|
+
id: tool2.schema.string().describe("ID del nodo"),
|
|
3858
|
+
role: tool2.schema.enum(["implementer", "reviewer"]).describe("Rol del nodo"),
|
|
3859
|
+
model: tool2.schema.string().describe("Modelo utilizado por el nodo"),
|
|
3860
|
+
ok: tool2.schema.boolean().describe("Éxito terminal del nodo"),
|
|
3861
|
+
sessionRef: tool2.schema.string().optional().describe("ID de sesión de opencode del nodo"),
|
|
3862
|
+
errorClass: tool2.schema.enum(["transient", "permanent", "exhausted"]).optional().describe("Clase de error si el nodo falló")
|
|
3863
|
+
})).optional().describe("Nodos de la ejecución (solo action=report-run)"),
|
|
3864
|
+
parentSessionRef: tool2.schema.string().optional().describe("Sesión padre del run (solo action=report-run)")
|
|
1922
3865
|
},
|
|
1923
3866
|
async execute(args) {
|
|
1924
|
-
|
|
1925
|
-
|
|
3867
|
+
if (args.action === "report-run") {
|
|
3868
|
+
return handleReportRun(args, observe, deps.tracker);
|
|
3869
|
+
}
|
|
3870
|
+
const taskID = args.taskID ?? "";
|
|
3871
|
+
if (args.action === "status") {
|
|
3872
|
+
return describeDecision(decideIngress(deps.policy, taskID));
|
|
3873
|
+
}
|
|
3874
|
+
const ingress = deps.ingress;
|
|
3875
|
+
if (ingress === undefined) {
|
|
3876
|
+
return unavailableReason(deps, taskID);
|
|
3877
|
+
}
|
|
3878
|
+
if (args.action === "cancel") {
|
|
3879
|
+
return describeOutcome(await ingress.cancel(args.runID ?? ""));
|
|
3880
|
+
}
|
|
3881
|
+
return describeOutcome(await ingress.start({
|
|
3882
|
+
id: taskID,
|
|
3883
|
+
implementerRoleID: "linus",
|
|
3884
|
+
title: taskID,
|
|
3885
|
+
brief: taskID,
|
|
3886
|
+
task: {
|
|
3887
|
+
promptChars: 0,
|
|
3888
|
+
hasCode: false,
|
|
3889
|
+
estimatedInputTokens: 0,
|
|
3890
|
+
estimatedOutputTokens: 0
|
|
3891
|
+
}
|
|
3892
|
+
}));
|
|
1926
3893
|
}
|
|
1927
3894
|
});
|
|
3895
|
+
return { definition, observeLegacyExecution: observe };
|
|
3896
|
+
}
|
|
3897
|
+
async function handleReportRun(args, observe, tracker) {
|
|
3898
|
+
const parsed = ReportRunPayloadSchema.safeParse({
|
|
3899
|
+
runID: args.runID,
|
|
3900
|
+
status: args.status,
|
|
3901
|
+
fixes: args.fixes,
|
|
3902
|
+
nodes: args.nodes,
|
|
3903
|
+
...args.parentSessionRef !== undefined ? { parentSessionRef: args.parentSessionRef } : {}
|
|
3904
|
+
});
|
|
3905
|
+
if (!parsed.success) {
|
|
3906
|
+
const issues = parsed.error.issues.map((issue) => issue.message).join("; ");
|
|
3907
|
+
return formatResponse({
|
|
3908
|
+
result: "ineligible",
|
|
3909
|
+
detail: `validation: ${issues}`
|
|
3910
|
+
});
|
|
3911
|
+
}
|
|
3912
|
+
const verification = crossVerify(parsed.data, tracker);
|
|
3913
|
+
const warnings = formatWarnings(verification);
|
|
3914
|
+
const verificationPair = {
|
|
3915
|
+
nodesChecked: parsed.data.nodes.length,
|
|
3916
|
+
unverified: verification.unverifiedModels.length,
|
|
3917
|
+
mismatched: verification.modelMismatches.length
|
|
3918
|
+
};
|
|
3919
|
+
const run = buildObservedRun(parsed.data);
|
|
3920
|
+
try {
|
|
3921
|
+
const result = await observe(run, verificationPair);
|
|
3922
|
+
const base = { result: result.kind };
|
|
3923
|
+
const detail = result.kind === "ineligible" ? result.reason : result.kind === "observation-rejected" ? result.reason : result.kind === "contained-failure" ? result.detail : null;
|
|
3924
|
+
return formatResponse({
|
|
3925
|
+
...base,
|
|
3926
|
+
...detail !== null ? { detail } : {},
|
|
3927
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
3928
|
+
});
|
|
3929
|
+
} catch (error) {
|
|
3930
|
+
return formatResponse({
|
|
3931
|
+
result: "contained-failure",
|
|
3932
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
3933
|
+
});
|
|
3934
|
+
}
|
|
1928
3935
|
}
|
|
1929
3936
|
|
|
1930
3937
|
// src/capabilities/budget.ts
|
|
@@ -2359,22 +4366,22 @@ function toCostRecord(decision2, context) {
|
|
|
2359
4366
|
}
|
|
2360
4367
|
|
|
2361
4368
|
// src/telemetry/types.ts
|
|
2362
|
-
import { z as
|
|
2363
|
-
var CostRecordSchema =
|
|
2364
|
-
ts:
|
|
2365
|
-
sessionID:
|
|
2366
|
-
promptHash:
|
|
2367
|
-
promptChars:
|
|
4369
|
+
import { z as z10 } from "zod";
|
|
4370
|
+
var CostRecordSchema = z10.object({
|
|
4371
|
+
ts: z10.number().finite(),
|
|
4372
|
+
sessionID: z10.string().min(1).optional(),
|
|
4373
|
+
promptHash: z10.string().min(1),
|
|
4374
|
+
promptChars: z10.number().int().min(0),
|
|
2368
4375
|
tier: ComplexityTierSchema,
|
|
2369
|
-
routeKind:
|
|
4376
|
+
routeKind: z10.enum(["local", "frontier"]),
|
|
2370
4377
|
selected: ModelRefSchema,
|
|
2371
|
-
rationale:
|
|
2372
|
-
estimatedCostUSD:
|
|
2373
|
-
baselineCostUSD:
|
|
2374
|
-
estimatedSavingsUSD:
|
|
2375
|
-
budgetAction:
|
|
2376
|
-
tokensIn:
|
|
2377
|
-
tokensOut:
|
|
4378
|
+
rationale: z10.string(),
|
|
4379
|
+
estimatedCostUSD: z10.number().finite().min(0),
|
|
4380
|
+
baselineCostUSD: z10.number().finite().min(0),
|
|
4381
|
+
estimatedSavingsUSD: z10.number().finite(),
|
|
4382
|
+
budgetAction: z10.string().min(1),
|
|
4383
|
+
tokensIn: z10.number().int().min(0).optional(),
|
|
4384
|
+
tokensOut: z10.number().int().min(0).optional()
|
|
2378
4385
|
});
|
|
2379
4386
|
|
|
2380
4387
|
// src/telemetry/read.ts
|
|
@@ -2621,6 +4628,12 @@ function createChatMessageHook(config, getAvailable, options = {}) {
|
|
|
2621
4628
|
telemetryContext.tokensOut = signals.estimatedOutputTokens;
|
|
2622
4629
|
}
|
|
2623
4630
|
emitTelemetry(sink, costRecordToRouteEvent(toCostRecord(decision2, telemetryContext)));
|
|
4631
|
+
if (options.onRoute !== undefined) {
|
|
4632
|
+
const routedSessionID = input.sessionID ?? output.message.sessionID;
|
|
4633
|
+
if (typeof routedSessionID === "string" && routedSessionID.length > 0) {
|
|
4634
|
+
options.onRoute(routedSessionID, `${decision2.selected.providerID}/${decision2.selected.modelID}`);
|
|
4635
|
+
}
|
|
4636
|
+
}
|
|
2624
4637
|
if (options.injectMemory !== undefined) {
|
|
2625
4638
|
try {
|
|
2626
4639
|
const block = await options.injectMemory(prompt);
|
|
@@ -2682,54 +4695,54 @@ ${lines.join(`
|
|
|
2682
4695
|
}
|
|
2683
4696
|
|
|
2684
4697
|
// src/memory/types.ts
|
|
2685
|
-
import { z as
|
|
4698
|
+
import { z as z11 } from "zod";
|
|
2686
4699
|
var SHARED_OWNER_KEY = "*";
|
|
2687
|
-
var OwnerKeySchema =
|
|
2688
|
-
var MemoryKindSchema =
|
|
2689
|
-
var MemoryBaseSchema =
|
|
2690
|
-
id:
|
|
4700
|
+
var OwnerKeySchema = z11.string().min(1);
|
|
4701
|
+
var MemoryKindSchema = z11.enum(["fact", "preference", "entity"]);
|
|
4702
|
+
var MemoryBaseSchema = z11.object({
|
|
4703
|
+
id: z11.string().min(1),
|
|
2691
4704
|
ownerKey: OwnerKeySchema,
|
|
2692
|
-
confidence:
|
|
2693
|
-
validFrom:
|
|
2694
|
-
validUntil:
|
|
2695
|
-
createdAt:
|
|
2696
|
-
invalidatedAt:
|
|
2697
|
-
sourceHash:
|
|
2698
|
-
supersededBy:
|
|
4705
|
+
confidence: z11.number().min(0).max(1),
|
|
4706
|
+
validFrom: z11.number().finite(),
|
|
4707
|
+
validUntil: z11.number().finite().nullable().default(null),
|
|
4708
|
+
createdAt: z11.number().finite(),
|
|
4709
|
+
invalidatedAt: z11.number().finite().nullable().default(null),
|
|
4710
|
+
sourceHash: z11.string().min(1),
|
|
4711
|
+
supersededBy: z11.string().min(1).nullable().default(null)
|
|
2699
4712
|
});
|
|
2700
4713
|
var FactSchema = MemoryBaseSchema.extend({
|
|
2701
|
-
kind:
|
|
2702
|
-
subject:
|
|
2703
|
-
predicate:
|
|
2704
|
-
object:
|
|
2705
|
-
category:
|
|
4714
|
+
kind: z11.literal("fact"),
|
|
4715
|
+
subject: z11.string().min(1),
|
|
4716
|
+
predicate: z11.string().min(1),
|
|
4717
|
+
object: z11.string().min(1),
|
|
4718
|
+
category: z11.string().min(1).nullable().default(null)
|
|
2706
4719
|
});
|
|
2707
4720
|
var PreferenceSchema = MemoryBaseSchema.extend({
|
|
2708
|
-
kind:
|
|
2709
|
-
category:
|
|
2710
|
-
preference:
|
|
2711
|
-
context:
|
|
2712
|
-
lastAccessedAt:
|
|
2713
|
-
accessCount:
|
|
4721
|
+
kind: z11.literal("preference"),
|
|
4722
|
+
category: z11.string().min(1),
|
|
4723
|
+
preference: z11.string().min(1),
|
|
4724
|
+
context: z11.string().min(1).nullable().default(null),
|
|
4725
|
+
lastAccessedAt: z11.number().finite().nullable().default(null),
|
|
4726
|
+
accessCount: z11.number().int().min(0).default(0)
|
|
2714
4727
|
});
|
|
2715
4728
|
var EntitySchema = MemoryBaseSchema.extend({
|
|
2716
|
-
kind:
|
|
2717
|
-
canonicalName:
|
|
2718
|
-
type:
|
|
2719
|
-
aliases:
|
|
4729
|
+
kind: z11.literal("entity"),
|
|
4730
|
+
canonicalName: z11.string().min(1),
|
|
4731
|
+
type: z11.string().min(1),
|
|
4732
|
+
aliases: z11.array(z11.string().min(1)).default([])
|
|
2720
4733
|
});
|
|
2721
|
-
var MemoryRecordSchema =
|
|
4734
|
+
var MemoryRecordSchema = z11.discriminatedUnion("kind", [
|
|
2722
4735
|
FactSchema,
|
|
2723
4736
|
PreferenceSchema,
|
|
2724
4737
|
EntitySchema
|
|
2725
4738
|
]);
|
|
2726
|
-
var RecallQuerySchema =
|
|
4739
|
+
var RecallQuerySchema = z11.object({
|
|
2727
4740
|
ownerKey: OwnerKeySchema,
|
|
2728
|
-
kinds:
|
|
2729
|
-
asOf:
|
|
2730
|
-
limit:
|
|
2731
|
-
minSimilarity:
|
|
2732
|
-
includeShared:
|
|
4741
|
+
kinds: z11.array(MemoryKindSchema).default(["fact", "preference", "entity"]),
|
|
4742
|
+
asOf: z11.number().finite().nullable().default(null),
|
|
4743
|
+
limit: z11.number().int().positive().default(8),
|
|
4744
|
+
minSimilarity: z11.number().min(0).max(1).default(0.2),
|
|
4745
|
+
includeShared: z11.boolean().default(true)
|
|
2733
4746
|
});
|
|
2734
4747
|
|
|
2735
4748
|
// src/memory/rank.ts
|
|
@@ -2878,6 +4891,33 @@ function buildMemoryInjector(deps, policy) {
|
|
|
2878
4891
|
};
|
|
2879
4892
|
}
|
|
2880
4893
|
|
|
4894
|
+
// src/plugin/sessionTracker.ts
|
|
4895
|
+
function createSessionTracker() {
|
|
4896
|
+
const knownSessions = new Set;
|
|
4897
|
+
const routedModels = new Map;
|
|
4898
|
+
return {
|
|
4899
|
+
trackSession(sessionID) {
|
|
4900
|
+
knownSessions.add(sessionID);
|
|
4901
|
+
},
|
|
4902
|
+
trackRoutedModel(sessionID, model) {
|
|
4903
|
+
routedModels.set(sessionID, model);
|
|
4904
|
+
},
|
|
4905
|
+
verifyModel(sessionRef, reportedModel) {
|
|
4906
|
+
const routedModel = routedModels.get(sessionRef);
|
|
4907
|
+
if (routedModel === undefined) {
|
|
4908
|
+
return { verified: false };
|
|
4909
|
+
}
|
|
4910
|
+
return { verified: true, match: routedModel === reportedModel };
|
|
4911
|
+
},
|
|
4912
|
+
verifySessionRef(sessionRef) {
|
|
4913
|
+
if (sessionRef === undefined) {
|
|
4914
|
+
return { known: null };
|
|
4915
|
+
}
|
|
4916
|
+
return { known: knownSessions.has(sessionRef) };
|
|
4917
|
+
}
|
|
4918
|
+
};
|
|
4919
|
+
}
|
|
4920
|
+
|
|
2881
4921
|
// src/plugin/toolcalls.ts
|
|
2882
4922
|
function key(sessionID, callID) {
|
|
2883
4923
|
return `${sessionID}\x00${callID}`;
|
|
@@ -3337,6 +5377,82 @@ function logTelemetryError(error) {
|
|
|
3337
5377
|
const message = error instanceof Error ? error.message : String(error);
|
|
3338
5378
|
console.warn(`[openteam] telemetry write failed: ${message}`);
|
|
3339
5379
|
}
|
|
5380
|
+
var SHADOW_CERT_PATH = "artifacts/graph-shadow-certificate.json";
|
|
5381
|
+
var RELEASE_CERT_PATH = "artifacts/graph-release-certificate.json";
|
|
5382
|
+
var SOAK_CERT_PATH = "artifacts/graph-soak-certificate.json";
|
|
5383
|
+
var SOAK_EVIDENCE_DIR = ".opencode/soak";
|
|
5384
|
+
async function readAndParseCertificate(storage, path, parse, digestExtractor) {
|
|
5385
|
+
try {
|
|
5386
|
+
const content = await storage.read(path);
|
|
5387
|
+
if (content === undefined) {
|
|
5388
|
+
return { status: "absent" };
|
|
5389
|
+
}
|
|
5390
|
+
const raw = JSON.parse(content);
|
|
5391
|
+
const cert = parse(raw);
|
|
5392
|
+
return { status: "valid", digest: digestExtractor(cert) };
|
|
5393
|
+
} catch (error) {
|
|
5394
|
+
if (isMissingFile(error)) {
|
|
5395
|
+
return { status: "absent" };
|
|
5396
|
+
}
|
|
5397
|
+
if (error instanceof ShadowCertificateError || error instanceof ReleaseCertificateError || error instanceof SoakCertificateError) {
|
|
5398
|
+
return { status: "invalid", code: error.code, detail: error.detail };
|
|
5399
|
+
}
|
|
5400
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
5401
|
+
return { status: "invalid", code: "read-error", detail: msg };
|
|
5402
|
+
}
|
|
5403
|
+
}
|
|
5404
|
+
async function readOpencodeVersion(serverUrl) {
|
|
5405
|
+
try {
|
|
5406
|
+
const url = new URL("/global/health", serverUrl);
|
|
5407
|
+
const res = await fetch(url);
|
|
5408
|
+
if (!res.ok) {
|
|
5409
|
+
return "unknown";
|
|
5410
|
+
}
|
|
5411
|
+
const body = await res.json();
|
|
5412
|
+
if (typeof body === "object" && body !== null && "version" in body && typeof body.version === "string" && body.version.length > 0) {
|
|
5413
|
+
return body.version;
|
|
5414
|
+
}
|
|
5415
|
+
return "unknown";
|
|
5416
|
+
} catch {
|
|
5417
|
+
return "unknown";
|
|
5418
|
+
}
|
|
5419
|
+
}
|
|
5420
|
+
async function buildGateInput(config, storage, opencodeVersion) {
|
|
5421
|
+
const shadow = await readAndParseCertificate(storage, SHADOW_CERT_PATH, (raw) => parseShadowCertificate(raw, opencodeVersion), (cert) => cert.digest);
|
|
5422
|
+
const shadowDigest = shadow.status === "valid" ? shadow.digest : "0".repeat(64);
|
|
5423
|
+
const release = await readAndParseCertificate(storage, RELEASE_CERT_PATH, (raw) => parseReleaseCertificate(raw, opencodeVersion, shadowDigest), (cert) => cert.digest);
|
|
5424
|
+
let soakLedgerDigest = "0".repeat(64);
|
|
5425
|
+
try {
|
|
5426
|
+
const recorderPorts = recorderPortsFromStorage(storage);
|
|
5427
|
+
const chains = await readAllChains(recorderPorts, SOAK_EVIDENCE_DIR);
|
|
5428
|
+
if (chains.length > 0) {
|
|
5429
|
+
soakLedgerDigest = canonicalLedger(chains);
|
|
5430
|
+
}
|
|
5431
|
+
} catch {}
|
|
5432
|
+
const soak = await readAndParseCertificate(storage, SOAK_CERT_PATH, (raw) => parseSoakCertificate(raw, DEFAULT_SOAK_POLICY, soakLedgerDigest), (cert) => cert.digest);
|
|
5433
|
+
const migration = "complete";
|
|
5434
|
+
const operatorApproval = config.graph?.operatorApproval ?? false;
|
|
5435
|
+
return { shadow, release, soak, migration, operatorApproval };
|
|
5436
|
+
}
|
|
5437
|
+
function recorderPortsFromStorage(storage) {
|
|
5438
|
+
return {
|
|
5439
|
+
fs: {
|
|
5440
|
+
readFile: async (path) => {
|
|
5441
|
+
const content = await storage.read(path);
|
|
5442
|
+
if (content === undefined) {
|
|
5443
|
+
throw Object.assign(new Error(`ENOENT: ${path}`), { code: "ENOENT" });
|
|
5444
|
+
}
|
|
5445
|
+
return content;
|
|
5446
|
+
},
|
|
5447
|
+
writeFile: (path, content) => storage.write(path, content),
|
|
5448
|
+
exists: (path) => storage.exists(path),
|
|
5449
|
+
readDir: (path) => storage.list(path),
|
|
5450
|
+
mkdir: (path) => storage.mkdir(path)
|
|
5451
|
+
},
|
|
5452
|
+
clock: { now: () => new Date().toISOString() },
|
|
5453
|
+
id: { randomUUID: () => crypto.randomUUID() }
|
|
5454
|
+
};
|
|
5455
|
+
}
|
|
3340
5456
|
async function buildMemoryInjectorFromConfig(config, storage) {
|
|
3341
5457
|
const runtime = await buildMemoryRuntimeFromConfig(config, { storage }, { requireInjectionEnabled: true });
|
|
3342
5458
|
if (runtime === undefined) {
|
|
@@ -3436,7 +5552,15 @@ var server = async (ctx, rawOptions) => {
|
|
|
3436
5552
|
const storage = createFsStorageProvider(process.cwd());
|
|
3437
5553
|
const sink = createEventSink(rawOptions, storage);
|
|
3438
5554
|
const injectMemory = await buildMemoryInjectorFromConfig(config, storage);
|
|
3439
|
-
const
|
|
5555
|
+
const sessionTracker = createSessionTracker();
|
|
5556
|
+
const hooks = createHooks(config, cache.get, injectMemory === undefined ? {
|
|
5557
|
+
sink,
|
|
5558
|
+
onRoute: (sid, model) => sessionTracker.trackRoutedModel(sid, model)
|
|
5559
|
+
} : {
|
|
5560
|
+
sink,
|
|
5561
|
+
injectMemory,
|
|
5562
|
+
onRoute: (sid, model) => sessionTracker.trackRoutedModel(sid, model)
|
|
5563
|
+
});
|
|
3440
5564
|
const cliDeps = createCliDeps(config, registry, telemetryPath, sessionsDir, storage);
|
|
3441
5565
|
const toolcalls = createToolcallTracker({ sink });
|
|
3442
5566
|
const now = () => Date.now();
|
|
@@ -3454,10 +5578,54 @@ var server = async (ctx, rawOptions) => {
|
|
|
3454
5578
|
sink.emit(endpoint);
|
|
3455
5579
|
}
|
|
3456
5580
|
};
|
|
5581
|
+
let graphConfig = resolveGraphConfig(config, "file");
|
|
5582
|
+
const opencodeVersion = graphConfig.effectiveMode !== "off" ? await readOpencodeVersion(ctx.serverUrl) : undefined;
|
|
5583
|
+
if (isGateApplicable(graphConfig.effectiveMode)) {
|
|
5584
|
+
if (opencodeVersion === undefined) {
|
|
5585
|
+
throw new Error("[openteam] BUG: gate applicable but opencodeVersion was not read");
|
|
5586
|
+
}
|
|
5587
|
+
const gateInput = await buildGateInput(config, storage, opencodeVersion);
|
|
5588
|
+
const gateResult = evaluateGraphGate(gateInput);
|
|
5589
|
+
graphConfig = applyGraphGate(graphConfig, gateResult);
|
|
5590
|
+
if (graphConfig.gateDenied === true && graphConfig.gateViolations !== undefined) {
|
|
5591
|
+
console.warn("[openteam] ⛔ cutover gate denied active mode — degrading to off");
|
|
5592
|
+
for (const v of graphConfig.gateViolations) {
|
|
5593
|
+
console.warn(`[openteam] • ${v.code}: ${v.detail}`);
|
|
5594
|
+
}
|
|
5595
|
+
}
|
|
5596
|
+
}
|
|
5597
|
+
let shadowDeps;
|
|
5598
|
+
if (graphConfig.effectiveMode !== "off") {
|
|
5599
|
+
if (opencodeVersion === undefined) {
|
|
5600
|
+
throw new Error("[openteam] BUG: shadow pipeline active but opencodeVersion was not read");
|
|
5601
|
+
}
|
|
5602
|
+
const recorderPorts = recorderPortsFromStorage(storage);
|
|
5603
|
+
const recorderId = await ensureRecorderId(recorderPorts, ".opencode");
|
|
5604
|
+
shadowDeps = {
|
|
5605
|
+
gate: {
|
|
5606
|
+
mode: graphConfig.configuredMode,
|
|
5607
|
+
killSwitch: graphConfig.killSwitch
|
|
5608
|
+
},
|
|
5609
|
+
ports: recorderPorts,
|
|
5610
|
+
evidenceDir: ".opencode/soak",
|
|
5611
|
+
recorderId,
|
|
5612
|
+
opencodeVersion,
|
|
5613
|
+
criticalCodes: DEFAULT_SOAK_POLICY.criticalDivergenceCodes
|
|
5614
|
+
};
|
|
5615
|
+
}
|
|
5616
|
+
const graphSurface = createGraphTool({
|
|
5617
|
+
policy: {
|
|
5618
|
+
graph: graphConfig,
|
|
5619
|
+
allowList: []
|
|
5620
|
+
},
|
|
5621
|
+
shadow: shadowDeps,
|
|
5622
|
+
tracker: sessionTracker
|
|
5623
|
+
});
|
|
3457
5624
|
return {
|
|
3458
5625
|
...hooks,
|
|
3459
5626
|
tool: {
|
|
3460
|
-
openteam: createCommandTool(cliDeps)
|
|
5627
|
+
openteam: createCommandTool(cliDeps),
|
|
5628
|
+
"openteam-graph": graphSurface.definition
|
|
3461
5629
|
},
|
|
3462
5630
|
"tool.execute.before": async (input) => {
|
|
3463
5631
|
toolcalls.before(input);
|
|
@@ -3472,6 +5640,7 @@ var server = async (ctx, rawOptions) => {
|
|
|
3472
5640
|
const activity = activityEventFrom(event, now);
|
|
3473
5641
|
if (activity !== undefined) {
|
|
3474
5642
|
announceEndpoint(activity.sessionID);
|
|
5643
|
+
sessionTracker.trackSession(activity.sessionID);
|
|
3475
5644
|
sink.emit(activity);
|
|
3476
5645
|
}
|
|
3477
5646
|
if (event.type === "message.updated") {
|
|
@@ -3479,6 +5648,7 @@ var server = async (ctx, rawOptions) => {
|
|
|
3479
5648
|
const message = info === undefined ? undefined : messageEventFrom(info, now);
|
|
3480
5649
|
if (message !== undefined) {
|
|
3481
5650
|
announceEndpoint(message.sessionID);
|
|
5651
|
+
sessionTracker.trackSession(message.sessionID);
|
|
3482
5652
|
sink.emit(message);
|
|
3483
5653
|
}
|
|
3484
5654
|
}
|
|
@@ -3492,10 +5662,13 @@ var plugin = {
|
|
|
3492
5662
|
var src_default = plugin;
|
|
3493
5663
|
export {
|
|
3494
5664
|
server,
|
|
5665
|
+
recorderPortsFromStorage,
|
|
5666
|
+
readOpencodeVersion,
|
|
3495
5667
|
logTelemetryError,
|
|
3496
5668
|
logAvailabilityRefreshError,
|
|
3497
5669
|
isMissingFile,
|
|
3498
5670
|
src_default as default,
|
|
3499
5671
|
createEventSink,
|
|
3500
|
-
createCliDeps
|
|
5672
|
+
createCliDeps,
|
|
5673
|
+
buildGateInput
|
|
3501
5674
|
};
|