@iowarp/clio-coder 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +407 -0
- package/CODE_OF_CONDUCT.md +21 -0
- package/CONTRIBUTING.md +224 -0
- package/LICENSE +202 -0
- package/NOTICE +9 -0
- package/README.md +798 -0
- package/SECURITY.md +72 -0
- package/assets/clio-coder-logo-128.webp +0 -0
- package/damage-control-rules.yaml +419 -0
- package/dist/acp-UMLFVA3F.js +92 -0
- package/dist/agents-Q4MYPMUW.js +91 -0
- package/dist/auth-O6HYIJ6J.js +521 -0
- package/dist/chunk-262G75JS.js +35 -0
- package/dist/chunk-26BZQOAD.js +1281 -0
- package/dist/chunk-2J63S4SF.js +508 -0
- package/dist/chunk-3DANZDGR.js +717 -0
- package/dist/chunk-4UQA7NCT.js +29 -0
- package/dist/chunk-527KG6XR.js +497 -0
- package/dist/chunk-5LDRNKX2.js +1063 -0
- package/dist/chunk-5N2FG33Q.js +25 -0
- package/dist/chunk-67MTHP2E.js +135 -0
- package/dist/chunk-6CWDTGUC.js +20 -0
- package/dist/chunk-7BHLZB3A.js +2115 -0
- package/dist/chunk-7RBKDI66.js +348 -0
- package/dist/chunk-AMFR5YA3.js +541 -0
- package/dist/chunk-BBUH4VAA.js +1224 -0
- package/dist/chunk-BYEU76JP.js +899 -0
- package/dist/chunk-CLJ5HLUD.js +458 -0
- package/dist/chunk-D5YD55AR.js +116 -0
- package/dist/chunk-DXQNI4PC.js +61 -0
- package/dist/chunk-E3NYWENM.js +1004 -0
- package/dist/chunk-GNGDQYDU.js +34688 -0
- package/dist/chunk-GOTUR54M.js +9 -0
- package/dist/chunk-HBU5MTAM.js +41 -0
- package/dist/chunk-HMYNFFY4.js +28 -0
- package/dist/chunk-JPOWPFCU.js +1010 -0
- package/dist/chunk-JWHCJDCI.js +1215 -0
- package/dist/chunk-KBR4MZZR.js +41 -0
- package/dist/chunk-KKKPTZLM.js +93 -0
- package/dist/chunk-ME6DNWIU.js +66 -0
- package/dist/chunk-NI4DEJMC.js +88 -0
- package/dist/chunk-O4EJEDHO.js +659 -0
- package/dist/chunk-PIDUD6M2.js +31 -0
- package/dist/chunk-PS4PFJQP.js +29459 -0
- package/dist/chunk-QV47YRF4.js +48 -0
- package/dist/chunk-RQDWMVRB.js +279 -0
- package/dist/chunk-TFSSEXL6.js +136 -0
- package/dist/chunk-TKHQ4DGZ.js +8290 -0
- package/dist/chunk-TPOCL34A.js +2876 -0
- package/dist/chunk-UGYAX5YI.js +565 -0
- package/dist/chunk-UHTSULZS.js +461 -0
- package/dist/chunk-UU3R62TT.js +128 -0
- package/dist/chunk-UWIJNAOB.js +3906 -0
- package/dist/chunk-VOO7NYPP.js +914 -0
- package/dist/chunk-VPAWTYLY.js +117 -0
- package/dist/chunk-WD6AJM35.js +1216 -0
- package/dist/chunk-X3BR7HWV.js +115 -0
- package/dist/chunk-X3NE4WVW.js +120 -0
- package/dist/chunk-XNISANGE.js +1395 -0
- package/dist/chunk-XV4ZJ6ZM.js +3177 -0
- package/dist/cli/index.js +236 -0
- package/dist/clio-KIQ5SNDS.js +53 -0
- package/dist/components-JVHMUBEB.js +653 -0
- package/dist/config-ZFCDBMDC.js +372 -0
- package/dist/configure-G4E3A2PG.js +27 -0
- package/dist/context-CDXTP2MP.js +293 -0
- package/dist/context-E3KIFVXI.js +185 -0
- package/dist/context-clear-3F4PLXOS.js +102 -0
- package/dist/context-index-Q7YSYTR3.js +106 -0
- package/dist/docs-YIETIWZI.js +280 -0
- package/dist/doctor-M5HJJZOL.js +61 -0
- package/dist/domains/agents/builtins/architect.md +33 -0
- package/dist/domains/agents/builtins/coder.md +31 -0
- package/dist/domains/agents/builtins/context-bootstrap.md +38 -0
- package/dist/domains/agents/builtins/debugger.md +30 -0
- package/dist/domains/agents/builtins/documenter.md +31 -0
- package/dist/domains/agents/builtins/git-master.md +30 -0
- package/dist/domains/agents/builtins/provenance.md +30 -0
- package/dist/domains/agents/builtins/researcher.md +71 -0
- package/dist/domains/agents/builtins/scout.md +42 -0
- package/dist/domains/agents/builtins/tester.md +31 -0
- package/dist/domains/agents/builtins/verifier.md +30 -0
- package/dist/domains/agents/builtins/wiki-writer.md +41 -0
- package/dist/eval-B3KZZESM.js +2674 -0
- package/dist/evidence-V67CHM35.js +233 -0
- package/dist/evolve-YDZSUQYA.js +518 -0
- package/dist/extensions-SRG7XCAH.js +207 -0
- package/dist/fleet-CA2CRTVG.js +760 -0
- package/dist/fleet-preflight-CLIAX7YR.js +21 -0
- package/dist/init-2OZDJE2D.js +227 -0
- package/dist/memory-3PIQQAKX.js +207 -0
- package/dist/models-DY35XI7Y.js +237 -0
- package/dist/paths-5OMXW7Z4.js +57 -0
- package/dist/preload-KZVHET2B.js +11 -0
- package/dist/reset-PIFYNOS3.js +216 -0
- package/dist/run-3VSPP24F.js +735 -0
- package/dist/share-D36RQCXM.js +241 -0
- package/dist/skills-F2MRLELY.js +445 -0
- package/dist/skills-eval-E2ZTW4PL.js +932 -0
- package/dist/targets-DZMEZAH4.js +977 -0
- package/dist/trace-7NYCUI2J.js +250 -0
- package/dist/uninstall-AD3JWHBB.js +322 -0
- package/dist/upgrade-WYYBKGDY.js +301 -0
- package/dist/usage-ULIDAGFF.js +755 -0
- package/dist/version-ROZ6CZKH.js +16 -0
- package/dist/wiki-generate-PKFIX6OB.js +377 -0
- package/dist/worker/entry.js +1739 -0
- package/docs/README.md +93 -0
- package/docs/acp.md +120 -0
- package/docs/alcf-provider.md +72 -0
- package/docs/architecture.md +172 -0
- package/docs/artifact-versions.md +54 -0
- package/docs/built-in-agents.md +265 -0
- package/docs/capacity-and-scheduling.md +97 -0
- package/docs/commands-and-modes.md +554 -0
- package/docs/config-knobs-audit.md +115 -0
- package/docs/configuration-and-targets.md +812 -0
- package/docs/context-engine.md +236 -0
- package/docs/dispatch-architecture-rationale.md +126 -0
- package/docs/documentation-coverage.md +46 -0
- package/docs/documentation-guide.md +166 -0
- package/docs/environment-variables.md +105 -0
- package/docs/eval-runner.md +205 -0
- package/docs/evals-internal.md +298 -0
- package/docs/evidence-and-memory.md +243 -0
- package/docs/evolution.md +143 -0
- package/docs/exit-codes-and-output.md +74 -0
- package/docs/extensions-and-sharing.md +306 -0
- package/docs/fleet-demo-runbook.md +179 -0
- package/docs/fleet-dispatch.md +591 -0
- package/docs/glossary.md +75 -0
- package/docs/html/agents_blueprint.html +936 -0
- package/docs/html/alcf_blueprint.html +324 -0
- package/docs/html/architecture_blueprint.html +850 -0
- package/docs/html/commands_blueprint.html +794 -0
- package/docs/html/config_knobs_audit_blueprint.html +178 -0
- package/docs/html/configuration_blueprint.html +1080 -0
- package/docs/html/context_blueprint.html +603 -0
- package/docs/html/documentation_blueprint.html +832 -0
- package/docs/html/environment_blueprint.html +404 -0
- package/docs/html/eval_blueprint.html +743 -0
- package/docs/html/evals_internal_blueprint.html +190 -0
- package/docs/html/evolution_blueprint.html +674 -0
- package/docs/html/extensions_blueprint.html +2065 -0
- package/docs/html/fleet_dispatch_blueprint.html +286 -0
- package/docs/html/index.html +919 -0
- package/docs/html/lifecycle_blueprint.html +723 -0
- package/docs/html/memory_blueprint.html +699 -0
- package/docs/html/middleware_blueprint.html +664 -0
- package/docs/html/models_blueprint.html +2366 -0
- package/docs/html/observability_blueprint.html +683 -0
- package/docs/html/provider_adapter_blueprint.html +245 -0
- package/docs/html/safety_blueprint.html +1386 -0
- package/docs/html/shared.css +571 -0
- package/docs/html/shared.js +143 -0
- package/docs/html/skills_blueprint.html +671 -0
- package/docs/html/soak_blueprint.html +182 -0
- package/docs/html/tool_usage_blueprint.html +350 -0
- package/docs/html/tools_blueprint.html +2249 -0
- package/docs/html/trace_blueprint.html +235 -0
- package/docs/html/tui_design_blueprint.html +314 -0
- package/docs/html/validation_blueprint.html +961 -0
- package/docs/html/worker_dispatch_blueprint.html +231 -0
- package/docs/installation-and-lifecycle.md +308 -0
- package/docs/middleware-and-components.md +148 -0
- package/docs/model-catalog.md +189 -0
- package/docs/observability.md +233 -0
- package/docs/proactive-memory.md +452 -0
- package/docs/prompt-envelope-and-tools.md +142 -0
- package/docs/provider-adapter-cookbook.md +148 -0
- package/docs/release-cut-checklist.md +138 -0
- package/docs/safety-model.md +357 -0
- package/docs/scientific-validation.md +105 -0
- package/docs/session-lifecycle.md +156 -0
- package/docs/skills-marketplace.md +46 -0
- package/docs/tool-usage.md +527 -0
- package/docs/trace-store.md +132 -0
- package/docs/troubleshooting.md +33 -0
- package/docs/tui-design.md +239 -0
- package/docs/worker-dispatch-mechanics.md +242 -0
- package/package.json +132 -0
- package/skills/README.md +408 -0
- package/skills/git/commit-crafting/SKILL.md +79 -0
- package/skills/git/commit-crafting/evals.md +92 -0
- package/skills/git/create-pr/SKILL.md +116 -0
- package/skills/git/create-pr/evals.md +114 -0
- package/skills/git/investigate-issue/SKILL.md +139 -0
- package/skills/git/investigate-issue/evals.md +94 -0
- package/skills/git/resolve-merge-conflicts/SKILL.md +96 -0
- package/skills/git/resolve-merge-conflicts/evals.md +58 -0
- package/skills/git/review-changes/SKILL.md +103 -0
- package/skills/git/review-changes/evals.md +85 -0
- package/skills/git/worktree-create/SKILL.md +92 -0
- package/skills/git/worktree-create/evals.md +97 -0
- package/skills/git/worktree-create/references/worktree-setup.md +66 -0
- package/skills/git/worktree-merge/SKILL.md +95 -0
- package/skills/git/worktree-merge/evals.md +114 -0
- package/skills/skill-marketplace.json +261 -0
- package/skills/workflow/cut-it/SKILL.md +86 -0
- package/skills/workflow/cut-it/evals.md +42 -0
- package/src/domains/agents/builtins/architect.md +33 -0
- package/src/domains/agents/builtins/coder.md +31 -0
- package/src/domains/agents/builtins/context-bootstrap.md +38 -0
- package/src/domains/agents/builtins/debugger.md +30 -0
- package/src/domains/agents/builtins/documenter.md +31 -0
- package/src/domains/agents/builtins/git-master.md +30 -0
- package/src/domains/agents/builtins/provenance.md +30 -0
- package/src/domains/agents/builtins/researcher.md +71 -0
- package/src/domains/agents/builtins/scout.md +42 -0
- package/src/domains/agents/builtins/tester.md +31 -0
- package/src/domains/agents/builtins/verifier.md +30 -0
- package/src/domains/agents/builtins/wiki-writer.md +41 -0
- package/src/domains/agents/fleets/build-review.md +34 -0
- package/src/domains/agents/fleets/build-test.md +35 -0
- package/src/domains/agents/fleets/sdlc.md +86 -0
- package/src/domains/prompts/fragments/identity/clio-worker.md +11 -0
- package/src/domains/prompts/fragments/identity/clio.md +26 -0
- package/src/domains/prompts/fragments/operating/contract.md +64 -0
- package/src/domains/prompts/fragments/safety/auto-edit.md +14 -0
- package/src/domains/prompts/fragments/safety/full-auto.md +14 -0
- package/src/domains/prompts/fragments/safety/read-only.md +13 -0
- package/src/domains/prompts/fragments/safety/suggest.md +13 -0
- package/src/domains/prompts/fragments/wiki/page.md +75 -0
- package/src/domains/prompts/fragments/wiki/plan.md +48 -0
- package/src/domains/providers/models/cloud-models/alcf.yaml +40 -0
- package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +993 -0
|
@@ -0,0 +1,2115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isAgentCapabilityClass
|
|
3
|
+
} from "./chunk-WD6AJM35.js";
|
|
4
|
+
import {
|
|
5
|
+
assertSafeId
|
|
6
|
+
} from "./chunk-5N2FG33Q.js";
|
|
7
|
+
import {
|
|
8
|
+
safeResourceWrite
|
|
9
|
+
} from "./chunk-VPAWTYLY.js";
|
|
10
|
+
import {
|
|
11
|
+
readClioVersion,
|
|
12
|
+
resolvePackageRoot
|
|
13
|
+
} from "./chunk-DXQNI4PC.js";
|
|
14
|
+
|
|
15
|
+
// src/domains/dispatch/execution-role.ts
|
|
16
|
+
var EXECUTION_ROLES = [
|
|
17
|
+
"builder",
|
|
18
|
+
"reviewer",
|
|
19
|
+
"judge",
|
|
20
|
+
"researcher",
|
|
21
|
+
"verifier",
|
|
22
|
+
"recovery"
|
|
23
|
+
];
|
|
24
|
+
function isExecutionRole(value) {
|
|
25
|
+
return typeof value === "string" && EXECUTION_ROLES.includes(value);
|
|
26
|
+
}
|
|
27
|
+
function deriveExecutionRole(input) {
|
|
28
|
+
if (input.attempt > 0) return "recovery";
|
|
29
|
+
if (input.gateRole === "reviewer") return "reviewer";
|
|
30
|
+
if (input.gateRole === "judge") return "judge";
|
|
31
|
+
if (input.gateRole === "builder" || input.gateRole === "candidate") return "builder";
|
|
32
|
+
if (input.resultContractKind === "verifier-report") return "verifier";
|
|
33
|
+
if (input.capabilityClass === "read-only") return "researcher";
|
|
34
|
+
return "builder";
|
|
35
|
+
}
|
|
36
|
+
function agentRoleFactsResolver(getSpec) {
|
|
37
|
+
return (agentId) => {
|
|
38
|
+
const spec = getSpec(agentId);
|
|
39
|
+
return spec === null ? null : { capabilityClass: spec.capabilityClass, resultContractKind: spec.resultContract.kind };
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function requestExecutionRole(input) {
|
|
43
|
+
const facts = input.resolveFacts?.(input.agentId) ?? null;
|
|
44
|
+
return deriveExecutionRole({
|
|
45
|
+
attempt: 0,
|
|
46
|
+
...input.gateRole !== void 0 ? { gateRole: input.gateRole } : {},
|
|
47
|
+
capabilityClass: facts?.capabilityClass ?? "workspace-edit",
|
|
48
|
+
resultContractKind: facts?.resultContractKind ?? null
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function withAttemptRole(requestRole, attempt) {
|
|
52
|
+
return attempt > 0 ? "recovery" : requestRole;
|
|
53
|
+
}
|
|
54
|
+
var DEFAULT_GATE_DECIDER_AGENT_ID = "verifier";
|
|
55
|
+
function gateDeciderAgentId(requested) {
|
|
56
|
+
const trimmed = requested?.trim() ?? "";
|
|
57
|
+
return trimmed.length > 0 ? trimmed : DEFAULT_GATE_DECIDER_AGENT_ID;
|
|
58
|
+
}
|
|
59
|
+
function appliesRecipeResultContract(gateRole) {
|
|
60
|
+
return gateRole !== "reviewer" && gateRole !== "judge";
|
|
61
|
+
}
|
|
62
|
+
function routeCorrelationFactsForRun(run) {
|
|
63
|
+
return {
|
|
64
|
+
agentId: run.agentId,
|
|
65
|
+
targetId: run.targetId,
|
|
66
|
+
wireModelId: run.wireModelId,
|
|
67
|
+
runtimeId: run.runtimeId,
|
|
68
|
+
nodeId: run.node?.id ?? "local"
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function modelFamily(model) {
|
|
72
|
+
return model.trim().toLowerCase().replace(/[-_:]?\d{4}[-_]?\d{2}[-_]?\d{2}$/u, "");
|
|
73
|
+
}
|
|
74
|
+
function gateRouteCorrelation(subject, decider) {
|
|
75
|
+
const agent = subject.agentId === decider.agentId;
|
|
76
|
+
const target = subject.targetId === decider.targetId;
|
|
77
|
+
const sameModelFamily = modelFamily(subject.wireModelId) === modelFamily(decider.wireModelId);
|
|
78
|
+
const runtime = subject.runtimeId === decider.runtimeId;
|
|
79
|
+
const node = subject.nodeId === decider.nodeId;
|
|
80
|
+
const dimensions = [
|
|
81
|
+
...agent ? ["agent"] : [],
|
|
82
|
+
...target ? ["target"] : [],
|
|
83
|
+
...sameModelFamily ? ["model-family"] : [],
|
|
84
|
+
...runtime ? ["runtime"] : [],
|
|
85
|
+
...node ? ["node"] : []
|
|
86
|
+
].sort();
|
|
87
|
+
return {
|
|
88
|
+
agent,
|
|
89
|
+
target,
|
|
90
|
+
modelFamily: sameModelFamily,
|
|
91
|
+
runtime,
|
|
92
|
+
node,
|
|
93
|
+
independent: !agent && !sameModelFamily,
|
|
94
|
+
dimensions
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/domains/dispatch/receipt-integrity.ts
|
|
99
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
100
|
+
|
|
101
|
+
// src/domains/dispatch/route-decision.ts
|
|
102
|
+
import { createHash } from "node:crypto";
|
|
103
|
+
|
|
104
|
+
// src/domains/dispatch/route-policy.ts
|
|
105
|
+
var ROUTE_POLICY_VERSION = "route-policy/4";
|
|
106
|
+
var MINIMUM_QUALITY_LABELED_OUTCOMES = 6;
|
|
107
|
+
var ROUTE_POSTURES = {
|
|
108
|
+
manual: {
|
|
109
|
+
floors: { qualityLowerBound: 0, reliability: 0 },
|
|
110
|
+
weights: { quality: 1, cost: 0, latency: 0 },
|
|
111
|
+
deadlineFirst: false
|
|
112
|
+
},
|
|
113
|
+
quality: {
|
|
114
|
+
floors: { qualityLowerBound: 0.8, reliability: 0.8 },
|
|
115
|
+
weights: { quality: 1, cost: 0.1, latency: 0.1 },
|
|
116
|
+
deadlineFirst: false
|
|
117
|
+
},
|
|
118
|
+
balanced: {
|
|
119
|
+
floors: { qualityLowerBound: 0.6, reliability: 0.6 },
|
|
120
|
+
weights: { quality: 0.6, cost: 0.2, latency: 0.2 },
|
|
121
|
+
deadlineFirst: false
|
|
122
|
+
},
|
|
123
|
+
latency: {
|
|
124
|
+
floors: { qualityLowerBound: 0.5, reliability: 0.6 },
|
|
125
|
+
weights: { quality: 0.3, cost: 0.1, latency: 0.6 },
|
|
126
|
+
deadlineFirst: true
|
|
127
|
+
},
|
|
128
|
+
economy: {
|
|
129
|
+
floors: { qualityLowerBound: 0.6, reliability: 0.6 },
|
|
130
|
+
weights: { quality: 0.3, cost: 0.6, latency: 0.1 },
|
|
131
|
+
deadlineFirst: false
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
var DEFAULT_ROUTE_PRIOR = {
|
|
135
|
+
qualityMean: 0.5,
|
|
136
|
+
firstPassSuccessProbability: 0.5,
|
|
137
|
+
costUpperBoundUsd: 1,
|
|
138
|
+
expectedEndToEndMs: 12e4,
|
|
139
|
+
reliability: 0.5,
|
|
140
|
+
queueWaitMs: 0
|
|
141
|
+
};
|
|
142
|
+
var LATENCY_CLASS_ROUTE_PRIORS = {
|
|
143
|
+
fast: { ...DEFAULT_ROUTE_PRIOR, expectedEndToEndMs: 3e4 },
|
|
144
|
+
balanced: { ...DEFAULT_ROUTE_PRIOR, expectedEndToEndMs: 12e4 },
|
|
145
|
+
deep: { ...DEFAULT_ROUTE_PRIOR, expectedEndToEndMs: 3e5 }
|
|
146
|
+
};
|
|
147
|
+
function routePriorForLatencyClass(latencyClass) {
|
|
148
|
+
return { ...LATENCY_CLASS_ROUTE_PRIORS[latencyClass] };
|
|
149
|
+
}
|
|
150
|
+
function routeObservationFromHistory(record2) {
|
|
151
|
+
return {
|
|
152
|
+
qualityLabel: record2.qualityLabel,
|
|
153
|
+
reliability: record2.reliability,
|
|
154
|
+
firstPass: record2.firstPass,
|
|
155
|
+
completedCostUsd: record2.completedCostUsd,
|
|
156
|
+
completedEndToEndMs: record2.completedPhaseTiming?.totalEndToEndMs ?? null,
|
|
157
|
+
cacheRead: record2.cacheRead,
|
|
158
|
+
queueWaitMs: record2.completedPhaseTiming?.queueWaitMs ?? null
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
var ROUTE_ESTIMATE_SHRINKAGE = 5;
|
|
162
|
+
function mean(values, fallback) {
|
|
163
|
+
if (values.length === 0) return fallback;
|
|
164
|
+
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
165
|
+
}
|
|
166
|
+
function quantile(values, fraction, fallback) {
|
|
167
|
+
if (values.length === 0) return fallback;
|
|
168
|
+
const sorted = [...values].sort((left, right) => left - right);
|
|
169
|
+
const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil(fraction * sorted.length) - 1));
|
|
170
|
+
return sorted[index] ?? fallback;
|
|
171
|
+
}
|
|
172
|
+
function shrink(measured, prior, sampleCount) {
|
|
173
|
+
const weight = sampleCount / (sampleCount + ROUTE_ESTIMATE_SHRINKAGE);
|
|
174
|
+
return measured * weight + prior * (1 - weight);
|
|
175
|
+
}
|
|
176
|
+
function rate(values, fallback = 0) {
|
|
177
|
+
if (values.length === 0) return fallback;
|
|
178
|
+
return values.filter(Boolean).length / values.length;
|
|
179
|
+
}
|
|
180
|
+
function wilsonLowerBound(successes, observations, z = 1.96) {
|
|
181
|
+
if (observations <= 0) return 0;
|
|
182
|
+
const proportion = successes / observations;
|
|
183
|
+
const z2 = z * z;
|
|
184
|
+
const center = proportion + z2 / (2 * observations);
|
|
185
|
+
const margin = z * Math.sqrt((proportion * (1 - proportion) + z2 / (4 * observations)) / observations);
|
|
186
|
+
return Math.max(0, Math.min(1, (center - margin) / (1 + z2 / observations)));
|
|
187
|
+
}
|
|
188
|
+
function estimateRoute(samples, prior = DEFAULT_ROUTE_PRIOR) {
|
|
189
|
+
const labeled = samples.filter((sample) => sample.qualityLabel !== "unmeasured");
|
|
190
|
+
const qualityPasses = labeled.filter((sample) => sample.qualityLabel === "pass").length;
|
|
191
|
+
const attributable = samples.filter((sample) => sample.reliability !== "neutral");
|
|
192
|
+
const completed = samples.filter((sample) => sample.completedCostUsd !== null && sample.completedEndToEndMs !== null);
|
|
193
|
+
const costs = completed.flatMap((sample) => sample.completedCostUsd === null ? [] : [sample.completedCostUsd]);
|
|
194
|
+
const latencies = completed.flatMap(
|
|
195
|
+
(sample) => sample.completedEndToEndMs === null ? [] : [sample.completedEndToEndMs]
|
|
196
|
+
);
|
|
197
|
+
const queueWaits = completed.flatMap((sample) => sample.queueWaitMs === null ? [] : [sample.queueWaitMs]);
|
|
198
|
+
const completedCount = completed.length;
|
|
199
|
+
const reliabilityCount = attributable.length;
|
|
200
|
+
const qualityMean = labeled.length === 0 ? prior.qualityMean : qualityPasses / labeled.length;
|
|
201
|
+
const completedCosts = mean(costs, prior.costUpperBoundUsd);
|
|
202
|
+
const completedLatency = mean(latencies, prior.expectedEndToEndMs);
|
|
203
|
+
return {
|
|
204
|
+
qualityLabeledCount: labeled.length,
|
|
205
|
+
unmeasuredCount: samples.length - labeled.length,
|
|
206
|
+
qualityCoverage: samples.length === 0 ? 0 : labeled.length / samples.length,
|
|
207
|
+
qualityMean,
|
|
208
|
+
qualityLowerBound: wilsonLowerBound(qualityPasses, labeled.length),
|
|
209
|
+
firstPassSuccessProbability: shrink(
|
|
210
|
+
rate(
|
|
211
|
+
attributable.map((sample) => sample.firstPass),
|
|
212
|
+
prior.firstPassSuccessProbability
|
|
213
|
+
),
|
|
214
|
+
prior.firstPassSuccessProbability,
|
|
215
|
+
reliabilityCount
|
|
216
|
+
),
|
|
217
|
+
expectedCostUsd: shrink(completedCosts, prior.costUpperBoundUsd, completedCount),
|
|
218
|
+
costUpperBoundUsd: Math.max(prior.costUpperBoundUsd, quantile(costs, 0.95, prior.costUpperBoundUsd)),
|
|
219
|
+
expectedEndToEndMs: shrink(completedLatency, prior.expectedEndToEndMs, completedCount),
|
|
220
|
+
p95EndToEndMs: Math.max(completedLatency, quantile(latencies, 0.95, prior.expectedEndToEndMs)),
|
|
221
|
+
reliability: shrink(
|
|
222
|
+
rate(
|
|
223
|
+
attributable.map((sample) => sample.reliability === "success"),
|
|
224
|
+
prior.reliability
|
|
225
|
+
),
|
|
226
|
+
prior.reliability,
|
|
227
|
+
reliabilityCount
|
|
228
|
+
),
|
|
229
|
+
cacheHitProbability: completedCount === 0 ? 0 : rate(completed.map((sample) => sample.cacheRead)),
|
|
230
|
+
queueWaitMs: shrink(mean(queueWaits, prior.queueWaitMs), prior.queueWaitMs, completedCount),
|
|
231
|
+
sampleCount: samples.length,
|
|
232
|
+
confidence: labeled.length / (labeled.length + ROUTE_ESTIMATE_SHRINKAGE)
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function clearsPostureFloors(estimate, posture) {
|
|
236
|
+
if (posture === "manual") return true;
|
|
237
|
+
const policy = ROUTE_POSTURES[posture];
|
|
238
|
+
return estimate.qualityLabeledCount >= MINIMUM_QUALITY_LABELED_OUTCOMES && estimate.qualityLowerBound >= policy.floors.qualityLowerBound && estimate.reliability >= policy.floors.reliability;
|
|
239
|
+
}
|
|
240
|
+
function normalize(value, min, max) {
|
|
241
|
+
if (!(max > min)) return 0;
|
|
242
|
+
return (value - min) / (max - min);
|
|
243
|
+
}
|
|
244
|
+
function routeScoreScale(estimates) {
|
|
245
|
+
const costs = estimates.map((estimate) => estimate.expectedCostUsd);
|
|
246
|
+
const latencies = estimates.map((estimate) => estimate.p95EndToEndMs);
|
|
247
|
+
const queueWaits = estimates.map((estimate) => estimate.queueWaitMs);
|
|
248
|
+
return {
|
|
249
|
+
minCostUsd: costs.length > 0 ? Math.min(...costs) : 0,
|
|
250
|
+
maxCostUsd: costs.length > 0 ? Math.max(...costs) : 0,
|
|
251
|
+
minLatencyMs: latencies.length > 0 ? Math.min(...latencies) : 0,
|
|
252
|
+
maxLatencyMs: latencies.length > 0 ? Math.max(...latencies) : 0,
|
|
253
|
+
minQueueWaitMs: queueWaits.length > 0 ? Math.min(...queueWaits) : 0,
|
|
254
|
+
maxQueueWaitMs: queueWaits.length > 0 ? Math.max(...queueWaits) : 0
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
var SCORE_PRECISION = 1e-9;
|
|
258
|
+
function round(value) {
|
|
259
|
+
return Math.round(value / SCORE_PRECISION) * SCORE_PRECISION;
|
|
260
|
+
}
|
|
261
|
+
function scoreRoute(estimate, posture, scale) {
|
|
262
|
+
const policy = ROUTE_POSTURES[posture];
|
|
263
|
+
const costPenalty = normalize(estimate.expectedCostUsd, scale.minCostUsd, scale.maxCostUsd);
|
|
264
|
+
const latencyPenalty = normalize(
|
|
265
|
+
policy.deadlineFirst ? estimate.p95EndToEndMs : estimate.expectedEndToEndMs,
|
|
266
|
+
scale.minLatencyMs,
|
|
267
|
+
scale.maxLatencyMs
|
|
268
|
+
);
|
|
269
|
+
const queuePenalty = normalize(estimate.queueWaitMs, scale.minQueueWaitMs, scale.maxQueueWaitMs);
|
|
270
|
+
const qualitySignal = estimate.qualityLabeledCount === 0 ? estimate.qualityMean : estimate.qualityLowerBound;
|
|
271
|
+
const health = qualitySignal * 0.7 + estimate.reliability * 0.3;
|
|
272
|
+
const affinityBonus = estimate.cacheHitProbability * 0.01;
|
|
273
|
+
return round(
|
|
274
|
+
policy.weights.quality * health - policy.weights.cost * costPenalty - policy.weights.latency * (latencyPenalty * 0.9 + queuePenalty * 0.1) + affinityBonus
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
function dominatesRoute(left, right) {
|
|
278
|
+
const noWorse = left.qualityLowerBound >= right.qualityLowerBound && left.reliability >= right.reliability && left.expectedCostUsd <= right.expectedCostUsd && left.p95EndToEndMs <= right.p95EndToEndMs && left.queueWaitMs <= right.queueWaitMs && left.cacheHitProbability >= right.cacheHitProbability;
|
|
279
|
+
return noWorse && (left.qualityLowerBound > right.qualityLowerBound || left.reliability > right.reliability || left.expectedCostUsd < right.expectedCostUsd || left.p95EndToEndMs < right.p95EndToEndMs || left.queueWaitMs < right.queueWaitMs || left.cacheHitProbability > right.cacheHitProbability);
|
|
280
|
+
}
|
|
281
|
+
function compareRankedRoutes(left, right) {
|
|
282
|
+
if (left.score !== right.score) return right.score - left.score;
|
|
283
|
+
if (left.estimate.expectedCostUsd !== right.estimate.expectedCostUsd) {
|
|
284
|
+
return left.estimate.expectedCostUsd - right.estimate.expectedCostUsd;
|
|
285
|
+
}
|
|
286
|
+
if (left.estimate.reliability !== right.estimate.reliability) {
|
|
287
|
+
return right.estimate.reliability - left.estimate.reliability;
|
|
288
|
+
}
|
|
289
|
+
if (left.estimate.p95EndToEndMs !== right.estimate.p95EndToEndMs) {
|
|
290
|
+
return left.estimate.p95EndToEndMs - right.estimate.p95EndToEndMs;
|
|
291
|
+
}
|
|
292
|
+
if (left.estimate.queueWaitMs !== right.estimate.queueWaitMs) {
|
|
293
|
+
return left.estimate.queueWaitMs - right.estimate.queueWaitMs;
|
|
294
|
+
}
|
|
295
|
+
if (left.estimate.cacheHitProbability !== right.estimate.cacheHitProbability) {
|
|
296
|
+
return right.estimate.cacheHitProbability - left.estimate.cacheHitProbability;
|
|
297
|
+
}
|
|
298
|
+
if (left.order !== right.order) return left.order - right.order;
|
|
299
|
+
return left.key < right.key ? -1 : left.key > right.key ? 1 : 0;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// src/domains/dispatch/route-readiness.ts
|
|
303
|
+
var MINIMUM_ACTIVE_QUALITY_LABELS = 6;
|
|
304
|
+
var DECISION_LATENCY_BUDGET_MS = 10;
|
|
305
|
+
function evaluateRouteReadiness(input) {
|
|
306
|
+
const gaps = [];
|
|
307
|
+
const floors = ROUTE_POSTURES[input.posture].floors;
|
|
308
|
+
const estimate = input.estimate;
|
|
309
|
+
if (input.hardConstraintValidity < 1) gaps.push("hard-constraint-validity-below-one");
|
|
310
|
+
if (input.integrityFailures > 0) gaps.push("integrity-failures-in-window");
|
|
311
|
+
if (estimate.qualityLabeledCount < MINIMUM_ACTIVE_QUALITY_LABELS) gaps.push("insufficient-quality-labels");
|
|
312
|
+
if (estimate.qualityLowerBound < floors.qualityLowerBound) gaps.push("quality-lower-bound-below-posture-floor");
|
|
313
|
+
if (input.requestedMinimumQuality !== null && estimate.qualityLowerBound < input.requestedMinimumQuality) {
|
|
314
|
+
gaps.push("quality-lower-bound-below-requested-minimum");
|
|
315
|
+
}
|
|
316
|
+
if (estimate.reliability < floors.reliability) gaps.push("reliability-below-posture-floor");
|
|
317
|
+
if (input.costUpperBoundUsd === null || !Number.isFinite(input.costUpperBoundUsd)) {
|
|
318
|
+
gaps.push("cost-upper-bound-unknown");
|
|
319
|
+
}
|
|
320
|
+
if (!input.factsFresh) gaps.push("route-facts-stale");
|
|
321
|
+
if (input.decisionP95Ms > DECISION_LATENCY_BUDGET_MS) gaps.push("decision-latency-above-budget");
|
|
322
|
+
return {
|
|
323
|
+
ready: gaps.length === 0,
|
|
324
|
+
gaps,
|
|
325
|
+
labelsNeeded: Math.max(0, MINIMUM_ACTIVE_QUALITY_LABELS - estimate.qualityLabeledCount)
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// src/domains/dispatch/route-decision.ts
|
|
330
|
+
function routeCorrelationFacts(candidate) {
|
|
331
|
+
return {
|
|
332
|
+
agentId: candidate.agentId,
|
|
333
|
+
targetId: candidate.targetId,
|
|
334
|
+
wireModelId: candidate.modelId,
|
|
335
|
+
runtimeId: candidate.runtimeId,
|
|
336
|
+
nodeId: candidate.nodeId
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
function exactKeys(value, keys) {
|
|
340
|
+
return Object.keys(value).sort().join("\0") === [...keys].sort().join("\0");
|
|
341
|
+
}
|
|
342
|
+
function decisionRecord(value) {
|
|
343
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
344
|
+
}
|
|
345
|
+
var READINESS_GAPS = /* @__PURE__ */ new Set([
|
|
346
|
+
"hard-constraint-validity-below-one",
|
|
347
|
+
"integrity-failures-in-window",
|
|
348
|
+
"insufficient-quality-labels",
|
|
349
|
+
"quality-lower-bound-below-posture-floor",
|
|
350
|
+
"quality-lower-bound-below-requested-minimum",
|
|
351
|
+
"reliability-below-posture-floor",
|
|
352
|
+
"cost-upper-bound-unknown",
|
|
353
|
+
"route-facts-stale",
|
|
354
|
+
"decision-latency-above-budget"
|
|
355
|
+
]);
|
|
356
|
+
function decisionRouteCandidate(value) {
|
|
357
|
+
if (!decisionRecord(value)) return false;
|
|
358
|
+
const required = [
|
|
359
|
+
"agentId",
|
|
360
|
+
"specFingerprint",
|
|
361
|
+
"executionRole",
|
|
362
|
+
"targetId",
|
|
363
|
+
"modelId",
|
|
364
|
+
"runtimeId",
|
|
365
|
+
"nodeId",
|
|
366
|
+
"toolSignature",
|
|
367
|
+
"promptCompositionHash",
|
|
368
|
+
"endpointIdentityHash",
|
|
369
|
+
"settingsFingerprint"
|
|
370
|
+
];
|
|
371
|
+
const keys = Object.keys(value);
|
|
372
|
+
if (keys.some((key) => !required.includes(key) && key !== "thinkingLevel")) return false;
|
|
373
|
+
if (required.some((key) => !Object.hasOwn(value, key))) return false;
|
|
374
|
+
return isExecutionRole(value.executionRole) && required.filter((key) => key !== "executionRole").every((key) => typeof value[key] === "string" && String(value[key]).length > 0) && (value.thinkingLevel === void 0 || typeof value.thinkingLevel === "string" && value.thinkingLevel.length > 0);
|
|
375
|
+
}
|
|
376
|
+
function decisionReadinessReport(value) {
|
|
377
|
+
if (!decisionRecord(value) || !exactKeys(value, ["ready", "gaps", "labelsNeeded"])) return false;
|
|
378
|
+
return typeof value.ready === "boolean" && Array.isArray(value.gaps) && value.gaps.length <= READINESS_GAPS.size && value.gaps.every((gap) => typeof gap === "string" && READINESS_GAPS.has(gap)) && new Set(value.gaps).size === value.gaps.length && value.ready === (value.gaps.length === 0) && Number.isInteger(value.labelsNeeded) && Number(value.labelsNeeded) >= 0;
|
|
379
|
+
}
|
|
380
|
+
function isRouteDecisionAgentSelection(value) {
|
|
381
|
+
if (!decisionRecord(value)) return false;
|
|
382
|
+
if (!exactKeys(value, [
|
|
383
|
+
"request",
|
|
384
|
+
"activation",
|
|
385
|
+
"baselineAgentId",
|
|
386
|
+
"recommendedAgentId",
|
|
387
|
+
"evaluations",
|
|
388
|
+
"readiness",
|
|
389
|
+
"authorityBasis",
|
|
390
|
+
"authorityTransition"
|
|
391
|
+
]) || value.request !== "explicit" && value.request !== "auto" || value.activation !== "shadow" && value.activation !== "active" || typeof value.baselineAgentId !== "string" || value.baselineAgentId.length === 0 || typeof value.recommendedAgentId !== "string" || value.recommendedAgentId.length === 0 || value.authorityBasis !== null && value.authorityBasis !== "operator-plan-approval" && value.authorityBasis !== "full-auto-policy" || !Array.isArray(value.evaluations) || value.evaluations.length === 0 || value.evaluations.length > 64 || !Array.isArray(value.readiness) || value.readiness.length === 0 || value.readiness.length > 64)
|
|
392
|
+
return false;
|
|
393
|
+
const priorKeys = [
|
|
394
|
+
"qualityMean",
|
|
395
|
+
"firstPassSuccessProbability",
|
|
396
|
+
"costUpperBoundUsd",
|
|
397
|
+
"expectedEndToEndMs",
|
|
398
|
+
"reliability",
|
|
399
|
+
"queueWaitMs"
|
|
400
|
+
];
|
|
401
|
+
if (!value.evaluations.every((entry) => {
|
|
402
|
+
if (!decisionRecord(entry) || !exactKeys(entry, [
|
|
403
|
+
"agentId",
|
|
404
|
+
"specFingerprint",
|
|
405
|
+
"executionRole",
|
|
406
|
+
"authority",
|
|
407
|
+
"rejections",
|
|
408
|
+
"coldPrior",
|
|
409
|
+
"priorReasons"
|
|
410
|
+
]))
|
|
411
|
+
return false;
|
|
412
|
+
const prior = entry.coldPrior;
|
|
413
|
+
if (typeof entry.agentId !== "string" || entry.agentId.length === 0 || typeof entry.specFingerprint !== "string" || entry.specFingerprint.length === 0 || !isExecutionRole(entry.executionRole) || !isAgentCapabilityClass(entry.authority) || !Array.isArray(entry.rejections) || entry.rejections.length > 64 || !entry.rejections.every((reason) => typeof reason === "string") || !Array.isArray(entry.priorReasons) || entry.priorReasons.length > 64 || !entry.priorReasons.every((reason) => typeof reason === "string") || !decisionRecord(prior) || !exactKeys(prior, priorKeys) || !priorKeys.every((key) => typeof prior[key] === "number" && Number.isFinite(prior[key])))
|
|
414
|
+
return false;
|
|
415
|
+
return true;
|
|
416
|
+
}))
|
|
417
|
+
return false;
|
|
418
|
+
if (!value.readiness.every((entry) => {
|
|
419
|
+
if (!decisionRecord(entry) || !exactKeys(entry, [
|
|
420
|
+
"agentId",
|
|
421
|
+
"specFingerprint",
|
|
422
|
+
"executionRole",
|
|
423
|
+
"ready",
|
|
424
|
+
"candidateCount",
|
|
425
|
+
"readyCandidateCount",
|
|
426
|
+
"routes"
|
|
427
|
+
]))
|
|
428
|
+
return false;
|
|
429
|
+
if (!Array.isArray(entry.routes) || entry.routes.length === 0 || entry.routes.length > 64 || !entry.routes.every(
|
|
430
|
+
(route) => decisionRecord(route) && exactKeys(route, ["candidate", "report"]) && decisionRouteCandidate(route.candidate) && decisionReadinessReport(route.report) && route.candidate.agentId === entry.agentId && route.candidate.specFingerprint === entry.specFingerprint && route.candidate.executionRole === entry.executionRole
|
|
431
|
+
))
|
|
432
|
+
return false;
|
|
433
|
+
const readyCandidateCount = entry.routes.filter(
|
|
434
|
+
(route) => decisionRecord(route) && decisionRecord(route.report) && route.report.ready === true
|
|
435
|
+
).length;
|
|
436
|
+
return typeof entry.agentId === "string" && entry.agentId.length > 0 && typeof entry.specFingerprint === "string" && entry.specFingerprint.length > 0 && isExecutionRole(entry.executionRole) && typeof entry.ready === "boolean" && Number.isInteger(entry.candidateCount) && Number(entry.candidateCount) === entry.routes.length && Number.isInteger(entry.readyCandidateCount) && Number(entry.readyCandidateCount) === readyCandidateCount && entry.ready === readyCandidateCount > 0;
|
|
437
|
+
}))
|
|
438
|
+
return false;
|
|
439
|
+
if (value.authorityTransition !== null) {
|
|
440
|
+
const transition = value.authorityTransition;
|
|
441
|
+
if (!decisionRecord(transition) || !exactKeys(transition, ["from", "to", "basis"]) || typeof transition.from !== "string" || transition.from.length === 0 || typeof transition.to !== "string" || transition.to.length === 0 || transition.basis !== "same-authority" && transition.basis !== "operator-plan-approval" && transition.basis !== "full-auto-policy") {
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
if (value.request !== "auto" || value.activation !== "active" || transition.from !== value.baselineAgentId || transition.to !== value.recommendedAgentId)
|
|
445
|
+
return false;
|
|
446
|
+
const from = value.evaluations.find((entry) => entry.agentId === transition.from);
|
|
447
|
+
const to = value.evaluations.find((entry) => entry.agentId === transition.to);
|
|
448
|
+
if (from === void 0 || to === void 0) return false;
|
|
449
|
+
if (transition.basis === "same-authority") {
|
|
450
|
+
if (from.authority !== to.authority) return false;
|
|
451
|
+
} else if (transition.basis !== value.authorityBasis) return false;
|
|
452
|
+
}
|
|
453
|
+
return (value.request !== "explicit" || value.authorityBasis === null) && new Set(
|
|
454
|
+
value.evaluations.map((entry) => `${entry.agentId}\0${entry.specFingerprint}\0${entry.executionRole}`)
|
|
455
|
+
).size === value.evaluations.length && value.evaluations.some((entry) => entry.agentId === value.baselineAgentId) && value.evaluations.some((entry) => entry.agentId === value.recommendedAgentId);
|
|
456
|
+
}
|
|
457
|
+
function routeCapabilityKey(candidate) {
|
|
458
|
+
return [
|
|
459
|
+
candidate.agentId,
|
|
460
|
+
candidate.specFingerprint,
|
|
461
|
+
candidate.executionRole,
|
|
462
|
+
candidate.targetId,
|
|
463
|
+
candidate.modelId,
|
|
464
|
+
candidate.runtimeId,
|
|
465
|
+
candidate.nodeId,
|
|
466
|
+
candidate.thinkingLevel ?? ""
|
|
467
|
+
].join("\0");
|
|
468
|
+
}
|
|
469
|
+
function routeDriftGuard(candidate) {
|
|
470
|
+
return {
|
|
471
|
+
promptCompositionHash: candidate.promptCompositionHash,
|
|
472
|
+
settingsFingerprint: candidate.settingsFingerprint,
|
|
473
|
+
toolSignature: candidate.toolSignature,
|
|
474
|
+
endpointIdentityHash: candidate.endpointIdentityHash
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
function routeDriftInvalidates(observed, current) {
|
|
478
|
+
return observed.toolSignature !== current.toolSignature || observed.endpointIdentityHash !== current.endpointIdentityHash;
|
|
479
|
+
}
|
|
480
|
+
function routeCandidateKey(candidate) {
|
|
481
|
+
return [
|
|
482
|
+
candidate.agentId,
|
|
483
|
+
candidate.specFingerprint,
|
|
484
|
+
candidate.executionRole,
|
|
485
|
+
candidate.targetId,
|
|
486
|
+
candidate.modelId,
|
|
487
|
+
candidate.runtimeId,
|
|
488
|
+
candidate.nodeId,
|
|
489
|
+
candidate.thinkingLevel ?? "",
|
|
490
|
+
candidate.toolSignature,
|
|
491
|
+
candidate.promptCompositionHash,
|
|
492
|
+
candidate.endpointIdentityHash,
|
|
493
|
+
candidate.settingsFingerprint
|
|
494
|
+
].join("\0");
|
|
495
|
+
}
|
|
496
|
+
function sameRouteIdentity(left, right) {
|
|
497
|
+
return routeCandidateKey(left) === routeCandidateKey(right);
|
|
498
|
+
}
|
|
499
|
+
function promptCompositionIdentity(identity, role) {
|
|
500
|
+
const persona = role.personaPrompt !== void 0 ? createHash("sha256").update(role.personaPrompt, "utf8").digest("hex") : null;
|
|
501
|
+
return createHash("sha256").update(
|
|
502
|
+
JSON.stringify({
|
|
503
|
+
specFingerprint: identity.specFingerprint,
|
|
504
|
+
toolSignature: identity.toolSignature,
|
|
505
|
+
persona
|
|
506
|
+
}),
|
|
507
|
+
"utf8"
|
|
508
|
+
).digest("hex");
|
|
509
|
+
}
|
|
510
|
+
function toRouteCandidate(identity, role) {
|
|
511
|
+
return {
|
|
512
|
+
agentId: identity.agentId,
|
|
513
|
+
specFingerprint: identity.specFingerprint,
|
|
514
|
+
executionRole: role.executionRole,
|
|
515
|
+
targetId: identity.targetId,
|
|
516
|
+
modelId: identity.wireModelId,
|
|
517
|
+
runtimeId: identity.runtimeId,
|
|
518
|
+
nodeId: identity.nodeId,
|
|
519
|
+
...identity.thinkingLevel !== null ? { thinkingLevel: identity.thinkingLevel } : {},
|
|
520
|
+
toolSignature: identity.toolSignature,
|
|
521
|
+
promptCompositionHash: promptCompositionIdentity(identity, role),
|
|
522
|
+
endpointIdentityHash: identity.endpointIdentityHash,
|
|
523
|
+
settingsFingerprint: identity.settingsFingerprint
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
function hashNumber(value) {
|
|
527
|
+
if (!Number.isFinite(value)) throw new Error(`route decision: non-finite number ${String(value)}`);
|
|
528
|
+
return Number(value.toFixed(9));
|
|
529
|
+
}
|
|
530
|
+
function estimateHashProjection(estimate) {
|
|
531
|
+
return [
|
|
532
|
+
hashNumber(estimate.qualityLabeledCount),
|
|
533
|
+
hashNumber(estimate.unmeasuredCount),
|
|
534
|
+
hashNumber(estimate.qualityCoverage),
|
|
535
|
+
hashNumber(estimate.qualityMean),
|
|
536
|
+
hashNumber(estimate.qualityLowerBound),
|
|
537
|
+
hashNumber(estimate.firstPassSuccessProbability),
|
|
538
|
+
hashNumber(estimate.expectedCostUsd),
|
|
539
|
+
hashNumber(estimate.costUpperBoundUsd),
|
|
540
|
+
hashNumber(estimate.expectedEndToEndMs),
|
|
541
|
+
hashNumber(estimate.p95EndToEndMs),
|
|
542
|
+
hashNumber(estimate.reliability),
|
|
543
|
+
hashNumber(estimate.cacheHitProbability),
|
|
544
|
+
hashNumber(estimate.queueWaitMs),
|
|
545
|
+
hashNumber(estimate.sampleCount),
|
|
546
|
+
hashNumber(estimate.confidence)
|
|
547
|
+
];
|
|
548
|
+
}
|
|
549
|
+
function readinessHashProjection(report) {
|
|
550
|
+
return [report.ready, report.gaps, hashNumber(report.labelsNeeded)];
|
|
551
|
+
}
|
|
552
|
+
function routeDecisionHash(input) {
|
|
553
|
+
const candidates = input.candidates.map((entry) => [
|
|
554
|
+
routeCandidateKey(entry.candidate),
|
|
555
|
+
estimateHashProjection(entry.estimate),
|
|
556
|
+
readinessHashProjection(entry.activeReadiness),
|
|
557
|
+
entry.rejection
|
|
558
|
+
]);
|
|
559
|
+
const agentSelection = [
|
|
560
|
+
input.agentSelection.request,
|
|
561
|
+
input.agentSelection.baselineAgentId,
|
|
562
|
+
input.agentSelection.evaluations.map((entry) => [
|
|
563
|
+
entry.agentId,
|
|
564
|
+
entry.specFingerprint,
|
|
565
|
+
entry.executionRole,
|
|
566
|
+
entry.authority,
|
|
567
|
+
entry.rejections,
|
|
568
|
+
[
|
|
569
|
+
hashNumber(entry.coldPrior.qualityMean),
|
|
570
|
+
hashNumber(entry.coldPrior.firstPassSuccessProbability),
|
|
571
|
+
hashNumber(entry.coldPrior.costUpperBoundUsd),
|
|
572
|
+
hashNumber(entry.coldPrior.expectedEndToEndMs),
|
|
573
|
+
hashNumber(entry.coldPrior.reliability),
|
|
574
|
+
hashNumber(entry.coldPrior.queueWaitMs)
|
|
575
|
+
],
|
|
576
|
+
entry.priorReasons
|
|
577
|
+
]),
|
|
578
|
+
input.agentSelection.readiness.map((entry) => [
|
|
579
|
+
entry.agentId,
|
|
580
|
+
entry.specFingerprint,
|
|
581
|
+
entry.executionRole,
|
|
582
|
+
entry.ready,
|
|
583
|
+
hashNumber(entry.candidateCount),
|
|
584
|
+
hashNumber(entry.readyCandidateCount),
|
|
585
|
+
entry.routes.map((route) => [routeCandidateKey(route.candidate), readinessHashProjection(route.report)])
|
|
586
|
+
]),
|
|
587
|
+
input.agentSelection.authorityBasis
|
|
588
|
+
];
|
|
589
|
+
const independenceSubject = input.independenceSubject === null ? null : [
|
|
590
|
+
input.independenceSubject.agentId,
|
|
591
|
+
input.independenceSubject.targetId,
|
|
592
|
+
input.independenceSubject.wireModelId,
|
|
593
|
+
input.independenceSubject.runtimeId,
|
|
594
|
+
input.independenceSubject.nodeId
|
|
595
|
+
];
|
|
596
|
+
return createHash("sha256").update(
|
|
597
|
+
JSON.stringify([
|
|
598
|
+
ROUTE_POLICY_VERSION,
|
|
599
|
+
input.mode,
|
|
600
|
+
input.posture,
|
|
601
|
+
routeCandidateKey(input.executedRoute),
|
|
602
|
+
candidates,
|
|
603
|
+
independenceSubject,
|
|
604
|
+
agentSelection,
|
|
605
|
+
input.hardConstraints,
|
|
606
|
+
hashNumber(input.maxFallbacks)
|
|
607
|
+
]),
|
|
608
|
+
"utf8"
|
|
609
|
+
).digest("hex");
|
|
610
|
+
}
|
|
611
|
+
function decideRoute(input) {
|
|
612
|
+
const reasonCodes = [];
|
|
613
|
+
const candidateKeys = new Map(input.candidates.map((entry) => [entry, routeCandidateKey(entry.candidate)]));
|
|
614
|
+
const candidateKey = (entry) => candidateKeys.get(entry);
|
|
615
|
+
const hardAdmissible = input.candidates.filter((entry) => entry.rejection === null);
|
|
616
|
+
const rejectedCount = input.candidates.length - hardAdmissible.length;
|
|
617
|
+
if (rejectedCount > 0) reasonCodes.push(`hard-filter-rejected-${rejectedCount}`);
|
|
618
|
+
const readinessExcluded = input.mode === "active" ? hardAdmissible.filter((entry) => !entry.activeReadiness.ready).length : 0;
|
|
619
|
+
if (readinessExcluded > 0) reasonCodes.push(`active-readiness-excluded-${readinessExcluded}`);
|
|
620
|
+
const admissible = input.mode === "active" ? hardAdmissible.filter((entry) => entry.activeReadiness.ready) : hardAdmissible;
|
|
621
|
+
const scale = routeScoreScale(admissible.map((entry) => entry.estimate));
|
|
622
|
+
const clearing = admissible.filter((entry) => clearsPostureFloors(entry.estimate, input.posture));
|
|
623
|
+
if (clearing.length < admissible.length) {
|
|
624
|
+
reasonCodes.push(`posture-floor-excluded-${admissible.length - clearing.length}`);
|
|
625
|
+
}
|
|
626
|
+
if (clearing.length === 0 && admissible.length > 0) {
|
|
627
|
+
reasonCodes.push("posture-floors-unsatisfiable");
|
|
628
|
+
if (input.mode === "active") throw new Error("route decision: posture-floors-unsatisfiable");
|
|
629
|
+
}
|
|
630
|
+
const selectable = clearing.length > 0 ? clearing : admissible;
|
|
631
|
+
const approvalOrder = new Map(input.candidates.map((entry, index) => [candidateKey(entry), index]));
|
|
632
|
+
const ranked = selectable.map((entry) => {
|
|
633
|
+
const key = candidateKey(entry);
|
|
634
|
+
return {
|
|
635
|
+
entry,
|
|
636
|
+
ranked: {
|
|
637
|
+
key,
|
|
638
|
+
order: approvalOrder.get(key) ?? input.candidates.length,
|
|
639
|
+
estimate: entry.estimate,
|
|
640
|
+
score: scoreRoute(entry.estimate, input.posture, scale)
|
|
641
|
+
}
|
|
642
|
+
};
|
|
643
|
+
}).sort((left, right) => {
|
|
644
|
+
const policyOrder = compareRankedRoutes(
|
|
645
|
+
{ ...left.ranked, key: "", order: 0 },
|
|
646
|
+
{ ...right.ranked, key: "", order: 0 }
|
|
647
|
+
);
|
|
648
|
+
if (policyOrder !== 0) return policyOrder;
|
|
649
|
+
if (input.independenceSubject !== null && (input.executedRoute.executionRole === "reviewer" || input.executedRoute.executionRole === "judge")) {
|
|
650
|
+
const leftIndependent = gateRouteCorrelation(
|
|
651
|
+
input.independenceSubject,
|
|
652
|
+
routeCorrelationFacts(left.entry.candidate)
|
|
653
|
+
).independent;
|
|
654
|
+
const rightIndependent = gateRouteCorrelation(
|
|
655
|
+
input.independenceSubject,
|
|
656
|
+
routeCorrelationFacts(right.entry.candidate)
|
|
657
|
+
).independent;
|
|
658
|
+
if (leftIndependent !== rightIndependent) return leftIndependent ? -1 : 1;
|
|
659
|
+
}
|
|
660
|
+
return compareRankedRoutes(left.ranked, right.ranked);
|
|
661
|
+
});
|
|
662
|
+
const dominatedKeys = /* @__PURE__ */ new Set();
|
|
663
|
+
for (const outer of admissible) {
|
|
664
|
+
for (const inner of admissible) {
|
|
665
|
+
if (inner === outer) continue;
|
|
666
|
+
if (dominatesRoute(inner.estimate, outer.estimate)) {
|
|
667
|
+
dominatedKeys.add(candidateKey(outer));
|
|
668
|
+
break;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
const scores = new Map(ranked.map((entry) => [entry.ranked.key, entry.ranked.score]));
|
|
673
|
+
const candidateEvaluations = input.candidates.map((entry) => {
|
|
674
|
+
const key = candidateKey(entry);
|
|
675
|
+
return {
|
|
676
|
+
candidate: { ...entry.candidate },
|
|
677
|
+
estimate: { ...entry.estimate },
|
|
678
|
+
activeReadiness: { ...entry.activeReadiness, gaps: [...entry.activeReadiness.gaps] },
|
|
679
|
+
rejection: entry.rejection,
|
|
680
|
+
score: entry.rejection === null ? scores.get(key) ?? null : null,
|
|
681
|
+
dominated: entry.rejection === null && dominatedKeys.has(key)
|
|
682
|
+
};
|
|
683
|
+
});
|
|
684
|
+
const executedKey = routeCandidateKey(input.executedRoute);
|
|
685
|
+
const executedAdmissible = admissible.some((entry) => candidateKey(entry) === executedKey);
|
|
686
|
+
const manual = input.posture === "manual";
|
|
687
|
+
const best = ranked[0]?.entry.candidate;
|
|
688
|
+
if (input.mode === "active" && best === void 0) {
|
|
689
|
+
const gaps = hardAdmissible.flatMap((entry) => entry.activeReadiness.gaps);
|
|
690
|
+
const detail = [...new Set(gaps)].join(", ") || "no-hard-admissible-candidate";
|
|
691
|
+
throw new Error(`route decision: no-active-eligible-candidate (${detail})`);
|
|
692
|
+
}
|
|
693
|
+
const selected = manual || best === void 0 ? { ...input.executedRoute } : { ...best };
|
|
694
|
+
if (manual) reasonCodes.push("manual-posture-exact-route");
|
|
695
|
+
if (best === void 0) reasonCodes.push("no-admissible-candidate");
|
|
696
|
+
if (!executedAdmissible) reasonCodes.push("executed-route-not-admissible");
|
|
697
|
+
if (input.mode === "shadow") reasonCodes.push("shadow-advisory-only");
|
|
698
|
+
if (input.mode !== "active" && best !== void 0 && routeCandidateKey(selected) !== executedKey) {
|
|
699
|
+
reasonCodes.push("shadow-differs-from-executed");
|
|
700
|
+
}
|
|
701
|
+
const selectedKey = routeCandidateKey(selected);
|
|
702
|
+
const approvedFallbacks = [];
|
|
703
|
+
if (!manual) {
|
|
704
|
+
for (const entry of ranked) {
|
|
705
|
+
if (approvedFallbacks.length >= Math.max(0, input.maxFallbacks)) break;
|
|
706
|
+
if (entry.ranked.key === selectedKey) continue;
|
|
707
|
+
approvedFallbacks.push({ ...entry.entry.candidate });
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
const selectedEstimate = candidateEvaluations.find(
|
|
711
|
+
(evaluation) => routeCandidateKey(evaluation.candidate) === selectedKey
|
|
712
|
+
)?.estimate;
|
|
713
|
+
const coldShare = admissible.length === 0 ? 1 : admissible.filter((entry) => entry.estimate.sampleCount === 0).length / admissible.length;
|
|
714
|
+
const confidence = Math.max(0, Math.min(1, (selectedEstimate?.confidence ?? 0) * (1 - coldShare) + coldShare * 0));
|
|
715
|
+
const recommendedAgentId = best?.agentId ?? input.agentSelection.baselineAgentId;
|
|
716
|
+
let authorityTransition = null;
|
|
717
|
+
if (input.mode === "active" && selected.agentId !== input.agentSelection.baselineAgentId) {
|
|
718
|
+
if (input.agentSelection.request !== "auto") {
|
|
719
|
+
throw new Error("route decision: explicit agent selection cannot change authority");
|
|
720
|
+
}
|
|
721
|
+
const from = input.agentSelection.evaluations.find(
|
|
722
|
+
(evaluation) => evaluation.agentId === input.agentSelection.baselineAgentId
|
|
723
|
+
)?.authority;
|
|
724
|
+
const to = input.agentSelection.evaluations.find((evaluation) => evaluation.agentId === selected.agentId)?.authority;
|
|
725
|
+
const basis = from !== void 0 && to !== void 0 && from === to ? "same-authority" : input.agentSelection.authorityBasis;
|
|
726
|
+
if (basis === null) throw new Error("route decision: active agent authority transition lacks trusted approval");
|
|
727
|
+
authorityTransition = { from: input.agentSelection.baselineAgentId, to: selected.agentId, basis };
|
|
728
|
+
}
|
|
729
|
+
return {
|
|
730
|
+
policyVersion: ROUTE_POLICY_VERSION,
|
|
731
|
+
mode: input.mode,
|
|
732
|
+
posture: input.posture,
|
|
733
|
+
selected,
|
|
734
|
+
approvedFallbacks,
|
|
735
|
+
hardConstraints: [...input.hardConstraints],
|
|
736
|
+
candidateEvaluations,
|
|
737
|
+
reasonCodes,
|
|
738
|
+
decisionDurationMs: input.decisionDurationMs,
|
|
739
|
+
confidence,
|
|
740
|
+
agentSelection: {
|
|
741
|
+
request: input.agentSelection.request,
|
|
742
|
+
activation: input.mode === "active" ? "active" : "shadow",
|
|
743
|
+
baselineAgentId: input.agentSelection.baselineAgentId,
|
|
744
|
+
recommendedAgentId,
|
|
745
|
+
evaluations: structuredClone([...input.agentSelection.evaluations]),
|
|
746
|
+
readiness: structuredClone([...input.agentSelection.readiness]),
|
|
747
|
+
authorityBasis: input.agentSelection.authorityBasis,
|
|
748
|
+
authorityTransition
|
|
749
|
+
},
|
|
750
|
+
decisionHash: routeDecisionHash({
|
|
751
|
+
...input,
|
|
752
|
+
executedRoute: input.mode === "active" ? selected : input.executedRoute
|
|
753
|
+
}),
|
|
754
|
+
executedRoute: { ...input.mode === "active" ? selected : input.executedRoute }
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
function fixedRouteDecision(executedRoute, reasonCode = "observer-failure-fixed-route") {
|
|
758
|
+
const estimate = estimateRoute([], DEFAULT_ROUTE_PRIOR);
|
|
759
|
+
const activeReadiness = evaluateRouteReadiness({
|
|
760
|
+
estimate,
|
|
761
|
+
posture: "manual",
|
|
762
|
+
hardConstraintValidity: 1,
|
|
763
|
+
integrityFailures: 0,
|
|
764
|
+
costUpperBoundUsd: null,
|
|
765
|
+
factsFresh: false,
|
|
766
|
+
decisionP95Ms: Number.POSITIVE_INFINITY,
|
|
767
|
+
requestedMinimumQuality: null
|
|
768
|
+
});
|
|
769
|
+
const input = {
|
|
770
|
+
mode: "fixed",
|
|
771
|
+
posture: "manual",
|
|
772
|
+
executedRoute,
|
|
773
|
+
candidates: [
|
|
774
|
+
{
|
|
775
|
+
candidate: executedRoute,
|
|
776
|
+
estimate,
|
|
777
|
+
activeReadiness,
|
|
778
|
+
rejection: null
|
|
779
|
+
}
|
|
780
|
+
],
|
|
781
|
+
independenceSubject: null,
|
|
782
|
+
hardConstraints: ["fixed-executed-route"],
|
|
783
|
+
maxFallbacks: 0,
|
|
784
|
+
decisionDurationMs: 0,
|
|
785
|
+
agentSelection: {
|
|
786
|
+
request: "explicit",
|
|
787
|
+
baselineAgentId: executedRoute.agentId,
|
|
788
|
+
evaluations: [
|
|
789
|
+
{
|
|
790
|
+
agentId: executedRoute.agentId,
|
|
791
|
+
specFingerprint: executedRoute.specFingerprint,
|
|
792
|
+
executionRole: executedRoute.executionRole,
|
|
793
|
+
authority: "internal",
|
|
794
|
+
rejections: [],
|
|
795
|
+
coldPrior: { ...DEFAULT_ROUTE_PRIOR },
|
|
796
|
+
priorReasons: ["fixed-route"]
|
|
797
|
+
}
|
|
798
|
+
],
|
|
799
|
+
readiness: [
|
|
800
|
+
{
|
|
801
|
+
agentId: executedRoute.agentId,
|
|
802
|
+
specFingerprint: executedRoute.specFingerprint,
|
|
803
|
+
executionRole: executedRoute.executionRole,
|
|
804
|
+
ready: activeReadiness.ready,
|
|
805
|
+
candidateCount: 1,
|
|
806
|
+
readyCandidateCount: activeReadiness.ready ? 1 : 0,
|
|
807
|
+
routes: [{ candidate: { ...executedRoute }, report: { ...activeReadiness, gaps: [...activeReadiness.gaps] } }]
|
|
808
|
+
}
|
|
809
|
+
],
|
|
810
|
+
authorityBasis: null
|
|
811
|
+
}
|
|
812
|
+
};
|
|
813
|
+
const decision = decideRoute(input);
|
|
814
|
+
return { ...decision, reasonCodes: [...decision.reasonCodes, reasonCode] };
|
|
815
|
+
}
|
|
816
|
+
function evaluationFor(decision, candidate) {
|
|
817
|
+
const key = routeCandidateKey(candidate);
|
|
818
|
+
return decision.candidateEvaluations.find((evaluation) => routeCandidateKey(evaluation.candidate) === key);
|
|
819
|
+
}
|
|
820
|
+
function routeRegret(decision) {
|
|
821
|
+
const selected = evaluationFor(decision, decision.selected);
|
|
822
|
+
const executed = evaluationFor(decision, decision.executedRoute);
|
|
823
|
+
return {
|
|
824
|
+
score: (selected?.score ?? 0) - (executed?.score ?? 0),
|
|
825
|
+
expectedCostUsd: (selected?.estimate.expectedCostUsd ?? 0) - (executed?.estimate.expectedCostUsd ?? 0),
|
|
826
|
+
expectedLatencyMs: (selected?.estimate.expectedEndToEndMs ?? 0) - (executed?.estimate.expectedEndToEndMs ?? 0),
|
|
827
|
+
executedOffFrontier: executed?.dominated ?? false,
|
|
828
|
+
routeDiffered: !sameRouteIdentity(decision.selected, decision.executedRoute)
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
function routeConstraintValidity(decision) {
|
|
832
|
+
const admissible = (candidate) => evaluationFor(decision, candidate)?.rejection === null;
|
|
833
|
+
const selectedAdmissible = admissible(decision.selected);
|
|
834
|
+
const executedAdmissible = admissible(decision.executedRoute);
|
|
835
|
+
const fallbacksAdmissible = decision.approvedFallbacks.every(admissible);
|
|
836
|
+
return {
|
|
837
|
+
selectedAdmissible,
|
|
838
|
+
executedAdmissible,
|
|
839
|
+
fallbacksAdmissible,
|
|
840
|
+
valid: selectedAdmissible && executedAdmissible && fallbacksAdmissible
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
function ratio(actual, expected) {
|
|
844
|
+
if (expected === 0) return actual === 0 ? 1 : Number.POSITIVE_INFINITY;
|
|
845
|
+
return actual / expected;
|
|
846
|
+
}
|
|
847
|
+
function routePredictionCalibration(decision, realized) {
|
|
848
|
+
const executed = evaluationFor(decision, realized.route);
|
|
849
|
+
const estimate = executed?.estimate;
|
|
850
|
+
const quality = realized.qualityLabel === "unmeasured" ? null : realized.qualityLabel === "pass" ? 1 : 0;
|
|
851
|
+
const firstPass = realized.firstPass ? 1 : 0;
|
|
852
|
+
return {
|
|
853
|
+
costErrorUsd: realized.costUsd - (estimate?.expectedCostUsd ?? 0),
|
|
854
|
+
costRatio: ratio(realized.costUsd, estimate?.expectedCostUsd ?? 0),
|
|
855
|
+
latencyErrorMs: realized.endToEndMs - (estimate?.expectedEndToEndMs ?? 0),
|
|
856
|
+
latencyRatio: ratio(realized.endToEndMs, estimate?.expectedEndToEndMs ?? 0),
|
|
857
|
+
qualityBrier: quality === null ? null : (quality - (estimate?.qualityMean ?? 0)) ** 2,
|
|
858
|
+
firstPassBrier: (firstPass - (estimate?.firstPassSuccessProbability ?? 0)) ** 2,
|
|
859
|
+
sampleCount: estimate?.sampleCount ?? 0
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
function routeOutcomeMetrics(realized) {
|
|
863
|
+
return {
|
|
864
|
+
outcome: realized.outcome,
|
|
865
|
+
qualityLabel: realized.qualityLabel,
|
|
866
|
+
firstPass: realized.firstPass,
|
|
867
|
+
escalated: realized.attempt > 0,
|
|
868
|
+
attempts: realized.attempt + 1,
|
|
869
|
+
costUsd: realized.costUsd,
|
|
870
|
+
endToEndMs: realized.endToEndMs
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
function evaluateRouteDecision(decision, realized) {
|
|
874
|
+
return {
|
|
875
|
+
regret: routeRegret(decision),
|
|
876
|
+
validity: routeConstraintValidity(decision),
|
|
877
|
+
calibration: routePredictionCalibration(decision, realized),
|
|
878
|
+
outcome: routeOutcomeMetrics(realized)
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// src/domains/dispatch/routing-intent.ts
|
|
883
|
+
var KEYS = /* @__PURE__ */ new Set([
|
|
884
|
+
"posture",
|
|
885
|
+
"maxCostUsd",
|
|
886
|
+
"deadlineMs",
|
|
887
|
+
"minimumQuality",
|
|
888
|
+
"requiredCapabilities",
|
|
889
|
+
"locality",
|
|
890
|
+
"failover"
|
|
891
|
+
]);
|
|
892
|
+
var POSTURES = /* @__PURE__ */ new Set(["manual", "quality", "balanced", "latency", "economy"]);
|
|
893
|
+
var LOCALITIES = /* @__PURE__ */ new Set(["local-only", "prefer-local", "any"]);
|
|
894
|
+
var FAILOVERS = /* @__PURE__ */ new Set(["none", "approved"]);
|
|
895
|
+
function record(value) {
|
|
896
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
897
|
+
}
|
|
898
|
+
function isRoutingIntent(value) {
|
|
899
|
+
if (!record(value) || Object.keys(value).some((key) => !KEYS.has(key))) return false;
|
|
900
|
+
return typeof value.posture === "string" && POSTURES.has(value.posture) && (value.maxCostUsd === null || typeof value.maxCostUsd === "number" && Number.isFinite(value.maxCostUsd) && value.maxCostUsd > 0) && (value.deadlineMs === null || typeof value.deadlineMs === "number" && Number.isInteger(value.deadlineMs) && value.deadlineMs > 0) && (value.minimumQuality === null || typeof value.minimumQuality === "number" && value.minimumQuality >= 0 && value.minimumQuality <= 1) && Array.isArray(value.requiredCapabilities) && value.requiredCapabilities.every((entry) => typeof entry === "string" && entry.length > 0) && typeof value.locality === "string" && LOCALITIES.has(value.locality) && typeof value.failover === "string" && FAILOVERS.has(value.failover);
|
|
901
|
+
}
|
|
902
|
+
function parseRoutingIntent(value, pins = {}) {
|
|
903
|
+
const errors = [];
|
|
904
|
+
if (value !== void 0 && !record(value)) return { ok: false, errors: ["routing must be an object"] };
|
|
905
|
+
const input = value === void 0 ? {} : value;
|
|
906
|
+
for (const key of Object.keys(input)) if (!KEYS.has(key)) errors.push(`routing unknown key: ${key}`);
|
|
907
|
+
const pinned = pins.target !== void 0 || pins.model !== void 0 || pins.node !== void 0;
|
|
908
|
+
const posture = input.posture ?? (pinned ? "manual" : "balanced");
|
|
909
|
+
if (typeof posture !== "string" || !POSTURES.has(posture)) {
|
|
910
|
+
errors.push("routing.posture must be one of: manual|quality|balanced|latency|economy");
|
|
911
|
+
}
|
|
912
|
+
if (pinned && posture !== "manual") errors.push("exact target, model, or node pins require routing.posture manual");
|
|
913
|
+
const positive = (key) => {
|
|
914
|
+
const candidate = input[key];
|
|
915
|
+
if (candidate === void 0) return null;
|
|
916
|
+
if (typeof candidate !== "number" || !Number.isFinite(candidate) || candidate <= 0) {
|
|
917
|
+
errors.push(`routing.${key} must be a finite number greater than zero`);
|
|
918
|
+
return null;
|
|
919
|
+
}
|
|
920
|
+
if (key === "deadlineMs" && !Number.isInteger(candidate)) errors.push("routing.deadlineMs must be an integer");
|
|
921
|
+
return candidate;
|
|
922
|
+
};
|
|
923
|
+
const maxCostUsd = positive("maxCostUsd");
|
|
924
|
+
const deadlineMs = positive("deadlineMs");
|
|
925
|
+
let minimumQuality = null;
|
|
926
|
+
if (input.minimumQuality !== void 0) {
|
|
927
|
+
if (typeof input.minimumQuality !== "number" || !Number.isFinite(input.minimumQuality) || input.minimumQuality < 0 || input.minimumQuality > 1)
|
|
928
|
+
errors.push("routing.minimumQuality must be between zero and one");
|
|
929
|
+
else minimumQuality = input.minimumQuality;
|
|
930
|
+
}
|
|
931
|
+
const requiredCapabilities = input.requiredCapabilities ?? [];
|
|
932
|
+
if (!Array.isArray(requiredCapabilities) || requiredCapabilities.some((entry) => typeof entry !== "string" || entry.trim().length === 0) || new Set(requiredCapabilities).size !== requiredCapabilities.length)
|
|
933
|
+
errors.push("routing.requiredCapabilities must be an array of unique non-empty strings");
|
|
934
|
+
const locality = input.locality ?? "any";
|
|
935
|
+
if (typeof locality !== "string" || !LOCALITIES.has(locality)) {
|
|
936
|
+
errors.push("routing.locality must be one of: local-only|prefer-local|any");
|
|
937
|
+
}
|
|
938
|
+
const failover = input.failover ?? "none";
|
|
939
|
+
if (typeof failover !== "string" || !FAILOVERS.has(failover)) {
|
|
940
|
+
errors.push("routing.failover must be one of: none|approved");
|
|
941
|
+
}
|
|
942
|
+
if (pinned && failover !== "none") errors.push("manual pins imply routing.failover none");
|
|
943
|
+
if (errors.length > 0) return { ok: false, errors };
|
|
944
|
+
return {
|
|
945
|
+
ok: true,
|
|
946
|
+
intent: {
|
|
947
|
+
posture,
|
|
948
|
+
maxCostUsd,
|
|
949
|
+
deadlineMs,
|
|
950
|
+
minimumQuality,
|
|
951
|
+
requiredCapabilities: [...requiredCapabilities],
|
|
952
|
+
locality,
|
|
953
|
+
failover
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
function defaultRoutingIntent(pins) {
|
|
958
|
+
const parsed = parseRoutingIntent(void 0, pins);
|
|
959
|
+
if (!parsed.ok) throw new Error(parsed.errors.join("; "));
|
|
960
|
+
return parsed.intent;
|
|
961
|
+
}
|
|
962
|
+
function routingIntentRejection(input) {
|
|
963
|
+
const { intent } = input;
|
|
964
|
+
if (intent.minimumQuality !== null && input.qualityLowerBound < intent.minimumQuality) return "minimum-quality";
|
|
965
|
+
if (intent.maxCostUsd !== null && input.costUpperBoundUsd > intent.maxCostUsd) return "max-cost";
|
|
966
|
+
if (intent.deadlineMs !== null && input.endToEndUpperBoundMs > intent.deadlineMs) return "deadline";
|
|
967
|
+
if (intent.locality === "local-only" && input.candidate.nodeId !== "local") return "local-only";
|
|
968
|
+
if (intent.requiredCapabilities.some((capability) => !input.capabilities.includes(capability))) {
|
|
969
|
+
return "required-capabilities";
|
|
970
|
+
}
|
|
971
|
+
return null;
|
|
972
|
+
}
|
|
973
|
+
var ROUTE_EXPLANATION_MAX_BYTES = 4096;
|
|
974
|
+
function safeIdentity(candidate) {
|
|
975
|
+
const safe = (value) => {
|
|
976
|
+
const redacted = /(?:https?:\/\/|api[_-]?key|token|secret|password)/i.test(value) ? "[redacted]" : value;
|
|
977
|
+
return redacted.length <= 128 ? redacted : `${redacted.slice(0, 127)}\u2026`;
|
|
978
|
+
};
|
|
979
|
+
return {
|
|
980
|
+
...candidate,
|
|
981
|
+
agentId: safe(candidate.agentId),
|
|
982
|
+
specFingerprint: safe(candidate.specFingerprint),
|
|
983
|
+
targetId: safe(candidate.targetId),
|
|
984
|
+
modelId: safe(candidate.modelId),
|
|
985
|
+
runtimeId: safe(candidate.runtimeId),
|
|
986
|
+
nodeId: safe(candidate.nodeId),
|
|
987
|
+
...candidate.thinkingLevel !== void 0 ? { thinkingLevel: safe(candidate.thinkingLevel) } : {},
|
|
988
|
+
toolSignature: safe(candidate.toolSignature),
|
|
989
|
+
promptCompositionHash: safe(candidate.promptCompositionHash),
|
|
990
|
+
endpointIdentityHash: safe(candidate.endpointIdentityHash),
|
|
991
|
+
settingsFingerprint: safe(candidate.settingsFingerprint)
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
function explainRouteDecision(decision, intent) {
|
|
995
|
+
const exclusions = [
|
|
996
|
+
...new Set(decision.candidateEvaluations.flatMap((entry) => entry.rejection ? [entry.rejection] : []))
|
|
997
|
+
];
|
|
998
|
+
const explanation = {
|
|
999
|
+
mode: decision.mode,
|
|
1000
|
+
executedRoute: safeIdentity(decision.executedRoute),
|
|
1001
|
+
shadowRecommendation: decision.mode === "shadow" ? safeIdentity(decision.selected) : null,
|
|
1002
|
+
hardExclusions: exclusions.slice(0, 16),
|
|
1003
|
+
approvedFallbacks: decision.approvedFallbacks.slice(0, 3).map(safeIdentity),
|
|
1004
|
+
costUpperBoundUsd: intent.maxCostUsd,
|
|
1005
|
+
deadlineMs: intent.deadlineMs,
|
|
1006
|
+
confidence: decision.confidence,
|
|
1007
|
+
activeEligible: decision.candidateEvaluations.some(
|
|
1008
|
+
(entry) => entry.rejection === null && entry.activeReadiness.ready
|
|
1009
|
+
),
|
|
1010
|
+
reasonCodes: decision.reasonCodes.slice(0, 16),
|
|
1011
|
+
decisionHash: decision.decisionHash,
|
|
1012
|
+
agentSelection: {
|
|
1013
|
+
request: decision.agentSelection.request,
|
|
1014
|
+
activation: decision.agentSelection.activation,
|
|
1015
|
+
baselineAgentId: safeIdentity({ ...decision.executedRoute, agentId: decision.agentSelection.baselineAgentId }).agentId,
|
|
1016
|
+
recommendedAgentId: safeIdentity({ ...decision.executedRoute, agentId: decision.agentSelection.recommendedAgentId }).agentId,
|
|
1017
|
+
excluded: decision.agentSelection.evaluations.filter((entry) => entry.rejections.length > 0).slice(0, 16).map((entry) => ({
|
|
1018
|
+
agentId: safeIdentity({ ...decision.executedRoute, agentId: entry.agentId }).agentId,
|
|
1019
|
+
reasons: entry.rejections.slice(0, 8)
|
|
1020
|
+
})),
|
|
1021
|
+
authorityTransition: decision.agentSelection.authorityTransition === null ? null : {
|
|
1022
|
+
...decision.agentSelection.authorityTransition,
|
|
1023
|
+
from: safeIdentity({
|
|
1024
|
+
...decision.executedRoute,
|
|
1025
|
+
agentId: decision.agentSelection.authorityTransition.from
|
|
1026
|
+
}).agentId,
|
|
1027
|
+
to: safeIdentity({
|
|
1028
|
+
...decision.executedRoute,
|
|
1029
|
+
agentId: decision.agentSelection.authorityTransition.to
|
|
1030
|
+
}).agentId
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
};
|
|
1034
|
+
if (Buffer.byteLength(JSON.stringify(explanation), "utf8") <= ROUTE_EXPLANATION_MAX_BYTES) return explanation;
|
|
1035
|
+
const compact = {
|
|
1036
|
+
...explanation,
|
|
1037
|
+
approvedFallbacks: explanation.approvedFallbacks.slice(0, 1),
|
|
1038
|
+
hardExclusions: explanation.hardExclusions.slice(0, 4),
|
|
1039
|
+
reasonCodes: explanation.reasonCodes.slice(0, 4),
|
|
1040
|
+
agentSelection: {
|
|
1041
|
+
...explanation.agentSelection,
|
|
1042
|
+
excluded: explanation.agentSelection.excluded.slice(0, 4).map((entry) => ({ ...entry, reasons: entry.reasons.slice(0, 2) }))
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
if (Buffer.byteLength(JSON.stringify(compact), "utf8") <= ROUTE_EXPLANATION_MAX_BYTES) return compact;
|
|
1046
|
+
return {
|
|
1047
|
+
...compact,
|
|
1048
|
+
shadowRecommendation: null,
|
|
1049
|
+
approvedFallbacks: [],
|
|
1050
|
+
hardExclusions: compact.hardExclusions.slice(0, 2),
|
|
1051
|
+
reasonCodes: compact.reasonCodes.slice(0, 2),
|
|
1052
|
+
agentSelection: {
|
|
1053
|
+
...compact.agentSelection,
|
|
1054
|
+
excluded: []
|
|
1055
|
+
}
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// src/domains/dispatch/receipt-integrity.ts
|
|
1060
|
+
var RUN_RECEIPT_INTEGRITY_VERSION = 15;
|
|
1061
|
+
var RUN_RECEIPT_INTEGRITY_ALGORITHM = "sha256";
|
|
1062
|
+
function sha256(input) {
|
|
1063
|
+
return createHash2("sha256").update(input, "utf8").digest("hex");
|
|
1064
|
+
}
|
|
1065
|
+
function canonicalJson(value) {
|
|
1066
|
+
return serializeCanonical(value);
|
|
1067
|
+
}
|
|
1068
|
+
function serializeCanonical(value) {
|
|
1069
|
+
if (value === null) return "null";
|
|
1070
|
+
if (typeof value === "number") {
|
|
1071
|
+
if (!Number.isFinite(value)) {
|
|
1072
|
+
throw new Error(`receipt integrity: non-finite number ${String(value)} is not representable`);
|
|
1073
|
+
}
|
|
1074
|
+
return JSON.stringify(value);
|
|
1075
|
+
}
|
|
1076
|
+
if (typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
|
|
1077
|
+
if (typeof value === "bigint") throw new Error("receipt integrity: bigint is not representable");
|
|
1078
|
+
if (typeof value === "symbol" || typeof value === "function") {
|
|
1079
|
+
throw new Error(`receipt integrity: ${typeof value} is not representable`);
|
|
1080
|
+
}
|
|
1081
|
+
if (value === void 0) throw new Error("receipt integrity: undefined is not representable at root");
|
|
1082
|
+
if (Array.isArray(value)) {
|
|
1083
|
+
const parts = [];
|
|
1084
|
+
for (let i = 0; i < value.length; i++) {
|
|
1085
|
+
if (!(i in value) || value[i] === void 0) {
|
|
1086
|
+
parts.push("null");
|
|
1087
|
+
continue;
|
|
1088
|
+
}
|
|
1089
|
+
parts.push(serializeCanonical(value[i]));
|
|
1090
|
+
}
|
|
1091
|
+
return `[${parts.join(",")}]`;
|
|
1092
|
+
}
|
|
1093
|
+
if (typeof value === "object") {
|
|
1094
|
+
const obj = value;
|
|
1095
|
+
const keys = Object.keys(obj).sort();
|
|
1096
|
+
const parts = [];
|
|
1097
|
+
for (const key of keys) {
|
|
1098
|
+
const child = obj[key];
|
|
1099
|
+
if (child === void 0) continue;
|
|
1100
|
+
parts.push(`${JSON.stringify(key)}:${serializeCanonical(child)}`);
|
|
1101
|
+
}
|
|
1102
|
+
return `{${parts.join(",")}}`;
|
|
1103
|
+
}
|
|
1104
|
+
throw new Error(`receipt integrity: unsupported value of type ${typeof value}`);
|
|
1105
|
+
}
|
|
1106
|
+
var RECEIPT_INTEGRITY_FIELD_COVERAGE = {
|
|
1107
|
+
runId: true,
|
|
1108
|
+
agentId: true,
|
|
1109
|
+
executionRole: true,
|
|
1110
|
+
agentAudience: true,
|
|
1111
|
+
requestOrigin: true,
|
|
1112
|
+
task: true,
|
|
1113
|
+
targetId: true,
|
|
1114
|
+
wireModelId: true,
|
|
1115
|
+
runtimeId: true,
|
|
1116
|
+
runtimeKind: true,
|
|
1117
|
+
startedAt: true,
|
|
1118
|
+
endedAt: true,
|
|
1119
|
+
outcome: true,
|
|
1120
|
+
outcomeDetail: true,
|
|
1121
|
+
lineage: true,
|
|
1122
|
+
identity: true,
|
|
1123
|
+
node: true,
|
|
1124
|
+
attestation: true,
|
|
1125
|
+
reroutes: true,
|
|
1126
|
+
pipeline: true,
|
|
1127
|
+
gate: true,
|
|
1128
|
+
plan: true,
|
|
1129
|
+
personaOverride: true,
|
|
1130
|
+
projectContext: true,
|
|
1131
|
+
exitCode: true,
|
|
1132
|
+
failureMessage: true,
|
|
1133
|
+
tokenCount: true,
|
|
1134
|
+
inputTokenCount: true,
|
|
1135
|
+
outputTokenCount: true,
|
|
1136
|
+
cacheReadTokenCount: true,
|
|
1137
|
+
cacheWriteTokenCount: true,
|
|
1138
|
+
reasoningTokenCount: true,
|
|
1139
|
+
upstreamResponses: true,
|
|
1140
|
+
output: true,
|
|
1141
|
+
costUsd: true,
|
|
1142
|
+
costProvenance: true,
|
|
1143
|
+
compiledPromptHash: true,
|
|
1144
|
+
staticCompositionHash: true,
|
|
1145
|
+
staticShellHash: true,
|
|
1146
|
+
sessionShellHash: true,
|
|
1147
|
+
dynamicHash: true,
|
|
1148
|
+
promptSignature: true,
|
|
1149
|
+
toolSignature: true,
|
|
1150
|
+
clioVersion: true,
|
|
1151
|
+
piMonoVersion: true,
|
|
1152
|
+
platform: true,
|
|
1153
|
+
nodeVersion: true,
|
|
1154
|
+
toolCalls: true,
|
|
1155
|
+
toolStats: true,
|
|
1156
|
+
toolActivity: true,
|
|
1157
|
+
verification: true,
|
|
1158
|
+
routingIntent: true,
|
|
1159
|
+
quality: true,
|
|
1160
|
+
skillActivations: true,
|
|
1161
|
+
autonomyEnforcement: true,
|
|
1162
|
+
safety: true,
|
|
1163
|
+
reproducibility: true,
|
|
1164
|
+
runtimeResolution: true,
|
|
1165
|
+
delegation: true,
|
|
1166
|
+
findingsSummary: true,
|
|
1167
|
+
// Both are optional and absent unless the run produced the condition they
|
|
1168
|
+
// name, so a receipt that carries neither digests exactly as it did before
|
|
1169
|
+
// they existed and the integrity version stays where it is.
|
|
1170
|
+
validationGrounding: true,
|
|
1171
|
+
capabilityMismatch: true,
|
|
1172
|
+
// Same reasoning: absent unless the run had an agent ledger, so a receipt
|
|
1173
|
+
// without one digests byte-identically to one produced before it existed.
|
|
1174
|
+
ledgerContribution: true,
|
|
1175
|
+
sessionId: true,
|
|
1176
|
+
briefing: true,
|
|
1177
|
+
outcomeCode: true,
|
|
1178
|
+
steering: true,
|
|
1179
|
+
routeDecision: true
|
|
1180
|
+
};
|
|
1181
|
+
var RECEIPT_FIELDS = Object.keys(RECEIPT_INTEGRITY_FIELD_COVERAGE);
|
|
1182
|
+
function selectedReceiptFields(receipt, fields) {
|
|
1183
|
+
const source = receipt;
|
|
1184
|
+
const result = {};
|
|
1185
|
+
for (const field of fields) {
|
|
1186
|
+
const value = source[field];
|
|
1187
|
+
if (value !== void 0) result[field] = value;
|
|
1188
|
+
}
|
|
1189
|
+
return result;
|
|
1190
|
+
}
|
|
1191
|
+
function receiptDigestFields(receipt) {
|
|
1192
|
+
const result = selectedReceiptFields(receipt, RECEIPT_FIELDS);
|
|
1193
|
+
result.briefing = receipt.briefing ?? null;
|
|
1194
|
+
result.outcomeCode = receipt.outcomeCode ?? null;
|
|
1195
|
+
result.steering = receipt.steering ?? null;
|
|
1196
|
+
return result;
|
|
1197
|
+
}
|
|
1198
|
+
function ledgerDigestFields(envelope) {
|
|
1199
|
+
return {
|
|
1200
|
+
id: envelope.id,
|
|
1201
|
+
agentId: envelope.agentId,
|
|
1202
|
+
executionRole: envelope.executionRole,
|
|
1203
|
+
task: envelope.task,
|
|
1204
|
+
targetId: envelope.targetId,
|
|
1205
|
+
wireModelId: envelope.wireModelId,
|
|
1206
|
+
runtimeId: envelope.runtimeId,
|
|
1207
|
+
runtimeKind: envelope.runtimeKind,
|
|
1208
|
+
startedAt: envelope.startedAt,
|
|
1209
|
+
endedAt: envelope.endedAt,
|
|
1210
|
+
status: envelope.status,
|
|
1211
|
+
exitCode: envelope.exitCode,
|
|
1212
|
+
sessionId: envelope.sessionId,
|
|
1213
|
+
cwd: envelope.cwd,
|
|
1214
|
+
tokenCount: envelope.tokenCount,
|
|
1215
|
+
cacheReadTokenCount: envelope.cacheReadTokenCount,
|
|
1216
|
+
cacheWriteTokenCount: envelope.cacheWriteTokenCount,
|
|
1217
|
+
reasoningTokenCount: envelope.reasoningTokenCount,
|
|
1218
|
+
staticShellHash: envelope.staticShellHash,
|
|
1219
|
+
sessionShellHash: envelope.sessionShellHash,
|
|
1220
|
+
dynamicHash: envelope.dynamicHash,
|
|
1221
|
+
costUsd: envelope.costUsd,
|
|
1222
|
+
agentAudience: envelope.agentAudience ?? null,
|
|
1223
|
+
requestOrigin: envelope.requestOrigin ?? null,
|
|
1224
|
+
outcome: envelope.outcome ?? null,
|
|
1225
|
+
outcomeDetail: envelope.outcomeDetail ?? null,
|
|
1226
|
+
lineage: envelope.lineage ?? null,
|
|
1227
|
+
identity: envelope.identity ?? null,
|
|
1228
|
+
node: envelope.node ?? null,
|
|
1229
|
+
reroutes: envelope.reroutes ?? null,
|
|
1230
|
+
pipeline: envelope.pipeline ?? null,
|
|
1231
|
+
gate: envelope.gate ?? null,
|
|
1232
|
+
plan: envelope.plan ?? null,
|
|
1233
|
+
personaOverride: envelope.personaOverride ?? null,
|
|
1234
|
+
inputTokenCount: envelope.inputTokenCount ?? 0,
|
|
1235
|
+
outputTokenCount: envelope.outputTokenCount ?? 0,
|
|
1236
|
+
promptSignature: envelope.promptSignature ?? null,
|
|
1237
|
+
toolSignature: envelope.toolSignature ?? null,
|
|
1238
|
+
briefing: envelope.briefing ?? null,
|
|
1239
|
+
outcomeCode: envelope.outcomeCode ?? null,
|
|
1240
|
+
steering: envelope.steering ?? null
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
function integrityPayload(receipt, envelope) {
|
|
1244
|
+
return {
|
|
1245
|
+
contract: "clio.runReceipt.integrity",
|
|
1246
|
+
version: RUN_RECEIPT_INTEGRITY_VERSION,
|
|
1247
|
+
sources: ["receipt", "run-ledger"],
|
|
1248
|
+
receipt: receiptDigestFields(receipt),
|
|
1249
|
+
ledger: ledgerDigestFields(envelope)
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
function computeReceiptIntegrity(receipt, envelope) {
|
|
1253
|
+
return {
|
|
1254
|
+
version: RUN_RECEIPT_INTEGRITY_VERSION,
|
|
1255
|
+
algorithm: RUN_RECEIPT_INTEGRITY_ALGORITHM,
|
|
1256
|
+
digest: sha256(canonicalJson(integrityPayload(receipt, envelope)))
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
function isReceiptQuality(value) {
|
|
1260
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1261
|
+
const quality = value;
|
|
1262
|
+
if (quality.version !== 1 || !Array.isArray(quality.typedValidations)) return false;
|
|
1263
|
+
if (!quality.typedValidations.every(
|
|
1264
|
+
(fact) => fact !== null && typeof fact === "object" && typeof fact.sourceId === "string" && typeof fact.validatorDigest === "string" && /^[0-9a-f]{64}$/.test(String(fact.validatorDigest)) && typeof fact.passed === "boolean"
|
|
1265
|
+
))
|
|
1266
|
+
return false;
|
|
1267
|
+
const schema = quality.responseSchema;
|
|
1268
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema)) return false;
|
|
1269
|
+
const responseSchema = schema;
|
|
1270
|
+
const resultContract = quality.resultContract;
|
|
1271
|
+
const validResultContract = resultContract === null || resultContract !== void 0 && typeof resultContract === "object" && !Array.isArray(resultContract) && typeof resultContract.sourceId === "string" && typeof resultContract.validatorDigest === "string" && /^[0-9a-f]{64}$/.test(String(resultContract.validatorDigest)) && (resultContract.conformance === "pass" || resultContract.conformance === "fail" || resultContract.conformance === "not-reached") && (resultContract.quality === "pass" || resultContract.quality === "fail" || resultContract.quality === "unmeasured");
|
|
1272
|
+
return (responseSchema.sourceId === null || typeof responseSchema.sourceId === "string") && (responseSchema.schemaDigest === null || typeof responseSchema.schemaDigest === "string" && /^[0-9a-f]{64}$/.test(responseSchema.schemaDigest)) && typeof responseSchema.runtimeEnforceable === "boolean" && (responseSchema.enforcementPassed === null || typeof responseSchema.enforcementPassed === "boolean") && validResultContract;
|
|
1273
|
+
}
|
|
1274
|
+
function isCurrentRouteDecision(value) {
|
|
1275
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
1276
|
+
const decision = value;
|
|
1277
|
+
return decision.policyVersion === ROUTE_POLICY_VERSION && (decision.mode === "fixed" || decision.mode === "shadow" || decision.mode === "active") && isRouteDecisionAgentSelection(decision.agentSelection) && typeof decision.decisionHash === "string" && /^[0-9a-f]{64}$/u.test(decision.decisionHash);
|
|
1278
|
+
}
|
|
1279
|
+
function withReceiptIntegrity(receipt, envelope) {
|
|
1280
|
+
if (!isReceiptQuality(receipt.quality)) throw new Error("receipt integrity: required quality block invalid");
|
|
1281
|
+
if (!isExecutionRole(receipt.executionRole)) throw new Error("receipt integrity: required execution role invalid");
|
|
1282
|
+
if (!isRoutingIntent(receipt.routingIntent)) {
|
|
1283
|
+
throw new Error("receipt integrity: required routing intent invalid");
|
|
1284
|
+
}
|
|
1285
|
+
if (receipt.routeDecision !== void 0 && !isCurrentRouteDecision(receipt.routeDecision)) {
|
|
1286
|
+
throw new Error("receipt integrity: route decision is invalid or retired");
|
|
1287
|
+
}
|
|
1288
|
+
return {
|
|
1289
|
+
...receipt,
|
|
1290
|
+
routingIntent: receipt.routingIntent,
|
|
1291
|
+
integrity: computeReceiptIntegrity(receipt, envelope)
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
function isReceiptIntegrity(value) {
|
|
1295
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1296
|
+
const candidate = value;
|
|
1297
|
+
return candidate.version === RUN_RECEIPT_INTEGRITY_VERSION && candidate.algorithm === RUN_RECEIPT_INTEGRITY_ALGORITHM && typeof candidate.digest === "string" && /^[0-9a-f]{64}$/.test(candidate.digest);
|
|
1298
|
+
}
|
|
1299
|
+
function firstLedgerMismatch(receipt, envelope) {
|
|
1300
|
+
const sharedFields = [
|
|
1301
|
+
["runId", receipt.runId, envelope.id],
|
|
1302
|
+
["agentId", receipt.agentId, envelope.agentId],
|
|
1303
|
+
["executionRole", receipt.executionRole, envelope.executionRole],
|
|
1304
|
+
["agentAudience", receipt.agentAudience, envelope.agentAudience],
|
|
1305
|
+
["requestOrigin", receipt.requestOrigin, envelope.requestOrigin],
|
|
1306
|
+
["task", receipt.task, envelope.task],
|
|
1307
|
+
["targetId", receipt.targetId, envelope.targetId],
|
|
1308
|
+
["wireModelId", receipt.wireModelId, envelope.wireModelId],
|
|
1309
|
+
["runtimeId", receipt.runtimeId, envelope.runtimeId],
|
|
1310
|
+
["runtimeKind", receipt.runtimeKind, envelope.runtimeKind],
|
|
1311
|
+
["startedAt", receipt.startedAt, envelope.startedAt],
|
|
1312
|
+
["endedAt", receipt.endedAt, envelope.endedAt],
|
|
1313
|
+
["exitCode", receipt.exitCode, envelope.exitCode],
|
|
1314
|
+
["tokenCount", receipt.tokenCount, envelope.tokenCount],
|
|
1315
|
+
["cacheReadTokenCount", receipt.cacheReadTokenCount ?? 0, envelope.cacheReadTokenCount ?? 0],
|
|
1316
|
+
["cacheWriteTokenCount", receipt.cacheWriteTokenCount ?? 0, envelope.cacheWriteTokenCount ?? 0],
|
|
1317
|
+
["reasoningTokenCount", receipt.reasoningTokenCount ?? 0, envelope.reasoningTokenCount ?? 0],
|
|
1318
|
+
["staticShellHash", receipt.staticShellHash ?? null, envelope.staticShellHash ?? null],
|
|
1319
|
+
["sessionShellHash", receipt.sessionShellHash ?? null, envelope.sessionShellHash ?? null],
|
|
1320
|
+
["dynamicHash", receipt.dynamicHash ?? null, envelope.dynamicHash ?? null],
|
|
1321
|
+
["costUsd", receipt.costUsd, envelope.costUsd],
|
|
1322
|
+
["sessionId", receipt.sessionId, envelope.sessionId],
|
|
1323
|
+
["outcome", receipt.outcome ?? null, envelope.outcome ?? null],
|
|
1324
|
+
["outcomeDetail", receipt.outcomeDetail ?? null, envelope.outcomeDetail ?? null],
|
|
1325
|
+
["nodeId", receipt.node?.id ?? null, envelope.node?.id ?? null],
|
|
1326
|
+
["outcomeCode", receipt.outcomeCode ?? null, envelope.outcomeCode ?? null]
|
|
1327
|
+
];
|
|
1328
|
+
for (const [field, receiptValue, ledgerValue] of sharedFields) {
|
|
1329
|
+
if (!Object.is(receiptValue, ledgerValue)) return field;
|
|
1330
|
+
}
|
|
1331
|
+
if (canonicalJson(receipt.briefing ?? null) !== canonicalJson(envelope.briefing ?? null)) return "briefing";
|
|
1332
|
+
if (canonicalJson(receipt.steering ?? null) !== canonicalJson(envelope.steering ?? null)) return "steering";
|
|
1333
|
+
return null;
|
|
1334
|
+
}
|
|
1335
|
+
function verifyReceiptIntegrity(receipt, envelope) {
|
|
1336
|
+
if (!isReceiptQuality(receipt.quality) || !isReceiptIntegrity(receipt.integrity)) {
|
|
1337
|
+
return { ok: false, reason: "integrity invalid" };
|
|
1338
|
+
}
|
|
1339
|
+
if (!isExecutionRole(receipt.executionRole)) return { ok: false, reason: "execution role invalid" };
|
|
1340
|
+
if (!isRoutingIntent(receipt.routingIntent)) {
|
|
1341
|
+
return { ok: false, reason: "routing intent invalid" };
|
|
1342
|
+
}
|
|
1343
|
+
if (receipt.routeDecision !== void 0 && !isCurrentRouteDecision(receipt.routeDecision)) {
|
|
1344
|
+
return { ok: false, reason: "route decision invalid" };
|
|
1345
|
+
}
|
|
1346
|
+
const mismatch = firstLedgerMismatch(receipt, envelope);
|
|
1347
|
+
if (mismatch) {
|
|
1348
|
+
return { ok: false, reason: `ledger mismatch: ${mismatch}` };
|
|
1349
|
+
}
|
|
1350
|
+
const expected = computeReceiptIntegrity(receipt, envelope);
|
|
1351
|
+
if (expected.digest !== receipt.integrity.digest) {
|
|
1352
|
+
return { ok: false, reason: "integrity mismatch" };
|
|
1353
|
+
}
|
|
1354
|
+
return { ok: true };
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
// src/domains/eval/harness-metrics.ts
|
|
1358
|
+
var ZERO_EVAL_HARNESS_METRICS = {
|
|
1359
|
+
receiptCount: 0,
|
|
1360
|
+
toolCalls: 0,
|
|
1361
|
+
retries: 0,
|
|
1362
|
+
safetyBlocks: 0,
|
|
1363
|
+
correctionLatencyMs: 0,
|
|
1364
|
+
validationEvidence: 0
|
|
1365
|
+
};
|
|
1366
|
+
function evalHarnessMetricsFromCommands(commands) {
|
|
1367
|
+
return {
|
|
1368
|
+
...ZERO_EVAL_HARNESS_METRICS,
|
|
1369
|
+
validationEvidence: commands.filter(
|
|
1370
|
+
(command) => command.phase === "verifier" && command.exitCode === 0 && !command.timedOut
|
|
1371
|
+
).length
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
function sumEvalHarnessMetrics(records) {
|
|
1375
|
+
return records.reduce((total, record2) => addEvalHarnessMetrics(total, record2.harness), {
|
|
1376
|
+
...ZERO_EVAL_HARNESS_METRICS
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
function addEvalHarnessMetrics(left, right) {
|
|
1380
|
+
return {
|
|
1381
|
+
receiptCount: left.receiptCount + right.receiptCount,
|
|
1382
|
+
toolCalls: left.toolCalls + right.toolCalls,
|
|
1383
|
+
retries: left.retries + right.retries,
|
|
1384
|
+
safetyBlocks: left.safetyBlocks + right.safetyBlocks,
|
|
1385
|
+
correctionLatencyMs: left.correctionLatencyMs + right.correctionLatencyMs,
|
|
1386
|
+
validationEvidence: left.validationEvidence + right.validationEvidence
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
// src/domains/eval/store.ts
|
|
1391
|
+
import { readFile } from "node:fs/promises";
|
|
1392
|
+
import { join } from "node:path";
|
|
1393
|
+
|
|
1394
|
+
// src/domains/eval/artifacts/redact.ts
|
|
1395
|
+
var SENSITIVE_KEY = /(?:^|[_-])(api[_-]?key|authorization|cookie|password|secret|token)(?:$|[_-])/i;
|
|
1396
|
+
var CREDENTIAL_PATTERNS = [
|
|
1397
|
+
[/\b(authorization\s*[:=]\s*)(?:bearer|basic)\s+[A-Za-z0-9._~+/=-]+/gi, "$1[redacted]"],
|
|
1398
|
+
[/\b(bearer\s+)[A-Za-z0-9._~+/=-]{12,}/gi, "$1[redacted]"],
|
|
1399
|
+
[/\b(sk-[A-Za-z0-9_-]{16,})\b/g, "[redacted]"],
|
|
1400
|
+
[/\b(gh[pousr]_[A-Za-z0-9_]{16,})\b/g, "[redacted]"],
|
|
1401
|
+
[/\b(AKIA[0-9A-Z]{16})\b/g, "[redacted]"],
|
|
1402
|
+
[/\b((?:api[_-]?key|access[_-]?token|refresh[_-]?token|password|secret)\s*[:=]\s*)[^\s"',}]+/gi, "$1[redacted]"]
|
|
1403
|
+
];
|
|
1404
|
+
function redactArtifactForStorage(value, env = process.env) {
|
|
1405
|
+
return redactValue(value, env);
|
|
1406
|
+
}
|
|
1407
|
+
function redactValue(value, env) {
|
|
1408
|
+
if (typeof value === "string") return redactString(value, env);
|
|
1409
|
+
if (Array.isArray(value)) return value.map((entry) => redactValue(entry, env));
|
|
1410
|
+
if (value !== null && typeof value === "object") {
|
|
1411
|
+
const out = {};
|
|
1412
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
1413
|
+
out[key] = SENSITIVE_KEY.test(key) ? "[redacted]" : redactValue(entry, env);
|
|
1414
|
+
}
|
|
1415
|
+
return out;
|
|
1416
|
+
}
|
|
1417
|
+
return value;
|
|
1418
|
+
}
|
|
1419
|
+
function redactString(value, env) {
|
|
1420
|
+
let out = stripHomePrefix(value, env.HOME);
|
|
1421
|
+
for (const [pattern, replacement] of CREDENTIAL_PATTERNS) {
|
|
1422
|
+
out = out.replace(pattern, replacement);
|
|
1423
|
+
}
|
|
1424
|
+
return out;
|
|
1425
|
+
}
|
|
1426
|
+
function stripHomePrefix(value, home) {
|
|
1427
|
+
if (home === void 0 || home.length === 0) return value;
|
|
1428
|
+
const normalized = home.replace(/\/+$/g, "");
|
|
1429
|
+
if (normalized.length === 0) return value;
|
|
1430
|
+
return value.split(normalized).join("$HOME");
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
// src/domains/eval/store.ts
|
|
1434
|
+
var NODE_SIGNALS = /* @__PURE__ */ new Set([
|
|
1435
|
+
"SIGHUP",
|
|
1436
|
+
"SIGINT",
|
|
1437
|
+
"SIGQUIT",
|
|
1438
|
+
"SIGILL",
|
|
1439
|
+
"SIGTRAP",
|
|
1440
|
+
"SIGABRT",
|
|
1441
|
+
"SIGIOT",
|
|
1442
|
+
"SIGBUS",
|
|
1443
|
+
"SIGFPE",
|
|
1444
|
+
"SIGKILL",
|
|
1445
|
+
"SIGUSR1",
|
|
1446
|
+
"SIGSEGV",
|
|
1447
|
+
"SIGUSR2",
|
|
1448
|
+
"SIGPIPE",
|
|
1449
|
+
"SIGALRM",
|
|
1450
|
+
"SIGTERM",
|
|
1451
|
+
"SIGCHLD",
|
|
1452
|
+
"SIGSTKFLT",
|
|
1453
|
+
"SIGCONT",
|
|
1454
|
+
"SIGSTOP",
|
|
1455
|
+
"SIGTSTP",
|
|
1456
|
+
"SIGTTIN",
|
|
1457
|
+
"SIGTTOU",
|
|
1458
|
+
"SIGURG",
|
|
1459
|
+
"SIGXCPU",
|
|
1460
|
+
"SIGXFSZ",
|
|
1461
|
+
"SIGVTALRM",
|
|
1462
|
+
"SIGPROF",
|
|
1463
|
+
"SIGWINCH",
|
|
1464
|
+
"SIGIO",
|
|
1465
|
+
"SIGPOLL",
|
|
1466
|
+
"SIGPWR",
|
|
1467
|
+
"SIGSYS",
|
|
1468
|
+
"SIGUNUSED"
|
|
1469
|
+
]);
|
|
1470
|
+
function evalRoot(dataDir) {
|
|
1471
|
+
return join(dataDir, "evals");
|
|
1472
|
+
}
|
|
1473
|
+
function evalArtifactPath(dataDir, evalId) {
|
|
1474
|
+
assertSafeId(evalId, "eval");
|
|
1475
|
+
return join(evalRoot(dataDir), `${evalId}.json`);
|
|
1476
|
+
}
|
|
1477
|
+
function createEvalId(startedAt, taskFileHash) {
|
|
1478
|
+
const stamp = startedAt.toISOString().replace(/[-:.]/g, "");
|
|
1479
|
+
return `eval-${stamp}-${taskFileHash.slice(0, 8)}`;
|
|
1480
|
+
}
|
|
1481
|
+
async function writeEvalArtifact(dataDir, artifact) {
|
|
1482
|
+
const path = evalArtifactPath(dataDir, artifact.evalId);
|
|
1483
|
+
const redacted = redactArtifactForStorage({
|
|
1484
|
+
...artifact,
|
|
1485
|
+
paths: { ...artifact.paths, artifact: path }
|
|
1486
|
+
});
|
|
1487
|
+
safeResourceWrite(path, `${JSON.stringify(redacted, null, 2)}
|
|
1488
|
+
`, { encoding: "utf8" });
|
|
1489
|
+
return path;
|
|
1490
|
+
}
|
|
1491
|
+
async function loadEvalArtifact(dataDir, evalId) {
|
|
1492
|
+
let raw;
|
|
1493
|
+
try {
|
|
1494
|
+
raw = await readFile(evalArtifactPath(dataDir, evalId), "utf8");
|
|
1495
|
+
} catch (error) {
|
|
1496
|
+
if (isErrorWithCode(error) && error.code === "ENOENT") throw new Error(`eval artifact not found: ${evalId}`);
|
|
1497
|
+
throw error;
|
|
1498
|
+
}
|
|
1499
|
+
const source = evalArtifactPath(dataDir, evalId);
|
|
1500
|
+
const parsed = parseJson(raw, source);
|
|
1501
|
+
return parseArtifact(parsed, source);
|
|
1502
|
+
}
|
|
1503
|
+
function parseArtifact(value, source) {
|
|
1504
|
+
if (!isRecord(value)) throw new Error(`${source}: expected object`);
|
|
1505
|
+
if (value.version !== 1) throw new Error(`${source}.version: expected 1`);
|
|
1506
|
+
const results = readArray(value, source, "results").map(
|
|
1507
|
+
(entry, index) => parseRecord(entry, `${source}.results[${index}]`)
|
|
1508
|
+
);
|
|
1509
|
+
return {
|
|
1510
|
+
version: 1,
|
|
1511
|
+
evalId: readString(value, source, "evalId"),
|
|
1512
|
+
taskFile: readString(value, source, "taskFile"),
|
|
1513
|
+
taskFileHash: readString(value, source, "taskFileHash"),
|
|
1514
|
+
clio: parseClioProvenance(readRequiredArtifactField(value, source, "clio"), `${source}.clio`),
|
|
1515
|
+
environment: parseEnvironmentProvenance(
|
|
1516
|
+
readRequiredArtifactField(value, source, "environment"),
|
|
1517
|
+
`${source}.environment`
|
|
1518
|
+
),
|
|
1519
|
+
target: readOptionalNullableString(value, source, "target"),
|
|
1520
|
+
model: readOptionalNullableString(value, source, "model"),
|
|
1521
|
+
thinking: readOptionalNullableString(value, source, "thinking"),
|
|
1522
|
+
paths: parseArtifactPaths(readRequiredArtifactField(value, source, "paths"), `${source}.paths`),
|
|
1523
|
+
repeat: readNumber(value, source, "repeat"),
|
|
1524
|
+
startedAt: readString(value, source, "startedAt"),
|
|
1525
|
+
endedAt: readString(value, source, "endedAt"),
|
|
1526
|
+
summary: parseSummary(value.summary, `${source}.summary`),
|
|
1527
|
+
results
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1530
|
+
function parseSummary(value, source) {
|
|
1531
|
+
if (!isRecord(value)) throw new Error(`${source}: expected object`);
|
|
1532
|
+
return {
|
|
1533
|
+
runs: readNumber(value, source, "runs"),
|
|
1534
|
+
passed: readNumber(value, source, "passed"),
|
|
1535
|
+
failed: readNumber(value, source, "failed"),
|
|
1536
|
+
passRate: readNumber(value, source, "passRate"),
|
|
1537
|
+
tokens: readNumber(value, source, "tokens"),
|
|
1538
|
+
costUsd: readNumber(value, source, "costUsd"),
|
|
1539
|
+
wallTimeMs: readNumber(value, source, "wallTimeMs"),
|
|
1540
|
+
harness: value.harness === void 0 ? { ...ZERO_EVAL_HARNESS_METRICS } : parseHarnessMetrics(value.harness, `${source}.harness`),
|
|
1541
|
+
failureClasses: readArray(value, source, "failureClasses").map(
|
|
1542
|
+
(entry, index) => parseFailureClassCount(entry, `${source}.failureClasses[${index}]`)
|
|
1543
|
+
)
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
function parseRecord(value, source) {
|
|
1547
|
+
if (!isRecord(value)) throw new Error(`${source}: expected object`);
|
|
1548
|
+
const failureClass = readOptionalFailureClass(value, source, "failureClass");
|
|
1549
|
+
const commands = readArray(value, source, "commands").map(
|
|
1550
|
+
(entry, index) => parseCommand(entry, `${source}.commands[${index}]`)
|
|
1551
|
+
);
|
|
1552
|
+
const record2 = {
|
|
1553
|
+
taskId: readString(value, source, "taskId"),
|
|
1554
|
+
runId: readString(value, source, "runId"),
|
|
1555
|
+
repeatIndex: readNumber(value, source, "repeatIndex"),
|
|
1556
|
+
cwd: readString(value, source, "cwd"),
|
|
1557
|
+
prompt: readString(value, source, "prompt"),
|
|
1558
|
+
tags: readStringArray(value, source, "tags"),
|
|
1559
|
+
pass: readBoolean(value, source, "pass"),
|
|
1560
|
+
exitCode: readNumber(value, source, "exitCode"),
|
|
1561
|
+
tokens: readNumber(value, source, "tokens"),
|
|
1562
|
+
costUsd: readNumber(value, source, "costUsd"),
|
|
1563
|
+
wallTimeMs: readNumber(value, source, "wallTimeMs"),
|
|
1564
|
+
harness: value.harness === void 0 ? evalHarnessMetricsFromCommands(commands) : parseHarnessMetrics(value.harness, `${source}.harness`),
|
|
1565
|
+
commands
|
|
1566
|
+
};
|
|
1567
|
+
if (failureClass !== void 0) record2.failureClass = failureClass;
|
|
1568
|
+
const receiptPath = readOptionalString(value, source, "receiptPath");
|
|
1569
|
+
if (receiptPath !== void 0) record2.receiptPath = receiptPath;
|
|
1570
|
+
const evidenceId = readOptionalString(value, source, "evidenceId");
|
|
1571
|
+
if (evidenceId !== void 0) record2.evidenceId = evidenceId;
|
|
1572
|
+
const paths = value.paths === void 0 ? void 0 : parseRunPaths(value.paths, `${source}.paths`);
|
|
1573
|
+
if (paths !== void 0) record2.paths = paths;
|
|
1574
|
+
return record2;
|
|
1575
|
+
}
|
|
1576
|
+
function parseClioProvenance(value, source) {
|
|
1577
|
+
if (!isRecord(value)) throw new Error(`${source}: expected object`);
|
|
1578
|
+
return {
|
|
1579
|
+
version: readString(value, source, "version"),
|
|
1580
|
+
commit: readNullableString(value, source, "commit"),
|
|
1581
|
+
entry: readString(value, source, "entry")
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
function parseEnvironmentProvenance(value, source) {
|
|
1585
|
+
if (!isRecord(value)) throw new Error(`${source}: expected object`);
|
|
1586
|
+
return {
|
|
1587
|
+
platform: readString(value, source, "platform"),
|
|
1588
|
+
node: readString(value, source, "node")
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
function parseArtifactPaths(value, source) {
|
|
1592
|
+
if (!isRecord(value)) throw new Error(`${source}: expected object`);
|
|
1593
|
+
const out = {
|
|
1594
|
+
taskFile: readString(value, source, "taskFile"),
|
|
1595
|
+
receipts: readStringArray(value, source, "receipts"),
|
|
1596
|
+
sessionLedgers: readStringArray(value, source, "sessionLedgers")
|
|
1597
|
+
};
|
|
1598
|
+
const artifact = readOptionalString(value, source, "artifact");
|
|
1599
|
+
if (artifact !== void 0) out.artifact = artifact;
|
|
1600
|
+
const evidence = readOptionalString(value, source, "evidence");
|
|
1601
|
+
if (evidence !== void 0) out.evidence = evidence;
|
|
1602
|
+
return out;
|
|
1603
|
+
}
|
|
1604
|
+
function parseRunPaths(value, source) {
|
|
1605
|
+
if (!isRecord(value)) throw new Error(`${source}: expected object`);
|
|
1606
|
+
const out = {};
|
|
1607
|
+
const receipt = readOptionalString(value, source, "receipt");
|
|
1608
|
+
if (receipt !== void 0) out.receipt = receipt;
|
|
1609
|
+
const sessionLedger = readOptionalString(value, source, "sessionLedger");
|
|
1610
|
+
if (sessionLedger !== void 0) out.sessionLedger = sessionLedger;
|
|
1611
|
+
const evidence = readOptionalString(value, source, "evidence");
|
|
1612
|
+
if (evidence !== void 0) out.evidence = evidence;
|
|
1613
|
+
return out;
|
|
1614
|
+
}
|
|
1615
|
+
function readRequiredArtifactField(record2, source, field) {
|
|
1616
|
+
const value = record2[field];
|
|
1617
|
+
if (value === void 0) {
|
|
1618
|
+
throw new Error(
|
|
1619
|
+
`eval artifact has an invalid schema: missing required field '${field}' in ${source}. Re-run the evaluation suite to generate a complete artifact.`
|
|
1620
|
+
);
|
|
1621
|
+
}
|
|
1622
|
+
return value;
|
|
1623
|
+
}
|
|
1624
|
+
function parseHarnessMetrics(value, source) {
|
|
1625
|
+
if (!isRecord(value)) throw new Error(`${source}: expected object`);
|
|
1626
|
+
return {
|
|
1627
|
+
receiptCount: readNumber(value, source, "receiptCount"),
|
|
1628
|
+
toolCalls: readNumber(value, source, "toolCalls"),
|
|
1629
|
+
retries: readNumber(value, source, "retries"),
|
|
1630
|
+
safetyBlocks: readNumber(value, source, "safetyBlocks"),
|
|
1631
|
+
correctionLatencyMs: readNumber(value, source, "correctionLatencyMs"),
|
|
1632
|
+
validationEvidence: readNumber(value, source, "validationEvidence")
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
function parseCommand(value, source) {
|
|
1636
|
+
if (!isRecord(value)) throw new Error(`${source}: expected object`);
|
|
1637
|
+
const phase = readString(value, source, "phase");
|
|
1638
|
+
if (phase !== "setup" && phase !== "verifier") throw new Error(`${source}.phase: expected setup or verifier`);
|
|
1639
|
+
return {
|
|
1640
|
+
phase,
|
|
1641
|
+
index: readNumber(value, source, "index"),
|
|
1642
|
+
command: readString(value, source, "command"),
|
|
1643
|
+
exitCode: readNumber(value, source, "exitCode"),
|
|
1644
|
+
signal: readSignal(value.signal, `${source}.signal`),
|
|
1645
|
+
timedOut: readBoolean(value, source, "timedOut"),
|
|
1646
|
+
wallTimeMs: readNumber(value, source, "wallTimeMs"),
|
|
1647
|
+
stdout: readStringAllowEmpty(value, source, "stdout"),
|
|
1648
|
+
stderr: readStringAllowEmpty(value, source, "stderr")
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
function readSignal(value, source) {
|
|
1652
|
+
if (value === null) return null;
|
|
1653
|
+
if (typeof value === "string" && NODE_SIGNALS.has(value)) return value;
|
|
1654
|
+
throw new Error(`${source}: expected signal or null`);
|
|
1655
|
+
}
|
|
1656
|
+
function parseFailureClassCount(value, source) {
|
|
1657
|
+
if (!isRecord(value)) throw new Error(`${source}: expected object`);
|
|
1658
|
+
return {
|
|
1659
|
+
failureClass: readFailureClass(value, source, "failureClass"),
|
|
1660
|
+
count: readNumber(value, source, "count")
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
function readFailureClass(record2, source, field) {
|
|
1664
|
+
const value = record2[field];
|
|
1665
|
+
if (value === "setup_failed" || value === "verifier_failed" || value === "timeout" || value === "cwd_missing" || value === "command_error") {
|
|
1666
|
+
return value;
|
|
1667
|
+
}
|
|
1668
|
+
throw new Error(`${source}.${field}: expected failure class`);
|
|
1669
|
+
}
|
|
1670
|
+
function readOptionalFailureClass(record2, source, field) {
|
|
1671
|
+
if (record2[field] === void 0) return void 0;
|
|
1672
|
+
return readFailureClass(record2, source, field);
|
|
1673
|
+
}
|
|
1674
|
+
function readString(record2, source, field) {
|
|
1675
|
+
const value = record2[field];
|
|
1676
|
+
if (typeof value !== "string" || value.length === 0) throw new Error(`${source}.${field}: expected string`);
|
|
1677
|
+
return value;
|
|
1678
|
+
}
|
|
1679
|
+
function readStringAllowEmpty(record2, source, field) {
|
|
1680
|
+
const value = record2[field];
|
|
1681
|
+
if (typeof value !== "string") throw new Error(`${source}.${field}: expected string`);
|
|
1682
|
+
return value;
|
|
1683
|
+
}
|
|
1684
|
+
function readOptionalString(record2, source, field) {
|
|
1685
|
+
const value = record2[field];
|
|
1686
|
+
if (value === void 0) return void 0;
|
|
1687
|
+
if (typeof value !== "string" || value.length === 0) throw new Error(`${source}.${field}: expected string`);
|
|
1688
|
+
return value;
|
|
1689
|
+
}
|
|
1690
|
+
function readNullableString(record2, source, field) {
|
|
1691
|
+
const value = record2[field];
|
|
1692
|
+
if (value === null) return null;
|
|
1693
|
+
if (typeof value !== "string" || value.length === 0) throw new Error(`${source}.${field}: expected string or null`);
|
|
1694
|
+
return value;
|
|
1695
|
+
}
|
|
1696
|
+
function readOptionalNullableString(record2, source, field) {
|
|
1697
|
+
const value = record2[field];
|
|
1698
|
+
if (value === void 0 || value === null) return null;
|
|
1699
|
+
if (typeof value !== "string" || value.length === 0) throw new Error(`${source}.${field}: expected string or null`);
|
|
1700
|
+
return value;
|
|
1701
|
+
}
|
|
1702
|
+
function readStringArray(record2, source, field) {
|
|
1703
|
+
const value = readArray(record2, source, field);
|
|
1704
|
+
const out = [];
|
|
1705
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
1706
|
+
const entry = value[index];
|
|
1707
|
+
if (typeof entry !== "string") throw new Error(`${source}.${field}[${index}]: expected string`);
|
|
1708
|
+
out.push(entry);
|
|
1709
|
+
}
|
|
1710
|
+
return out;
|
|
1711
|
+
}
|
|
1712
|
+
function readNumber(record2, source, field) {
|
|
1713
|
+
const value = record2[field];
|
|
1714
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`${source}.${field}: expected number`);
|
|
1715
|
+
return value;
|
|
1716
|
+
}
|
|
1717
|
+
function readBoolean(record2, source, field) {
|
|
1718
|
+
const value = record2[field];
|
|
1719
|
+
if (typeof value !== "boolean") throw new Error(`${source}.${field}: expected boolean`);
|
|
1720
|
+
return value;
|
|
1721
|
+
}
|
|
1722
|
+
function readArray(record2, source, field) {
|
|
1723
|
+
const value = record2[field];
|
|
1724
|
+
if (!Array.isArray(value)) throw new Error(`${source}.${field}: expected array`);
|
|
1725
|
+
return value;
|
|
1726
|
+
}
|
|
1727
|
+
function parseJson(raw, source) {
|
|
1728
|
+
try {
|
|
1729
|
+
return JSON.parse(raw);
|
|
1730
|
+
} catch (error) {
|
|
1731
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1732
|
+
throw new Error(`${source}: invalid JSON: ${message}`);
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
function isRecord(value) {
|
|
1736
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1737
|
+
}
|
|
1738
|
+
function isErrorWithCode(error) {
|
|
1739
|
+
return error instanceof Error && "code" in error && typeof error.code === "string";
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
// src/domains/eval/artifacts/store.ts
|
|
1743
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
1744
|
+
import { join as join2 } from "node:path";
|
|
1745
|
+
function evalArtifactPathV4(dataDir, evalId) {
|
|
1746
|
+
assertSafeId(evalId, "eval");
|
|
1747
|
+
return join2(evalRoot(dataDir), `${evalId}.json`);
|
|
1748
|
+
}
|
|
1749
|
+
async function writeEvalArtifactV4(dataDir, artifact, out) {
|
|
1750
|
+
const path = out === void 0 ? evalArtifactPathV4(dataDir, artifact.evalId) : out.endsWith(".json") ? out : join2(out, `${artifact.evalId}.json`);
|
|
1751
|
+
safeResourceWrite(path, `${JSON.stringify(redactArtifactForStorage(artifact), null, 2)}
|
|
1752
|
+
`, { encoding: "utf8" });
|
|
1753
|
+
return path;
|
|
1754
|
+
}
|
|
1755
|
+
async function loadEvalArtifactV4(dataDir, evalId) {
|
|
1756
|
+
let raw;
|
|
1757
|
+
try {
|
|
1758
|
+
raw = await readFile2(evalArtifactPathV4(dataDir, evalId), "utf8");
|
|
1759
|
+
} catch (error) {
|
|
1760
|
+
if (error.code === "ENOENT") throw new Error(`eval artifact not found: ${evalId}`);
|
|
1761
|
+
throw error;
|
|
1762
|
+
}
|
|
1763
|
+
return parseEvalArtifactV4(JSON.parse(raw), evalId);
|
|
1764
|
+
}
|
|
1765
|
+
function parseEvalArtifactV4(value, source) {
|
|
1766
|
+
if (!isRecord2(value)) throw new Error(`${source}: expected object`);
|
|
1767
|
+
if (value.version !== 4) throw new Error(`${source}.version: expected current version 4`);
|
|
1768
|
+
const summary = asRecord(value.summary, `${source}.summary`);
|
|
1769
|
+
const matrix = asRecord(value.matrix, `${source}.matrix`);
|
|
1770
|
+
const suite = asRecord(value.suite, `${source}.suite`);
|
|
1771
|
+
return {
|
|
1772
|
+
version: 4,
|
|
1773
|
+
evalId: readString2(value, source, "evalId"),
|
|
1774
|
+
suite: { id: readString2(suite, `${source}.suite`, "id"), hash: readString2(suite, `${source}.suite`, "hash") },
|
|
1775
|
+
clio: {
|
|
1776
|
+
version: readString2(asRecord(value.clio, `${source}.clio`), `${source}.clio`, "version"),
|
|
1777
|
+
commit: readNullableString2(asRecord(value.clio, `${source}.clio`), `${source}.clio`, "commit"),
|
|
1778
|
+
entry: readString2(asRecord(value.clio, `${source}.clio`), `${source}.clio`, "entry")
|
|
1779
|
+
},
|
|
1780
|
+
environment: {
|
|
1781
|
+
platform: readString2(asRecord(value.environment, `${source}.environment`), `${source}.environment`, "platform"),
|
|
1782
|
+
node: readString2(asRecord(value.environment, `${source}.environment`), `${source}.environment`, "node")
|
|
1783
|
+
},
|
|
1784
|
+
matrix: {
|
|
1785
|
+
target: readString2(matrix, `${source}.matrix`, "target"),
|
|
1786
|
+
model: readNullableString2(matrix, `${source}.matrix`, "model"),
|
|
1787
|
+
thinking: readNullableString2(matrix, `${source}.matrix`, "thinking")
|
|
1788
|
+
},
|
|
1789
|
+
summary: {
|
|
1790
|
+
runs: readNumber2(summary, `${source}.summary`, "runs"),
|
|
1791
|
+
passed: readNumber2(summary, `${source}.summary`, "passed"),
|
|
1792
|
+
failed: readNumber2(summary, `${source}.summary`, "failed"),
|
|
1793
|
+
passRate: readNumber2(summary, `${source}.summary`, "passRate"),
|
|
1794
|
+
tokens: parseTokenAccounting(summary.tokens, `${source}.summary.tokens`),
|
|
1795
|
+
wallTimeMs: readNumber2(summary, `${source}.summary`, "wallTimeMs")
|
|
1796
|
+
},
|
|
1797
|
+
results: readArray2(value, source, "results").map((entry, index) => parseResult(entry, `${source}.results[${index}]`))
|
|
1798
|
+
};
|
|
1799
|
+
}
|
|
1800
|
+
function parseTokenAccounting(value, source) {
|
|
1801
|
+
const record2 = asRecord(value, source);
|
|
1802
|
+
const measured = readBoolean2(record2, source, "measured");
|
|
1803
|
+
const runs = readNumber2(record2, source, "runs");
|
|
1804
|
+
const measuredRuns = readNumber2(record2, source, "measuredRuns");
|
|
1805
|
+
const countFields = ["input", "output", "total", "cacheRead", "cacheWrite"];
|
|
1806
|
+
if (!measured) {
|
|
1807
|
+
if (measuredRuns !== 0) throw new Error(`${source}.measuredRuns: expected 0 when unmeasured`);
|
|
1808
|
+
const present = countFields.filter((field) => field in record2);
|
|
1809
|
+
if (present.length > 0) throw new Error(`${source}: unmeasured accounting carries no counts (${present.join(", ")})`);
|
|
1810
|
+
return { measured: false, runs, measuredRuns: 0 };
|
|
1811
|
+
}
|
|
1812
|
+
if (measuredRuns <= 0) throw new Error(`${source}.measuredRuns: expected a positive count when measured`);
|
|
1813
|
+
return {
|
|
1814
|
+
measured: true,
|
|
1815
|
+
runs,
|
|
1816
|
+
measuredRuns,
|
|
1817
|
+
input: readNumber2(record2, source, "input"),
|
|
1818
|
+
output: readNumber2(record2, source, "output"),
|
|
1819
|
+
total: readNumber2(record2, source, "total"),
|
|
1820
|
+
cacheRead: readNumber2(record2, source, "cacheRead"),
|
|
1821
|
+
cacheWrite: readNumber2(record2, source, "cacheWrite")
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
function parseResult(value, source) {
|
|
1825
|
+
const record2 = asRecord(value, source);
|
|
1826
|
+
const target = asRecord(record2.target, `${source}.target`);
|
|
1827
|
+
return {
|
|
1828
|
+
assignmentId: readNullableString2(record2, source, "assignmentId"),
|
|
1829
|
+
terminalReceiptDigest: readNullableDigest(record2, source, "terminalReceiptDigest"),
|
|
1830
|
+
taskId: readString2(record2, source, "taskId"),
|
|
1831
|
+
repeatIndex: readNumber2(record2, source, "repeatIndex"),
|
|
1832
|
+
target: {
|
|
1833
|
+
id: readString2(target, `${source}.target`, "id"),
|
|
1834
|
+
model: readNullableString2(target, `${source}.target`, "model"),
|
|
1835
|
+
thinking: readNullableString2(target, `${source}.target`, "thinking")
|
|
1836
|
+
},
|
|
1837
|
+
pass: readBoolean2(record2, source, "pass"),
|
|
1838
|
+
failureClass: readNullableString2(record2, source, "failureClass"),
|
|
1839
|
+
metrics: asRecord(record2.metrics, `${source}.metrics`),
|
|
1840
|
+
artifacts: asRecord(record2.artifacts, `${source}.artifacts`)
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
function asRecord(value, source) {
|
|
1844
|
+
if (isRecord2(value)) return value;
|
|
1845
|
+
throw new Error(`${source}: expected object`);
|
|
1846
|
+
}
|
|
1847
|
+
function isRecord2(value) {
|
|
1848
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1849
|
+
}
|
|
1850
|
+
function readString2(record2, source, field) {
|
|
1851
|
+
const value = record2[field];
|
|
1852
|
+
if (typeof value !== "string" || value.length === 0) throw new Error(`${source}.${field}: expected string`);
|
|
1853
|
+
return value;
|
|
1854
|
+
}
|
|
1855
|
+
function readNullableString2(record2, source, field) {
|
|
1856
|
+
const value = record2[field];
|
|
1857
|
+
if (value === null) return null;
|
|
1858
|
+
if (typeof value !== "string" || value.length === 0) throw new Error(`${source}.${field}: expected string or null`);
|
|
1859
|
+
return value;
|
|
1860
|
+
}
|
|
1861
|
+
function readNullableDigest(record2, source, field) {
|
|
1862
|
+
const value = readNullableString2(record2, source, field);
|
|
1863
|
+
if (value !== null && !/^[0-9a-f]{64}$/u.test(value))
|
|
1864
|
+
throw new Error(`${source}.${field}: expected sha256 digest or null`);
|
|
1865
|
+
return value;
|
|
1866
|
+
}
|
|
1867
|
+
function readNumber2(record2, source, field) {
|
|
1868
|
+
const value = record2[field];
|
|
1869
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`${source}.${field}: expected number`);
|
|
1870
|
+
return value;
|
|
1871
|
+
}
|
|
1872
|
+
function readBoolean2(record2, source, field) {
|
|
1873
|
+
const value = record2[field];
|
|
1874
|
+
if (typeof value !== "boolean") throw new Error(`${source}.${field}: expected boolean`);
|
|
1875
|
+
return value;
|
|
1876
|
+
}
|
|
1877
|
+
function readArray2(record2, source, field) {
|
|
1878
|
+
const value = record2[field];
|
|
1879
|
+
if (!Array.isArray(value)) throw new Error(`${source}.${field}: expected array`);
|
|
1880
|
+
return value;
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
// src/domains/eval/provenance.ts
|
|
1884
|
+
import { spawnSync } from "node:child_process";
|
|
1885
|
+
function evalClioProvenance(options = {}) {
|
|
1886
|
+
return {
|
|
1887
|
+
version: readClioVersion(),
|
|
1888
|
+
commit: options.commit === void 0 ? currentClioCommit() : options.commit,
|
|
1889
|
+
entry: options.entry ?? process.argv[1] ?? "unknown"
|
|
1890
|
+
};
|
|
1891
|
+
}
|
|
1892
|
+
function evalEnvironmentProvenance() {
|
|
1893
|
+
return {
|
|
1894
|
+
platform: `${process.platform}-${process.arch}`,
|
|
1895
|
+
node: process.version
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
function currentClioCommit() {
|
|
1899
|
+
const result = spawnSync("git", ["rev-parse", "HEAD"], {
|
|
1900
|
+
cwd: resolvePackageRoot(),
|
|
1901
|
+
encoding: "utf8",
|
|
1902
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1903
|
+
timeout: 1e3
|
|
1904
|
+
});
|
|
1905
|
+
if (result.status !== 0 || typeof result.stdout !== "string") return null;
|
|
1906
|
+
const value = result.stdout.trim();
|
|
1907
|
+
return value.length > 0 ? value : null;
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
// src/domains/eval/task-file.ts
|
|
1911
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
1912
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
1913
|
+
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
1914
|
+
import { parse as parseYaml } from "yaml";
|
|
1915
|
+
|
|
1916
|
+
// src/domains/eval/types.ts
|
|
1917
|
+
var EVAL_TASK_FILE_VERSION = 1;
|
|
1918
|
+
|
|
1919
|
+
// src/domains/eval/task-file.ts
|
|
1920
|
+
var TASK_KEYS = /* @__PURE__ */ new Set(["id", "prompt", "cwd", "setup", "verifier", "timeoutMs", "tags"]);
|
|
1921
|
+
async function loadEvalTaskFile(path) {
|
|
1922
|
+
const resolved = resolve(path);
|
|
1923
|
+
const raw = await readFile3(resolved, "utf8");
|
|
1924
|
+
const result = parseEvalTaskFileYaml(raw);
|
|
1925
|
+
if (!result.valid) {
|
|
1926
|
+
throw new EvalTaskFileError(result.issues);
|
|
1927
|
+
}
|
|
1928
|
+
const baseDir = dirname(resolved);
|
|
1929
|
+
const cwdIssues = validateTaskCwds(result.taskFile.tasks, baseDir);
|
|
1930
|
+
if (cwdIssues.length > 0) throw new EvalTaskFileError(cwdIssues);
|
|
1931
|
+
return {
|
|
1932
|
+
path: resolved,
|
|
1933
|
+
baseDir,
|
|
1934
|
+
contentHash: sha256Hex(raw),
|
|
1935
|
+
taskFile: result.taskFile
|
|
1936
|
+
};
|
|
1937
|
+
}
|
|
1938
|
+
function parseEvalTaskFileYaml(raw) {
|
|
1939
|
+
let parsed;
|
|
1940
|
+
try {
|
|
1941
|
+
parsed = parseYaml(raw);
|
|
1942
|
+
} catch (error) {
|
|
1943
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1944
|
+
return { valid: false, issues: [{ path: "$", message: `invalid YAML: ${message}` }] };
|
|
1945
|
+
}
|
|
1946
|
+
return validateEvalTaskFile(parsed);
|
|
1947
|
+
}
|
|
1948
|
+
function validateEvalTaskFile(value) {
|
|
1949
|
+
const issues = [];
|
|
1950
|
+
if (!isRecord3(value)) {
|
|
1951
|
+
return { valid: false, issues: [{ path: "$", message: "expected object" }] };
|
|
1952
|
+
}
|
|
1953
|
+
if (value.version !== EVAL_TASK_FILE_VERSION) {
|
|
1954
|
+
issues.push({ path: "$.version", message: "expected version 1" });
|
|
1955
|
+
}
|
|
1956
|
+
if (!Array.isArray(value.tasks)) {
|
|
1957
|
+
issues.push({ path: "$.tasks", message: "expected non-empty array" });
|
|
1958
|
+
return { valid: false, issues };
|
|
1959
|
+
}
|
|
1960
|
+
if (value.tasks.length === 0) {
|
|
1961
|
+
issues.push({ path: "$.tasks", message: "expected non-empty array" });
|
|
1962
|
+
}
|
|
1963
|
+
const tasks = [];
|
|
1964
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
1965
|
+
for (let index = 0; index < value.tasks.length; index += 1) {
|
|
1966
|
+
const task = parseTask(value.tasks[index], `$.tasks[${index}]`, issues);
|
|
1967
|
+
if (task === null) continue;
|
|
1968
|
+
if (seenIds.has(task.id)) {
|
|
1969
|
+
issues.push({ path: `$.tasks[${index}].id`, message: `duplicate task id: ${task.id}` });
|
|
1970
|
+
}
|
|
1971
|
+
seenIds.add(task.id);
|
|
1972
|
+
tasks.push(task);
|
|
1973
|
+
}
|
|
1974
|
+
if (issues.length > 0) return { valid: false, issues };
|
|
1975
|
+
return { valid: true, taskFile: { version: 1, tasks } };
|
|
1976
|
+
}
|
|
1977
|
+
var EvalTaskFileError = class extends Error {
|
|
1978
|
+
issues;
|
|
1979
|
+
constructor(issues) {
|
|
1980
|
+
super(`eval task file invalid (${issues.length} ${issues.length === 1 ? "issue" : "issues"})`);
|
|
1981
|
+
this.name = "EvalTaskFileError";
|
|
1982
|
+
this.issues = [...issues];
|
|
1983
|
+
}
|
|
1984
|
+
};
|
|
1985
|
+
function parseTask(value, path, issues) {
|
|
1986
|
+
if (!isRecord3(value)) {
|
|
1987
|
+
issues.push({ path, message: "expected object" });
|
|
1988
|
+
return null;
|
|
1989
|
+
}
|
|
1990
|
+
for (const key of Object.keys(value).sort((a, b) => a.localeCompare(b))) {
|
|
1991
|
+
if (!TASK_KEYS.has(key)) issues.push({ path: `${path}.${key}`, message: "unknown field" });
|
|
1992
|
+
}
|
|
1993
|
+
const id = readNonEmptyString(value, path, "id", issues);
|
|
1994
|
+
if (id !== null && !/^[A-Za-z0-9._-]+$/.test(id)) {
|
|
1995
|
+
issues.push({ path: `${path}.id`, message: "expected id with letters, numbers, dots, underscores, or hyphens" });
|
|
1996
|
+
}
|
|
1997
|
+
const prompt = readNonEmptyString(value, path, "prompt", issues);
|
|
1998
|
+
const cwd = readNonEmptyString(value, path, "cwd", issues);
|
|
1999
|
+
if (cwd !== null && isAbsolute(cwd)) {
|
|
2000
|
+
issues.push({ path: `${path}.cwd`, message: "expected repo-local relative path" });
|
|
2001
|
+
}
|
|
2002
|
+
const setup = readStringArray2(value, path, "setup", issues, true);
|
|
2003
|
+
const verifier = readStringArray2(value, path, "verifier", issues, false);
|
|
2004
|
+
if (verifier !== null && verifier.length === 0) {
|
|
2005
|
+
issues.push({ path: `${path}.verifier`, message: "expected at least one command" });
|
|
2006
|
+
}
|
|
2007
|
+
const timeoutMs = readPositiveInteger(value, path, "timeoutMs", issues);
|
|
2008
|
+
const tags = readStringArray2(value, path, "tags", issues, true);
|
|
2009
|
+
if (id === null || prompt === null || cwd === null || setup === null || verifier === null || timeoutMs === null) {
|
|
2010
|
+
return null;
|
|
2011
|
+
}
|
|
2012
|
+
return { id, prompt, cwd, setup, verifier, timeoutMs, tags: tags ?? [] };
|
|
2013
|
+
}
|
|
2014
|
+
function validateTaskCwds(tasks, baseDir) {
|
|
2015
|
+
const issues = [];
|
|
2016
|
+
for (let index = 0; index < tasks.length; index += 1) {
|
|
2017
|
+
const task = tasks[index];
|
|
2018
|
+
if (task === void 0) continue;
|
|
2019
|
+
const resolved = resolve(baseDir, task.cwd);
|
|
2020
|
+
const rel = relative(baseDir, resolved);
|
|
2021
|
+
if (rel === "" || !rel.startsWith("..") && !isAbsolute(rel)) continue;
|
|
2022
|
+
issues.push({ path: `$.tasks[${index}].cwd`, message: "cwd must stay within the task file directory" });
|
|
2023
|
+
}
|
|
2024
|
+
return issues;
|
|
2025
|
+
}
|
|
2026
|
+
function readNonEmptyString(record2, path, field, issues) {
|
|
2027
|
+
const value = record2[field];
|
|
2028
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
2029
|
+
issues.push({ path: `${path}.${field}`, message: "expected non-empty string" });
|
|
2030
|
+
return null;
|
|
2031
|
+
}
|
|
2032
|
+
return value;
|
|
2033
|
+
}
|
|
2034
|
+
function readStringArray2(record2, path, field, issues, allowMissing) {
|
|
2035
|
+
const value = record2[field];
|
|
2036
|
+
if (value === void 0 && allowMissing) return [];
|
|
2037
|
+
if (!Array.isArray(value)) {
|
|
2038
|
+
issues.push({ path: `${path}.${field}`, message: "expected string array" });
|
|
2039
|
+
return null;
|
|
2040
|
+
}
|
|
2041
|
+
const out = [];
|
|
2042
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
2043
|
+
const entry = value[index];
|
|
2044
|
+
if (typeof entry !== "string" || entry.trim().length === 0) {
|
|
2045
|
+
issues.push({ path: `${path}.${field}[${index}]`, message: "expected non-empty string" });
|
|
2046
|
+
continue;
|
|
2047
|
+
}
|
|
2048
|
+
out.push(entry);
|
|
2049
|
+
}
|
|
2050
|
+
return out;
|
|
2051
|
+
}
|
|
2052
|
+
function readPositiveInteger(record2, path, field, issues) {
|
|
2053
|
+
const value = record2[field];
|
|
2054
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
2055
|
+
issues.push({ path: `${path}.${field}`, message: "expected positive integer" });
|
|
2056
|
+
return null;
|
|
2057
|
+
}
|
|
2058
|
+
return value;
|
|
2059
|
+
}
|
|
2060
|
+
function isRecord3(value) {
|
|
2061
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2062
|
+
}
|
|
2063
|
+
function sha256Hex(content) {
|
|
2064
|
+
return createHash3("sha256").update(content, "utf8").digest("hex");
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
export {
|
|
2068
|
+
isExecutionRole,
|
|
2069
|
+
deriveExecutionRole,
|
|
2070
|
+
agentRoleFactsResolver,
|
|
2071
|
+
requestExecutionRole,
|
|
2072
|
+
withAttemptRole,
|
|
2073
|
+
gateDeciderAgentId,
|
|
2074
|
+
appliesRecipeResultContract,
|
|
2075
|
+
routeCorrelationFactsForRun,
|
|
2076
|
+
gateRouteCorrelation,
|
|
2077
|
+
ROUTE_POLICY_VERSION,
|
|
2078
|
+
routePriorForLatencyClass,
|
|
2079
|
+
routeObservationFromHistory,
|
|
2080
|
+
estimateRoute,
|
|
2081
|
+
evaluateRouteReadiness,
|
|
2082
|
+
isRouteDecisionAgentSelection,
|
|
2083
|
+
routeCapabilityKey,
|
|
2084
|
+
routeDriftGuard,
|
|
2085
|
+
routeDriftInvalidates,
|
|
2086
|
+
routeCandidateKey,
|
|
2087
|
+
sameRouteIdentity,
|
|
2088
|
+
toRouteCandidate,
|
|
2089
|
+
decideRoute,
|
|
2090
|
+
fixedRouteDecision,
|
|
2091
|
+
routeConstraintValidity,
|
|
2092
|
+
evaluateRouteDecision,
|
|
2093
|
+
isRoutingIntent,
|
|
2094
|
+
parseRoutingIntent,
|
|
2095
|
+
defaultRoutingIntent,
|
|
2096
|
+
routingIntentRejection,
|
|
2097
|
+
explainRouteDecision,
|
|
2098
|
+
withReceiptIntegrity,
|
|
2099
|
+
isReceiptIntegrity,
|
|
2100
|
+
verifyReceiptIntegrity,
|
|
2101
|
+
redactArtifactForStorage,
|
|
2102
|
+
ZERO_EVAL_HARNESS_METRICS,
|
|
2103
|
+
sumEvalHarnessMetrics,
|
|
2104
|
+
createEvalId,
|
|
2105
|
+
writeEvalArtifact,
|
|
2106
|
+
loadEvalArtifact,
|
|
2107
|
+
writeEvalArtifactV4,
|
|
2108
|
+
loadEvalArtifactV4,
|
|
2109
|
+
parseEvalArtifactV4,
|
|
2110
|
+
evalClioProvenance,
|
|
2111
|
+
evalEnvironmentProvenance,
|
|
2112
|
+
loadEvalTaskFile,
|
|
2113
|
+
EvalTaskFileError
|
|
2114
|
+
};
|
|
2115
|
+
//# sourceMappingURL=chunk-7BHLZB3A.js.map
|