@tangle-network/agent-runtime 0.337.1 → 0.338.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{activation-DMehb166.d.ts → activation-CsbNax2X.d.ts} +92 -4
- package/dist/activation-kbkAuSmY.js +889 -0
- package/dist/activation-kbkAuSmY.js.map +1 -0
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +3 -6
- package/dist/agent.js.map +1 -1
- package/dist/{bundle-BPGjESee.js → bundle-DM60AI2B.js} +2 -2
- package/dist/{bundle-BPGjESee.js.map → bundle-DM60AI2B.js.map} +1 -1
- package/dist/candidate-execution/index.d.ts +2 -2
- package/dist/candidate-execution/index.js +4 -5
- package/dist/candidate-execution/index.js.map +1 -1
- package/dist/content-digest-sBTLujlK.js +9 -0
- package/dist/content-digest-sBTLujlK.js.map +1 -0
- package/dist/{delegate-aC14Cww3.js → delegate-Se1YZKMb.js} +2 -2
- package/dist/{delegate-aC14Cww3.js.map → delegate-Se1YZKMb.js.map} +1 -1
- package/dist/{coordination-driver-Dg9a6qsm.js → driver-control-acknowledgers-T2a1Ouay.js} +4514 -4440
- package/dist/driver-control-acknowledgers-T2a1Ouay.js.map +1 -0
- package/dist/durable.d.ts +1 -1
- package/dist/durable.js +223 -227
- package/dist/durable.js.map +1 -1
- package/dist/{graph-Cfx711ES.js → graph-CY0wH838.js} +65 -18
- package/dist/graph-CY0wH838.js.map +1 -0
- package/dist/{graph-registry-DAI6KNXB.js → graph-registry-By0tUj4S.js} +16 -15
- package/dist/graph-registry-By0tUj4S.js.map +1 -0
- package/dist/{improvement-cycle-DHKcb_YW.js → improve-CWGBT13K.js} +75 -1220
- package/dist/improve-CWGBT13K.js.map +1 -0
- package/dist/index.d.ts +6 -6
- package/dist/index.js +10 -13
- package/dist/index.js.map +1 -1
- package/dist/{integrations-DopfCC2a.d.ts → integrations-6xttbPi9.d.ts} +3 -3
- package/dist/integrations-dFi-ALk7.js.map +1 -1
- package/dist/intelligence.d.ts +7 -94
- package/dist/intelligence.js +505 -13
- package/dist/intelligence.js.map +1 -1
- package/dist/kernel.d.ts +2 -2
- package/dist/kernel.js +13 -12
- package/dist/{loop-runner-bin-B86AiPBf.d.ts → loop-runner-bin-CFnrAHpl.d.ts} +2 -2
- package/dist/{loop-runner-bin-BSKBNC38.js → loop-runner-bin-CdPlhy9g.js} +3 -3
- package/dist/{loop-runner-bin-BSKBNC38.js.map → loop-runner-bin-CdPlhy9g.js.map} +1 -1
- package/dist/loop-runner-bin.d.ts +1 -1
- package/dist/loop-runner-bin.js +1 -1
- package/dist/mcp/bin.js +3 -3
- package/dist/mcp/bin.js.map +1 -1
- package/dist/mcp/index.d.ts +7 -3
- package/dist/mcp/index.js +6 -8
- package/dist/mcp/index.js.map +1 -1
- package/dist/{openai-tools-DHO2QS1q.d.ts → openai-tools-zh2I_uD7.d.ts} +2 -2
- package/dist/platform.d.ts +7 -2
- package/dist/platform.js +16 -8
- package/dist/platform.js.map +1 -1
- package/dist/{prepare-CER-ZkPp.js → prepare-Bt0IouPq.js} +1241 -586
- package/dist/prepare-Bt0IouPq.js.map +1 -0
- package/dist/{profile-gNUsHNIJ.js → profile-Do-5xO_D.js} +89 -3
- package/dist/profile-Do-5xO_D.js.map +1 -0
- package/dist/profiles.d.ts +1 -1
- package/dist/profiles.js +1 -3
- package/dist/profiles.js.map +1 -1
- package/dist/{protected-model-port-BhpQRTw-.d.ts → protected-model-port-DGxQ8_YM.d.ts} +3 -3
- package/dist/{provision-supervisor-72YFf-i_.js → provision-supervisor-0jMufp4h.js} +12 -9
- package/dist/provision-supervisor-0jMufp4h.js.map +1 -0
- package/dist/record-value-B6bwJxLa.js +12 -0
- package/dist/record-value-B6bwJxLa.js.map +1 -0
- package/dist/{run-layout-Cu8H7aeh.js → run-cancellation-BoW5Sg9Y.js} +174 -95
- package/dist/run-cancellation-BoW5Sg9Y.js.map +1 -0
- package/dist/{runtime-qFLOq1R9.js → runtime-D37zRSs7.js} +432 -684
- package/dist/runtime-D37zRSs7.js.map +1 -0
- package/dist/{sandbox-profile-compat-QTkLe1Z6.js → sandbox-profile-compat-xSoVtdWw.js} +690 -697
- package/dist/sandbox-profile-compat-xSoVtdWw.js.map +1 -0
- package/dist/search-executor-DmgUm5p7.js +1916 -0
- package/dist/search-executor-DmgUm5p7.js.map +1 -0
- package/dist/{server-_FldJ9Cl.js → server-DQHJszfk.js} +4 -4
- package/dist/server-DQHJszfk.js.map +1 -0
- package/dist/{search-executor-CGxvizHh.js → structural-rollout-DAvIE8mw.js} +2262 -3984
- package/dist/structural-rollout-DAvIE8mw.js.map +1 -0
- package/dist/{supervise-H4BTN6uA.js → supervise-WuPAwfPh.js} +7793 -5338
- package/dist/supervise-WuPAwfPh.js.map +1 -0
- package/dist/{supervisor-C27ne4yv.js → supervisor-CPG1I9UN.js} +27379 -27899
- package/dist/supervisor-CPG1I9UN.js.map +1 -0
- package/dist/testing.d.ts +226 -1
- package/dist/testing.js +13 -14
- package/dist/testing.js.map +1 -1
- package/dist/{top-app-BaMw1W2z.js → top-app-C55Wa7uy.js} +339 -337
- package/dist/top-app-C55Wa7uy.js.map +1 -0
- package/dist/tui/bin.js +1 -1
- package/dist/tui/index.d.ts +17 -15
- package/dist/tui/index.js +2 -2
- package/dist/{types-CupCBY-j.d.ts → types-CrNWif8-.d.ts} +17655 -17406
- package/dist/{workspace-streams-JLjh5DC6.d.ts → workspace-streams-BZZds1Fw.d.ts} +18 -16
- package/package.json +4 -4
- package/skills/agent-graphs/SKILL.md +10 -3
- package/skills/agent-graphs/references/authoring.md +17 -0
- package/skills/profile-authoring/SKILL.md +3 -3
- package/skills/supervise/SKILL.md +2 -2
- package/dist/activation-BgWVlVu2.js +0 -234
- package/dist/activation-BgWVlVu2.js.map +0 -1
- package/dist/coordination-driver-Dg9a6qsm.js.map +0 -1
- package/dist/graph-Cfx711ES.js.map +0 -1
- package/dist/graph-registry-DAI6KNXB.js.map +0 -1
- package/dist/improvement-cycle-DHKcb_YW.js.map +0 -1
- package/dist/prepare-CER-ZkPp.js.map +0 -1
- package/dist/profile-gNUsHNIJ.js.map +0 -1
- package/dist/protected-model-port-BuRsviSL.js +0 -766
- package/dist/protected-model-port-BuRsviSL.js.map +0 -1
- package/dist/provision-supervisor-72YFf-i_.js.map +0 -1
- package/dist/run-layout-Cu8H7aeh.js.map +0 -1
- package/dist/runtime-qFLOq1R9.js.map +0 -1
- package/dist/sandbox-profile-compat-QTkLe1Z6.js.map +0 -1
- package/dist/search-executor-CGxvizHh.js.map +0 -1
- package/dist/server-_FldJ9Cl.js.map +0 -1
- package/dist/supervise-H4BTN6uA.js.map +0 -1
- package/dist/supervisor-C27ne4yv.js.map +0 -1
- package/dist/top-app-BaMw1W2z.js.map +0 -1
|
@@ -1,14 +1,1185 @@
|
|
|
1
|
-
import { A as verifyMaterializedWorkspace, B as omitTopLevelDigest, E as readVerifiedArtifact, F as canonicalCandidateDigest$1, I as canonicalCandidateDocument, L as deepFreezeCandidate, O as verifyBytes, P as canonicalCandidateBytes$1, R as embeddedCandidateArtifact, S as artifactCacheKey, T as readMaterializedWorkspaceFiles, V as sha256Bytes$1,
|
|
1
|
+
import { A as verifyMaterializedWorkspace, B as omitTopLevelDigest, E as readVerifiedArtifact, F as canonicalCandidateDigest$1, I as canonicalCandidateDocument, L as deepFreezeCandidate, O as verifyBytes, P as canonicalCandidateBytes$1, R as embeddedCandidateArtifact, S as artifactCacheKey, T as readMaterializedWorkspaceFiles, U as hasControlCharacter, V as sha256Bytes$1, Z as errorMessage, b as isWellFormedUnicode, d as readCandidateGitHubResource, f as verifyCandidateCode, j as verifyWorkspaceSnapshotArtifacts, k as verifyMaterializedProfileWorkspace, m as verifyTaskOutcomePatch, p as verifyTaskCheckout, v as persistCandidateOutputArtifact, y as assertExactObjectKeys, z as immutableCandidateValue } from "./sandbox-profile-compat-xSoVtdWw.js";
|
|
2
2
|
import { i as redactProtectedValue, n as createProtectedRecordRedactor, r as redactProtectedReason, t as assertNoProtectedBytes } from "./protected-redaction-DFCeo4vg.js";
|
|
3
|
-
import {
|
|
3
|
+
import { _ as parseAgentCandidateProfileActivation, c as buildLaunch, f as exactProfileExecutorFiles, h as modelRoutes, l as candidateEntrypointReceipt, m as materializeAgentCandidateProfilePlan, n as PI_CANDIDATE_SESSION_DIR_ENV, r as absoluteExecutionCwd, s as assertCandidateProfileExecutionSupport, t as PI_CANDIDATE_AGENT_DIR_ENV, u as candidateProfileAgentPaths, x as requirePublicCandidateConfigValue } from "./profile-Do-5xO_D.js";
|
|
4
4
|
import { agentCandidateArtifactRefSchema, agentCandidateBenchmarkResultEvidenceSchema, agentCandidateBenchmarkSuiteSchema, agentCandidateBenchmarkTaskSchema, agentCandidateBundleSchema, agentCandidateContainerSchema, agentCandidateExecutionLimitsSchema, agentCandidateExecutionPlanEvidenceSchema, agentCandidateExecutionPlanMaterialSchema, agentCandidateMaterializationReceiptSchema, agentCandidateModelAccessNetworkSchema, agentCandidateModelSettlementEvidenceSchema, agentCandidateProfilePlanEvidenceSchema, agentCandidateResolvedModelSchema, agentCandidateRunCellSchema, agentCandidateRunReceiptSchema, agentCandidateTaskOutcomeEvidenceSchema, agentCandidateTaskOutcomeSpecSchema, agentCandidateTerminationSchema, agentCandidateWorkspaceManifestMaterialSchema, agentCandidateWorkspaceSnapshotEvidenceSchema, sha256DigestSchema } from "@tangle-network/agent-interface";
|
|
5
5
|
import { REDACTION_VERSION, isLlmSpan } from "@tangle-network/agent-eval";
|
|
6
6
|
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
7
7
|
import { isAbsolute, join, posix, relative, resolve } from "node:path";
|
|
8
8
|
import { lstat, mkdtemp, readFile, readdir, rm } from "node:fs/promises";
|
|
9
9
|
import { applyAgentCandidateWorkspacePlan } from "@tangle-network/agent-profile-materialize";
|
|
10
|
+
import { isDeepStrictEqual } from "node:util";
|
|
10
11
|
import { tmpdir } from "node:os";
|
|
11
12
|
import { hashKnowledgeBase } from "@tangle-network/agent-knowledge";
|
|
13
|
+
//#region src/candidate-execution/execution-roots.ts
|
|
14
|
+
/** Validate the canonical identity and isolation boundary for execution roots. */
|
|
15
|
+
function assertAgentCandidateExecutionRoots(roots) {
|
|
16
|
+
const entries = [
|
|
17
|
+
["task", roots.taskRoot],
|
|
18
|
+
["candidate", roots.candidateRoot],
|
|
19
|
+
["profile", roots.profileRoot]
|
|
20
|
+
];
|
|
21
|
+
for (const [name, root] of entries) {
|
|
22
|
+
if (root === void 0) continue;
|
|
23
|
+
if (!posix.isAbsolute(root) || posix.normalize(root) !== root) throw new Error(`execution ${name} root must be a canonical absolute path`);
|
|
24
|
+
}
|
|
25
|
+
for (let left = 0; left < entries.length; left++) {
|
|
26
|
+
const entry = entries[left];
|
|
27
|
+
if (!entry) continue;
|
|
28
|
+
const [leftName, leftRoot] = entry;
|
|
29
|
+
if (leftRoot === void 0) continue;
|
|
30
|
+
for (let right = left + 1; right < entries.length; right++) {
|
|
31
|
+
const rightEntry = entries[right];
|
|
32
|
+
if (!rightEntry) continue;
|
|
33
|
+
const [rightName, rightRoot] = rightEntry;
|
|
34
|
+
if (rightRoot !== void 0 && executionPathsOverlap(leftRoot, rightRoot)) throw new Error(`execution ${leftName} and ${rightName} roots must be distinct and non-overlapping`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function executionPathsOverlap(left, right) {
|
|
39
|
+
const a = posix.normalize(left);
|
|
40
|
+
const b = posix.normalize(right);
|
|
41
|
+
return a === b || b.startsWith(a === "/" ? "/" : `${a}/`) || a.startsWith(b === "/" ? "/" : `${b}/`);
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/candidate-execution/types.ts
|
|
45
|
+
const verifiedCandidateBrand = Symbol("verifiedAgentCandidate");
|
|
46
|
+
const preparedCandidateBrand = Symbol("preparedAgentCandidate");
|
|
47
|
+
const verifiedTaskOutcomeBrand = Symbol("verifiedTaskOutcome");
|
|
48
|
+
/** Protected trace tags that bind a run to one prepared candidate execution. */
|
|
49
|
+
const CANDIDATE_TRACE_TAGS = {
|
|
50
|
+
executionId: "tangle.candidate.execution_id",
|
|
51
|
+
bundleDigest: "tangle.candidate.bundle_digest",
|
|
52
|
+
executionPlanDigest: "tangle.candidate.execution_plan_digest",
|
|
53
|
+
materializationReceiptDigest: "tangle.candidate.materialization_receipt_digest"
|
|
54
|
+
};
|
|
55
|
+
/** Environment keys used to propagate immutable candidate trace identity. */
|
|
56
|
+
const CANDIDATE_TRACE_ENV = {
|
|
57
|
+
executionId: "TANGLE_CANDIDATE_EXECUTION_ID",
|
|
58
|
+
bundleDigest: "TANGLE_CANDIDATE_BUNDLE_DIGEST",
|
|
59
|
+
executionPlanDigest: "TANGLE_CANDIDATE_EXECUTION_PLAN_DIGEST",
|
|
60
|
+
materializationReceiptDigest: "TANGLE_CANDIDATE_MATERIALIZATION_RECEIPT_DIGEST",
|
|
61
|
+
traceRunId: "TANGLE_TRACE_RUN_ID"
|
|
62
|
+
};
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/candidate-execution/verify.ts
|
|
65
|
+
const verifiedCandidateState = /* @__PURE__ */ new WeakMap();
|
|
66
|
+
/** Surfaces admitted by Runtime's verifier before an environment adapter is selected. */
|
|
67
|
+
const AGENT_CANDIDATE_EXECUTION_SUPPORT = Object.freeze({
|
|
68
|
+
outcomes: Object.freeze(["workspace", "output"]),
|
|
69
|
+
code: Object.freeze([
|
|
70
|
+
"disabled",
|
|
71
|
+
"no-op",
|
|
72
|
+
"git-patch"
|
|
73
|
+
]),
|
|
74
|
+
memory: Object.freeze(["disabled", "isolated"]),
|
|
75
|
+
knowledge: true,
|
|
76
|
+
profile: Object.freeze({
|
|
77
|
+
mcpTransports: Object.freeze(["stdio"]),
|
|
78
|
+
remoteMcp: false,
|
|
79
|
+
tools: false,
|
|
80
|
+
permissions: false,
|
|
81
|
+
modes: false,
|
|
82
|
+
confidential: false
|
|
83
|
+
})
|
|
84
|
+
});
|
|
85
|
+
/** Verifies every digest, resource, workspace, and Git object in a candidate bundle. */
|
|
86
|
+
async function verifyAgentCandidateBundle(input, ports) {
|
|
87
|
+
const parsed = agentCandidateBundleSchema.parse(input);
|
|
88
|
+
const withoutDigest = omitTopLevelDigest(parsed);
|
|
89
|
+
const actualDigest = canonicalCandidateDigest$1(withoutDigest);
|
|
90
|
+
if (actualDigest !== parsed.digest) throw new Error(`candidate bundle digest ${parsed.digest} does not match ${actualDigest}`);
|
|
91
|
+
const canonicalBytes = canonicalCandidateBytes$1(withoutDigest);
|
|
92
|
+
verifyBytes(canonicalBytes, parsed.digest, canonicalBytes.byteLength, "candidate bundle");
|
|
93
|
+
assertCandidateProfileExecutionSupport(parsed.profile);
|
|
94
|
+
const artifactBytes = /* @__PURE__ */ new Map();
|
|
95
|
+
const readArtifact = async (artifact) => {
|
|
96
|
+
const key = artifactCacheKey(artifact);
|
|
97
|
+
const existing = artifactBytes.get(key);
|
|
98
|
+
if (existing) return Uint8Array.from(existing);
|
|
99
|
+
const bytes = await readVerifiedArtifact(artifact, ports.artifacts);
|
|
100
|
+
artifactBytes.set(key, Uint8Array.from(bytes));
|
|
101
|
+
return bytes;
|
|
102
|
+
};
|
|
103
|
+
let patchBytes;
|
|
104
|
+
if (parsed.code.kind === "git-patch") patchBytes = await readArtifact(parsed.code.patch.artifact);
|
|
105
|
+
const materializedTree = await verifyCandidateCode(parsed.code, ports.repositories, patchBytes);
|
|
106
|
+
const resourceBytes = /* @__PURE__ */ new Map();
|
|
107
|
+
for (const resource of candidateResources(parsed)) {
|
|
108
|
+
const bytes = resource.kind === "inline" ? Buffer.from(resource.content, "utf8") : await readCandidateGitHubResource(resource, ports.repositories);
|
|
109
|
+
verifyBytes(bytes, resource.sha256, resource.byteLength, `candidate resource ${resource.name ?? (resource.kind === "github" ? resource.path : "<unnamed>")}`);
|
|
110
|
+
resourceBytes.set(resourceKey(resource), Uint8Array.from(bytes));
|
|
111
|
+
resourceBytes.set(resource.sha256, Uint8Array.from(bytes));
|
|
112
|
+
}
|
|
113
|
+
if (parsed.execution.workspace) {
|
|
114
|
+
const workspace = await verifyWorkspaceSnapshotArtifacts(parsed.execution.workspace, ports.artifacts);
|
|
115
|
+
artifactBytes.set(artifactCacheKey(parsed.execution.workspace.manifest), workspace.manifest);
|
|
116
|
+
artifactBytes.set(artifactCacheKey(parsed.execution.workspace.archive), workspace.archive);
|
|
117
|
+
}
|
|
118
|
+
if (parsed.knowledge) {
|
|
119
|
+
const knowledge = await verifyWorkspaceSnapshotArtifacts(parsed.knowledge.snapshot, ports.artifacts);
|
|
120
|
+
artifactBytes.set(artifactCacheKey(parsed.knowledge.snapshot.manifest), knowledge.manifest);
|
|
121
|
+
artifactBytes.set(artifactCacheKey(parsed.knowledge.snapshot.archive), knowledge.archive);
|
|
122
|
+
if (parsed.knowledge.retrievalConfig) await readArtifact(parsed.knowledge.retrievalConfig);
|
|
123
|
+
await readArtifact(parsed.knowledge.evaluation);
|
|
124
|
+
}
|
|
125
|
+
if (parsed.memory.mode === "isolated" && parsed.memory.seed) await readArtifact(parsed.memory.seed);
|
|
126
|
+
deepFreezeCandidate(parsed);
|
|
127
|
+
const verified = Object.freeze({
|
|
128
|
+
bundle: parsed,
|
|
129
|
+
...materializedTree === void 0 ? {} : { materializedTree },
|
|
130
|
+
[verifiedCandidateBrand]: true
|
|
131
|
+
});
|
|
132
|
+
verifiedCandidateState.set(verified, {
|
|
133
|
+
ports,
|
|
134
|
+
artifactBytes,
|
|
135
|
+
resourceBytes
|
|
136
|
+
});
|
|
137
|
+
return verified;
|
|
138
|
+
}
|
|
139
|
+
function getVerifiedCandidateState(candidate) {
|
|
140
|
+
const state = verifiedCandidateState.get(candidate);
|
|
141
|
+
if (!state || candidate[verifiedCandidateBrand] !== true) throw new Error("candidate must come from verifyAgentCandidateBundle");
|
|
142
|
+
return state;
|
|
143
|
+
}
|
|
144
|
+
async function verifiedArtifactBytes(candidate, artifact) {
|
|
145
|
+
const state = getVerifiedCandidateState(candidate);
|
|
146
|
+
const key = artifactCacheKey(artifact);
|
|
147
|
+
const existing = state.artifactBytes.get(key);
|
|
148
|
+
if (existing) return Uint8Array.from(existing);
|
|
149
|
+
const bytes = await readVerifiedArtifact(artifact, state.ports.artifacts);
|
|
150
|
+
state.artifactBytes.set(key, Uint8Array.from(bytes));
|
|
151
|
+
return bytes;
|
|
152
|
+
}
|
|
153
|
+
function verifiedResourceTextByDigest(candidate) {
|
|
154
|
+
const output = /* @__PURE__ */ new Map();
|
|
155
|
+
for (const resource of candidateResources(candidate.bundle)) {
|
|
156
|
+
const bytes = getVerifiedCandidateState(candidate).resourceBytes.get(resource.sha256);
|
|
157
|
+
if (!bytes) throw new Error(`candidate resource digest was not verified: ${resource.sha256}`);
|
|
158
|
+
output.set(resource.sha256, new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
159
|
+
}
|
|
160
|
+
return output;
|
|
161
|
+
}
|
|
162
|
+
function candidateResources(bundle) {
|
|
163
|
+
const resources = bundle.profile.resources;
|
|
164
|
+
if (!resources) return [];
|
|
165
|
+
const output = [];
|
|
166
|
+
for (const mount of resources.files ?? []) output.push(mount.resource);
|
|
167
|
+
output.push(...resources.tools ?? []);
|
|
168
|
+
output.push(...resources.skills ?? []);
|
|
169
|
+
output.push(...resources.agents ?? []);
|
|
170
|
+
output.push(...resources.commands ?? []);
|
|
171
|
+
if (typeof resources.instructions === "object") output.push(resources.instructions);
|
|
172
|
+
return output;
|
|
173
|
+
}
|
|
174
|
+
function resourceKey(resource) {
|
|
175
|
+
return canonicalCandidateDigest$1(resource);
|
|
176
|
+
}
|
|
177
|
+
//#endregion
|
|
178
|
+
//#region src/candidate-execution/knowledge.ts
|
|
179
|
+
/** Environment variable containing the materialized candidate knowledge root. */
|
|
180
|
+
const CANDIDATE_KNOWLEDGE_ROOT_ENV = "TANGLE_CANDIDATE_KNOWLEDGE_ROOT";
|
|
181
|
+
/** Environment variable containing the materialized retrieval configuration path. */
|
|
182
|
+
const CANDIDATE_KNOWLEDGE_RETRIEVAL_CONFIG_ENV = "TANGLE_CANDIDATE_KNOWLEDGE_RETRIEVAL_CONFIG";
|
|
183
|
+
/** Deterministic, signed locations used by every candidate executor. */
|
|
184
|
+
function candidateKnowledgeExecutionPaths(taskRoot, hasRetrievalConfig) {
|
|
185
|
+
if (!posix.isAbsolute(taskRoot) || posix.normalize(taskRoot) !== taskRoot) throw new Error("candidate knowledge requires a normalized absolute task root");
|
|
186
|
+
const parent = posix.join(taskRoot, ".tangle");
|
|
187
|
+
return {
|
|
188
|
+
root: posix.join(parent, "knowledge"),
|
|
189
|
+
...hasRetrievalConfig ? { retrievalConfig: posix.join(parent, "knowledge-retrieval-config.json") } : {}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/** Verify and detach the exact file-backed knowledge admitted by the bundle. */
|
|
193
|
+
async function prepareAgentCandidateKnowledge(candidate, ports, arm) {
|
|
194
|
+
const knowledge = candidate.bundle.knowledge;
|
|
195
|
+
if (!knowledge) return void 0;
|
|
196
|
+
const archive = await verifiedArtifactBytes(candidate, knowledge.snapshot.archive);
|
|
197
|
+
const retrievalConfig = knowledge.retrievalConfig ? await verifiedArtifactBytes(candidate, knowledge.retrievalConfig) : void 0;
|
|
198
|
+
const root = await mkdtemp(join(tmpdir(), "agent-candidate-knowledge-"));
|
|
199
|
+
try {
|
|
200
|
+
await ports.workspaces.materialize({
|
|
201
|
+
role: "knowledge",
|
|
202
|
+
snapshot: knowledge.snapshot,
|
|
203
|
+
archive,
|
|
204
|
+
destination: root
|
|
205
|
+
});
|
|
206
|
+
const expectedHash = arm === "baseline" ? knowledge.candidate.baseHash : knowledge.candidate.candidateHash;
|
|
207
|
+
const actualHash = `sha256:${await hashKnowledgeBase(root, knowledge.stateScope)}`;
|
|
208
|
+
if (actualHash !== expectedHash) throw new Error(`materialized ${arm} knowledge does not match its measured content: expected ${expectedHash}, got ${actualHash}`);
|
|
209
|
+
const files = await readMaterializedWorkspaceFiles(root, knowledge.snapshot.material);
|
|
210
|
+
return Object.freeze({
|
|
211
|
+
candidate: knowledge.candidate,
|
|
212
|
+
...knowledge.stateScope ? { stateScope: Object.freeze({ ...knowledge.stateScope }) } : {},
|
|
213
|
+
snapshot: knowledge.snapshot,
|
|
214
|
+
files: Object.freeze(files.map((file) => Object.freeze({
|
|
215
|
+
path: file.path,
|
|
216
|
+
mode: file.mode,
|
|
217
|
+
bytes: Uint8Array.from(file.bytes)
|
|
218
|
+
}))),
|
|
219
|
+
...retrievalConfig ? { retrievalConfig: Uint8Array.from(retrievalConfig) } : {}
|
|
220
|
+
});
|
|
221
|
+
} finally {
|
|
222
|
+
await rm(root, {
|
|
223
|
+
recursive: true,
|
|
224
|
+
force: true
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
//#endregion
|
|
229
|
+
//#region src/candidate-execution/exact-process-executor.ts
|
|
230
|
+
const DEFAULT_PROVISION_TIMEOUT_MS = 12e4;
|
|
231
|
+
const DEFAULT_RECOVERY_RETENTION_MS = 15 * 6e4;
|
|
232
|
+
/** Adapt one neutral exact-process provider to Runtime's trusted candidate boundary. */
|
|
233
|
+
function exactProcessProviderAsCandidateExecutor(options) {
|
|
234
|
+
return new ExactProcessAgentCandidateExecutor(options);
|
|
235
|
+
}
|
|
236
|
+
var ExactProcessAgentCandidateExecutor = class {
|
|
237
|
+
options;
|
|
238
|
+
states = /* @__PURE__ */ new Map();
|
|
239
|
+
exactProcess;
|
|
240
|
+
resources;
|
|
241
|
+
provisionTimeoutMs;
|
|
242
|
+
recoveryRetentionMs;
|
|
243
|
+
providerOptions;
|
|
244
|
+
capabilitiesPromise;
|
|
245
|
+
constructor(options) {
|
|
246
|
+
this.options = options;
|
|
247
|
+
if (!options.provider.exactProcess) throw new Error(`agent environment provider "${options.provider.name}" does not implement exact processes`);
|
|
248
|
+
this.exactProcess = options.provider.exactProcess;
|
|
249
|
+
this.resources = exactResources(options.resources);
|
|
250
|
+
this.provisionTimeoutMs = positiveInteger(options.provisionTimeoutMs ?? DEFAULT_PROVISION_TIMEOUT_MS, "exact process provision timeout");
|
|
251
|
+
this.recoveryRetentionMs = positiveInteger(options.recoveryRetentionMs ?? DEFAULT_RECOVERY_RETENTION_MS, "exact process recovery retention");
|
|
252
|
+
this.providerOptions = options.providerOptions ? immutableCandidateValue(options.providerOptions) : void 0;
|
|
253
|
+
}
|
|
254
|
+
async execute(request, context) {
|
|
255
|
+
assertSupportedRequest(request);
|
|
256
|
+
const outcome = request.benchmark.task.outcome;
|
|
257
|
+
if (outcome.kind !== "output") throw new Error("exact process executor requires an output task");
|
|
258
|
+
const network = request.executionPlan.value.material.model.access.network;
|
|
259
|
+
const egress = network.mode === "disabled" ? { mode: "blocked" } : {
|
|
260
|
+
mode: "strict",
|
|
261
|
+
allowDomains: [...network.domains]
|
|
262
|
+
};
|
|
263
|
+
await this.assertCapability(egress.mode);
|
|
264
|
+
context.signal.throwIfAborted();
|
|
265
|
+
const key = executionKey(request.executionId, request.executionPlan.value.digest);
|
|
266
|
+
if (this.states.has(key)) throw new Error("candidate execution environment is already active");
|
|
267
|
+
const image = exactImage(request.executionPlan.value.material.container.image, request.executionPlan.value.material.container.manifestDigest);
|
|
268
|
+
const maxLifetimeMs = environmentLifetimeMs(request.hardLimits.timeoutMs, this.recoveryRetentionMs);
|
|
269
|
+
const createInputDigest = canonicalCandidateDigest$1(immutableCandidateValue({
|
|
270
|
+
kind: "agent-candidate-exact-process-create",
|
|
271
|
+
provider: this.options.provider.name,
|
|
272
|
+
executionId: request.executionId,
|
|
273
|
+
executionPlanDigest: request.executionPlan.value.digest,
|
|
274
|
+
image,
|
|
275
|
+
egress,
|
|
276
|
+
maxLifetimeMs,
|
|
277
|
+
provisionTimeoutMs: this.provisionTimeoutMs,
|
|
278
|
+
resources: this.resources,
|
|
279
|
+
output: {
|
|
280
|
+
mediaType: outcome.mediaType,
|
|
281
|
+
maxBytes: outcome.maxBytes
|
|
282
|
+
},
|
|
283
|
+
...this.providerOptions ? { providerOptions: this.providerOptions } : {}
|
|
284
|
+
}));
|
|
285
|
+
const metadata = immutableCandidateValue({
|
|
286
|
+
kind: "agent-candidate-execution",
|
|
287
|
+
provider: this.options.provider.name,
|
|
288
|
+
executionId: request.executionId,
|
|
289
|
+
executionPlanDigest: request.executionPlan.value.digest,
|
|
290
|
+
createInputDigest,
|
|
291
|
+
outputMediaType: outcome.mediaType,
|
|
292
|
+
outputMaxBytes: outcome.maxBytes
|
|
293
|
+
});
|
|
294
|
+
const environment = await this.exactProcess.create({
|
|
295
|
+
image,
|
|
296
|
+
egress,
|
|
297
|
+
maxLifetimeMs,
|
|
298
|
+
provisionTimeoutMs: this.provisionTimeoutMs,
|
|
299
|
+
resources: this.resources,
|
|
300
|
+
metadata,
|
|
301
|
+
idempotencyKey: executionIdempotencyKey(request.executionId, request.executionPlan.value.digest),
|
|
302
|
+
signal: context.signal,
|
|
303
|
+
...this.providerOptions ? { providerOptions: this.providerOptions } : {}
|
|
304
|
+
});
|
|
305
|
+
try {
|
|
306
|
+
assertExecutionEnvironment(environment, metadata, this.options.provider.name);
|
|
307
|
+
} catch (error) {
|
|
308
|
+
try {
|
|
309
|
+
await environment.destroy();
|
|
310
|
+
} catch (cleanupError) {
|
|
311
|
+
throw new AggregateError([error, cleanupError], "exact process validation and cleanup both failed");
|
|
312
|
+
}
|
|
313
|
+
throw error;
|
|
314
|
+
}
|
|
315
|
+
const state = { environment };
|
|
316
|
+
this.states.set(key, state);
|
|
317
|
+
if ((await environment.process.list()).length !== 0) throw new Error("fresh candidate environment already contains a process");
|
|
318
|
+
await materializeRequest(environment, request, context.signal);
|
|
319
|
+
const launch = exactLaunch(request);
|
|
320
|
+
const startedAt = Date.now();
|
|
321
|
+
state.trace = {
|
|
322
|
+
runId: request.trace.runId,
|
|
323
|
+
scenarioId: request.benchmark.task.scenario.id,
|
|
324
|
+
startedAt,
|
|
325
|
+
deadlineAtMs: context.deadlineAtMs,
|
|
326
|
+
timeoutMs: request.hardLimits.timeoutMs,
|
|
327
|
+
tags: {
|
|
328
|
+
...request.trace.tags,
|
|
329
|
+
environmentId: environment.id,
|
|
330
|
+
environmentProvider: environment.provider
|
|
331
|
+
},
|
|
332
|
+
written: false
|
|
333
|
+
};
|
|
334
|
+
const process = await environment.process.spawn(launch, { signal: context.signal });
|
|
335
|
+
const outputPromise = collectOutput(process, outcome.maxBytes);
|
|
336
|
+
outputPromise.catch(() => void 0);
|
|
337
|
+
let cancellation;
|
|
338
|
+
let processExited = false;
|
|
339
|
+
const cancel = () => {
|
|
340
|
+
if (!processExited) cancellation ??= stopProcess(process);
|
|
341
|
+
};
|
|
342
|
+
context.signal.addEventListener("abort", cancel, { once: true });
|
|
343
|
+
if (context.signal.aborted) cancel();
|
|
344
|
+
try {
|
|
345
|
+
const termination = await process.wait();
|
|
346
|
+
processExited = true;
|
|
347
|
+
context.signal.removeEventListener("abort", cancel);
|
|
348
|
+
if (cancellation) await cancellation;
|
|
349
|
+
const status = await process.status();
|
|
350
|
+
assertTerminalStatus(status, termination);
|
|
351
|
+
state.output = await awaitOutput(outputPromise, context.signal, context.deadlineAtMs);
|
|
352
|
+
state.termination = termination;
|
|
353
|
+
await appendExactProcessTrace(state, context.traceStore, status);
|
|
354
|
+
return {
|
|
355
|
+
executionId: request.executionId,
|
|
356
|
+
termination
|
|
357
|
+
};
|
|
358
|
+
} finally {
|
|
359
|
+
context.signal.removeEventListener("abort", cancel);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
async stop(request, context) {
|
|
363
|
+
await this.assertCapability();
|
|
364
|
+
context.signal.throwIfAborted();
|
|
365
|
+
const environment = await this.resolve(request, true);
|
|
366
|
+
if (!environment) return { stopped: true };
|
|
367
|
+
let statuses = await environment.process.list();
|
|
368
|
+
if (statuses.length > 1) throw new Error("candidate exact process environment contains more than one process");
|
|
369
|
+
const status = statuses[0];
|
|
370
|
+
if (status?.running) {
|
|
371
|
+
const process = await environment.process.get(status.pid);
|
|
372
|
+
if (!process) throw new Error("candidate environment lost its running process handle");
|
|
373
|
+
await stopProcess(process);
|
|
374
|
+
}
|
|
375
|
+
context.signal.throwIfAborted();
|
|
376
|
+
statuses = await environment.process.list();
|
|
377
|
+
if (statuses.some((entry) => entry.running)) throw new Error("candidate exact process termination is not proven");
|
|
378
|
+
const finalStatus = statuses[0];
|
|
379
|
+
if (finalStatus) assertTerminalStatus(finalStatus);
|
|
380
|
+
const state = this.states.get(executionKey(request.executionId, request.executionPlanDigest));
|
|
381
|
+
if (state) {
|
|
382
|
+
state.termination = finalStatus?.termination ?? state.termination;
|
|
383
|
+
if (context.reason === "timeout" && state.trace) state.runtimeTermination = {
|
|
384
|
+
kind: "timeout",
|
|
385
|
+
timeoutMs: state.trace.timeoutMs
|
|
386
|
+
};
|
|
387
|
+
await appendExactProcessTrace(state, context.traceStore, finalStatus);
|
|
388
|
+
}
|
|
389
|
+
return { stopped: true };
|
|
390
|
+
}
|
|
391
|
+
async capture(request, context) {
|
|
392
|
+
await this.assertCapability();
|
|
393
|
+
context.signal.throwIfAborted();
|
|
394
|
+
const environment = await this.resolve(request, false);
|
|
395
|
+
const statuses = await environment.process.list();
|
|
396
|
+
const status = statuses[0];
|
|
397
|
+
if (statuses.length !== 1 || !status || status.running) throw new Error("candidate environment must contain one stopped process before capture");
|
|
398
|
+
assertTerminalStatus(status);
|
|
399
|
+
const process = await environment.process.get(status.pid);
|
|
400
|
+
if (!process) throw new Error("candidate environment lost its captured process handle");
|
|
401
|
+
assertTerminalStatus(await process.status(), status.termination);
|
|
402
|
+
const state = this.states.get(executionKey(request.executionId, request.executionPlanDigest));
|
|
403
|
+
const output = state?.output ?? await awaitOutput(collectOutput(process, outputSpec(environment).maxBytes), context.signal, Date.now() + this.provisionTimeoutMs);
|
|
404
|
+
context.signal.throwIfAborted();
|
|
405
|
+
const evidence = canonicalCandidateBytes$1({
|
|
406
|
+
kind: "agent-candidate-exact-process-capture",
|
|
407
|
+
provider: environment.provider,
|
|
408
|
+
environmentId: environment.id,
|
|
409
|
+
executionId: request.executionId,
|
|
410
|
+
executionPlanDigest: request.executionPlanDigest,
|
|
411
|
+
createInputDigest: environment.metadata?.createInputDigest,
|
|
412
|
+
process: {
|
|
413
|
+
pid: status.pid,
|
|
414
|
+
exitCode: status.exitCode,
|
|
415
|
+
...status.exitSignal ? { exitSignal: status.exitSignal } : {},
|
|
416
|
+
termination: status.termination
|
|
417
|
+
},
|
|
418
|
+
...state?.runtimeTermination ? { runtimeTermination: state.runtimeTermination } : {},
|
|
419
|
+
output: {
|
|
420
|
+
sha256: sha256Bytes$1(output),
|
|
421
|
+
byteLength: output.byteLength
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
return {
|
|
425
|
+
taskOutcome: {
|
|
426
|
+
kind: "output",
|
|
427
|
+
bytes: output
|
|
428
|
+
},
|
|
429
|
+
evidence
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
async dispose(request, context) {
|
|
433
|
+
await this.assertCapability();
|
|
434
|
+
context.signal.throwIfAborted();
|
|
435
|
+
const environment = await this.resolve(request, true);
|
|
436
|
+
if (!environment) return { disposed: true };
|
|
437
|
+
let destroyError;
|
|
438
|
+
try {
|
|
439
|
+
await environment.destroy();
|
|
440
|
+
} catch (error) {
|
|
441
|
+
destroyError = error;
|
|
442
|
+
}
|
|
443
|
+
if (await this.exactProcess.get(environment.id)) throw new Error("exact process environment disposal is not proven", { cause: destroyError });
|
|
444
|
+
context.signal.throwIfAborted();
|
|
445
|
+
this.states.delete(executionKey(request.executionId, request.executionPlanDigest));
|
|
446
|
+
return { disposed: true };
|
|
447
|
+
}
|
|
448
|
+
async assertCapability(mode) {
|
|
449
|
+
const pending = this.capabilitiesPromise ?? Promise.resolve(this.options.provider.capabilities());
|
|
450
|
+
this.capabilitiesPromise = pending;
|
|
451
|
+
let capabilities;
|
|
452
|
+
try {
|
|
453
|
+
capabilities = await pending;
|
|
454
|
+
} catch (error) {
|
|
455
|
+
if (this.capabilitiesPromise === pending) this.capabilitiesPromise = void 0;
|
|
456
|
+
throw error;
|
|
457
|
+
}
|
|
458
|
+
const exact = capabilities.exactProcess;
|
|
459
|
+
if (!exact) throw new Error(`agent environment provider "${this.options.provider.name}" does not declare exact process support`);
|
|
460
|
+
if (mode && !exact.egress.includes(mode)) throw new Error(`agent environment provider "${this.options.provider.name}" does not declare ${mode} exact egress`);
|
|
461
|
+
}
|
|
462
|
+
async resolve(request, missingIsDeleted) {
|
|
463
|
+
const key = executionKey(request.executionId, request.executionPlanDigest);
|
|
464
|
+
const active = this.states.get(key)?.environment;
|
|
465
|
+
if (active) return active;
|
|
466
|
+
const metadata = {
|
|
467
|
+
kind: "agent-candidate-execution",
|
|
468
|
+
provider: this.options.provider.name,
|
|
469
|
+
executionId: request.executionId,
|
|
470
|
+
executionPlanDigest: request.executionPlanDigest
|
|
471
|
+
};
|
|
472
|
+
const matches = (await this.exactProcess.list({
|
|
473
|
+
metadata,
|
|
474
|
+
...this.providerOptions ? { providerOptions: this.providerOptions } : {}
|
|
475
|
+
})).filter((environment) => environment.provider === this.options.provider.name && Object.entries(metadata).every(([name, value]) => isDeepStrictEqual(environment.metadata?.[name], value)));
|
|
476
|
+
if (matches.length > 1) throw new Error("multiple exact process environments match one candidate execution");
|
|
477
|
+
const environment = matches[0];
|
|
478
|
+
if (!environment) {
|
|
479
|
+
if (missingIsDeleted) return void 0;
|
|
480
|
+
throw new Error("candidate exact process evidence is unavailable");
|
|
481
|
+
}
|
|
482
|
+
if (!isSha256Digest$1(environment.metadata?.createInputDigest)) throw new Error("candidate exact process create-input digest is unavailable");
|
|
483
|
+
this.states.set(key, { environment });
|
|
484
|
+
return environment;
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
async function appendExactProcessTrace(state, traceStore, status) {
|
|
488
|
+
const trace = state.trace;
|
|
489
|
+
if (!trace || trace.written) return;
|
|
490
|
+
const endedAt = Math.max(trace.startedAt, Math.min(Date.now(), trace.deadlineAtMs));
|
|
491
|
+
const termination = state.runtimeTermination ?? state.termination ?? status?.termination;
|
|
492
|
+
await traceStore.appendRun({
|
|
493
|
+
runId: trace.runId,
|
|
494
|
+
scenarioId: trace.scenarioId,
|
|
495
|
+
startedAt: trace.startedAt,
|
|
496
|
+
endedAt,
|
|
497
|
+
status: termination?.kind === "exit" && termination.exitCode === 0 ? "completed" : "failed",
|
|
498
|
+
tags: trace.tags
|
|
499
|
+
});
|
|
500
|
+
trace.written = true;
|
|
501
|
+
}
|
|
502
|
+
function assertSupportedRequest(request) {
|
|
503
|
+
if (request.benchmark.task.outcome.kind !== "output") throw new Error("exact process candidate executor does not support workspace outcomes; use Pier");
|
|
504
|
+
if (request.executionPlan.value.material.codeKind !== "disabled" || request.inputs.candidate) throw new Error("exact process candidate executor does not support code workspaces; use Pier");
|
|
505
|
+
if (request.memory.mode !== "disabled") throw new Error("exact process candidate executor does not support isolated memory");
|
|
506
|
+
const mediaType = request.benchmark.task.outcome.mediaType.toLowerCase();
|
|
507
|
+
if (!(mediaType.startsWith("text/") || mediaType === "application/json" || mediaType.endsWith("+json"))) throw new Error("exact process candidate executor supports only UTF-8 text and JSON outputs");
|
|
508
|
+
if (!posix.isAbsolute(request.launch.executable) && !request.launch.env.PATH?.trim()) throw new Error("exact process candidate executable must be absolute or declare a signed PATH");
|
|
509
|
+
if (!request.roots) throw new Error("candidate execution roots are missing");
|
|
510
|
+
assertAgentCandidateExecutionRoots(request.roots);
|
|
511
|
+
}
|
|
512
|
+
async function materializeRequest(environment, request, signal) {
|
|
513
|
+
assertAgentCandidateExecutionRoots(request.roots);
|
|
514
|
+
const knowledgePaths = assertKnowledgeExecutionBinding(request);
|
|
515
|
+
for (const file of request.inputs.task.files) await environment.writeFile(beneath(request.roots.taskRoot, file.path), file.bytes, {
|
|
516
|
+
mode: file.mode,
|
|
517
|
+
signal
|
|
518
|
+
});
|
|
519
|
+
for (const file of request.inputs.profile.files) await environment.writeFile(profileFileExecutionPath(request, file), file.bytes, {
|
|
520
|
+
mode: file.mode,
|
|
521
|
+
signal
|
|
522
|
+
});
|
|
523
|
+
if (request.knowledge && knowledgePaths) {
|
|
524
|
+
for (const file of request.knowledge.files) await environment.writeFile(beneath(knowledgePaths.root, file.path), file.bytes, {
|
|
525
|
+
mode: file.mode,
|
|
526
|
+
signal
|
|
527
|
+
});
|
|
528
|
+
if (request.knowledge.retrievalConfig && knowledgePaths.retrievalConfig) await environment.writeFile(knowledgePaths.retrievalConfig, request.knowledge.retrievalConfig, {
|
|
529
|
+
mode: 420,
|
|
530
|
+
signal
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
if (request.instruction.delivery.kind === "utf8-file") await environment.writeFile(request.instruction.delivery.path, request.instruction.bytes, {
|
|
534
|
+
mode: 420,
|
|
535
|
+
signal
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
function assertKnowledgeExecutionBinding(request) {
|
|
539
|
+
const knowledge = request.knowledge;
|
|
540
|
+
if (!knowledge) {
|
|
541
|
+
if (request.launch.env["TANGLE_CANDIDATE_KNOWLEDGE_ROOT"] !== void 0 || request.launch.env["TANGLE_CANDIDATE_KNOWLEDGE_RETRIEVAL_CONFIG"] !== void 0) throw new Error("candidate launch declares knowledge paths without verified knowledge");
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
const paths = candidateKnowledgeExecutionPaths(request.roots.taskRoot, knowledge.retrievalConfig !== void 0);
|
|
545
|
+
if (request.launch.env["TANGLE_CANDIDATE_KNOWLEDGE_ROOT"] !== paths.root || request.launch.env["TANGLE_CANDIDATE_KNOWLEDGE_RETRIEVAL_CONFIG"] !== paths.retrievalConfig) throw new Error("candidate knowledge paths do not match the signed launch environment");
|
|
546
|
+
const reserved = [paths.root, paths.retrievalConfig].filter((value) => value !== void 0);
|
|
547
|
+
if ([
|
|
548
|
+
...request.inputs.task.files.map((file) => beneath(request.roots.taskRoot, file.path)),
|
|
549
|
+
...request.inputs.profile.files.map((file) => profileFileExecutionPath(request, file)),
|
|
550
|
+
...request.instruction.delivery.kind === "utf8-file" ? [request.instruction.delivery.path] : []
|
|
551
|
+
].some((path) => reserved.some((reservedPath) => path === reservedPath || path.startsWith(`${reservedPath}/`) || reservedPath.startsWith(`${path}/`)))) throw new Error("candidate knowledge paths overlap other execution inputs");
|
|
552
|
+
return paths;
|
|
553
|
+
}
|
|
554
|
+
function profileFileExecutionPath(request, file) {
|
|
555
|
+
const workspaceRoot = request.executionPlan.value.material.profile.targetWorkspace === "task" ? request.roots.taskRoot : request.roots.candidateRoot;
|
|
556
|
+
if (!workspaceRoot) throw new Error("candidate profile targets a missing workspace");
|
|
557
|
+
if (file.root !== "agent") return beneath(workspaceRoot, file.path);
|
|
558
|
+
return beneath(profileAgentDirectory(request), file.path);
|
|
559
|
+
}
|
|
560
|
+
function profileAgentDirectory(request) {
|
|
561
|
+
const paths = candidateProfileAgentPaths(request.profilePlan.value.material, request.roots.profileRoot);
|
|
562
|
+
if (!paths) throw new Error("candidate profile agent-root file is missing its private root");
|
|
563
|
+
if (request.launch.env["PI_CODING_AGENT_DIR"] !== paths.agentDir || request.launch.env["PI_CODING_AGENT_SESSION_DIR"] !== paths.sessionDir) throw new Error("candidate Pi agent-root files do not match the signed launch environment");
|
|
564
|
+
return paths.agentDir;
|
|
565
|
+
}
|
|
566
|
+
function exactLaunch(request) {
|
|
567
|
+
const instruction = new TextDecoder("utf-8", { fatal: true }).decode(request.instruction.bytes);
|
|
568
|
+
const base = {
|
|
569
|
+
executable: request.launch.executable,
|
|
570
|
+
args: request.launch.args,
|
|
571
|
+
cwd: request.launch.cwd,
|
|
572
|
+
env: request.launch.env,
|
|
573
|
+
timeoutMs: 0
|
|
574
|
+
};
|
|
575
|
+
switch (request.instruction.delivery.kind) {
|
|
576
|
+
case "argv-append": return {
|
|
577
|
+
...base,
|
|
578
|
+
args: [...base.args, instruction]
|
|
579
|
+
};
|
|
580
|
+
case "stdin-utf8": return {
|
|
581
|
+
...base,
|
|
582
|
+
stdin: instruction
|
|
583
|
+
};
|
|
584
|
+
case "utf8-file": return base;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
async function collectOutput(process, maxBytes) {
|
|
588
|
+
const chunks = [];
|
|
589
|
+
let byteLength = 0;
|
|
590
|
+
for await (const chunk of process.stdout()) {
|
|
591
|
+
const bytes = Buffer.from(chunk, "utf8");
|
|
592
|
+
byteLength += bytes.byteLength;
|
|
593
|
+
if (byteLength > maxBytes) {
|
|
594
|
+
let stopError;
|
|
595
|
+
try {
|
|
596
|
+
await stopProcess(process);
|
|
597
|
+
} catch (error) {
|
|
598
|
+
stopError = error;
|
|
599
|
+
}
|
|
600
|
+
throw new Error(`candidate output exceeds its ${maxBytes}-byte maximum`, { cause: stopError });
|
|
601
|
+
}
|
|
602
|
+
chunks.push(bytes);
|
|
603
|
+
}
|
|
604
|
+
return Uint8Array.from(Buffer.concat(chunks, byteLength));
|
|
605
|
+
}
|
|
606
|
+
async function awaitOutput(output, signal, deadlineAtMs) {
|
|
607
|
+
signal.throwIfAborted();
|
|
608
|
+
const remainingMs = deadlineAtMs - Date.now();
|
|
609
|
+
if (remainingMs <= 0) throw new Error("candidate output deadline expired");
|
|
610
|
+
let timer;
|
|
611
|
+
let onAbort;
|
|
612
|
+
try {
|
|
613
|
+
return await Promise.race([output, new Promise((_resolve, reject) => {
|
|
614
|
+
onAbort = () => reject(signal.reason ?? /* @__PURE__ */ new Error("candidate output cancelled"));
|
|
615
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
616
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error("candidate output deadline expired")), remainingMs);
|
|
617
|
+
})]);
|
|
618
|
+
} finally {
|
|
619
|
+
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
620
|
+
if (timer) clearTimeout(timer);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
async function stopProcess(process) {
|
|
624
|
+
if (!(await process.status()).running) return;
|
|
625
|
+
try {
|
|
626
|
+
await process.kill();
|
|
627
|
+
} catch (error) {
|
|
628
|
+
if ((await process.status()).running) throw error;
|
|
629
|
+
}
|
|
630
|
+
const termination = await process.wait();
|
|
631
|
+
assertTerminalStatus(await process.status(), termination);
|
|
632
|
+
}
|
|
633
|
+
function assertTerminalStatus(status, expected) {
|
|
634
|
+
if (status.running || !status.termination) throw new Error("exact process returned no terminal reason after exit");
|
|
635
|
+
if (expected && !isDeepStrictEqual(status.termination, expected)) throw new Error("exact process wait and status returned different terminal reasons");
|
|
636
|
+
}
|
|
637
|
+
function assertExecutionEnvironment(environment, metadata, providerName) {
|
|
638
|
+
if (!environment.id || environment.provider !== providerName) throw new Error("exact process provider returned the wrong environment identity");
|
|
639
|
+
if (!Object.entries(metadata).every(([name, value]) => isDeepStrictEqual(environment.metadata?.[name], value))) throw new Error("exact process provider did not persist candidate execution metadata");
|
|
640
|
+
}
|
|
641
|
+
function outputSpec(environment) {
|
|
642
|
+
const maxBytes = environment.metadata?.outputMaxBytes;
|
|
643
|
+
if (!Number.isSafeInteger(maxBytes) || Number(maxBytes) < 1) throw new Error("candidate exact process output bound is unavailable");
|
|
644
|
+
return { maxBytes: Number(maxBytes) };
|
|
645
|
+
}
|
|
646
|
+
function executionKey(executionId, executionPlanDigest) {
|
|
647
|
+
return `${executionId}\0${executionPlanDigest}`;
|
|
648
|
+
}
|
|
649
|
+
function executionIdempotencyKey(executionId, executionPlanDigest) {
|
|
650
|
+
return `candidate-${canonicalCandidateDigest$1({
|
|
651
|
+
kind: "agent-candidate-execution-identity",
|
|
652
|
+
executionId,
|
|
653
|
+
executionPlanDigest
|
|
654
|
+
}).slice(7)}`;
|
|
655
|
+
}
|
|
656
|
+
function exactImage(image, manifestDigest) {
|
|
657
|
+
if (isSha256Digest$1(image)) {
|
|
658
|
+
if (image !== manifestDigest) throw new Error("candidate container image conflicts with its resolved manifest");
|
|
659
|
+
return image;
|
|
660
|
+
}
|
|
661
|
+
const marker = image.lastIndexOf("@");
|
|
662
|
+
if (marker < 0) return `${image}@${manifestDigest}`;
|
|
663
|
+
if (image.slice(marker + 1) !== manifestDigest) throw new Error("candidate container image conflicts with its resolved manifest");
|
|
664
|
+
return image;
|
|
665
|
+
}
|
|
666
|
+
function environmentLifetimeMs(timeoutMs, retentionMs) {
|
|
667
|
+
const total = positiveInteger(timeoutMs, "candidate execution timeout") + retentionMs;
|
|
668
|
+
if (!Number.isSafeInteger(total)) throw new Error("candidate environment lifetime is too large");
|
|
669
|
+
return Math.ceil(total / 1e3) * 1e3;
|
|
670
|
+
}
|
|
671
|
+
function exactResources(resources) {
|
|
672
|
+
if (!Number.isFinite(resources.cpu) || resources.cpu <= 0) throw new Error("exact process CPU must be positive and finite");
|
|
673
|
+
return Object.freeze({
|
|
674
|
+
cpu: resources.cpu,
|
|
675
|
+
memoryMb: positiveInteger(resources.memoryMb, "exact process memory"),
|
|
676
|
+
diskMb: positiveInteger(resources.diskMb, "exact process disk")
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
function positiveInteger(value, label) {
|
|
680
|
+
if (!Number.isSafeInteger(value) || value < 1) throw new Error(`${label} must be a positive integer`);
|
|
681
|
+
return value;
|
|
682
|
+
}
|
|
683
|
+
function beneath(root, relativePath) {
|
|
684
|
+
if (posix.isAbsolute(relativePath)) throw new Error("candidate input path must be relative");
|
|
685
|
+
const path = posix.normalize(relativePath);
|
|
686
|
+
if (path === ".." || path.startsWith("../")) throw new Error("candidate input path escapes its execution root");
|
|
687
|
+
return posix.join(root, path);
|
|
688
|
+
}
|
|
689
|
+
function isSha256Digest$1(value) {
|
|
690
|
+
return typeof value === "string" && /^sha256:[a-f0-9]{64}$/.test(value);
|
|
691
|
+
}
|
|
692
|
+
//#endregion
|
|
693
|
+
//#region src/candidate-execution/model-settlement.ts
|
|
694
|
+
const USD_NANOS = 1e9;
|
|
695
|
+
/** Validate and detach the evaluator gateway's terminal, revoked call ledger. */
|
|
696
|
+
function sealAgentCandidateModelSettlement(settlement, expected) {
|
|
697
|
+
assertExactObjectKeys(settlement, [
|
|
698
|
+
"preparationId",
|
|
699
|
+
"grantDigest",
|
|
700
|
+
"closed",
|
|
701
|
+
"usageWithinLimits",
|
|
702
|
+
"calls"
|
|
703
|
+
], "model settlement");
|
|
704
|
+
if (settlement.closed !== true) throw new Error("protected model grant is not closed");
|
|
705
|
+
if (settlement.usageWithinLimits !== true) throw new Error("protected model settlement reports usage outside frozen limits");
|
|
706
|
+
if (settlement.grantDigest !== expected.grantDigest) throw new Error("protected model settlement grant digest does not match the reservation");
|
|
707
|
+
if (settlement.preparationId !== expected.preparationId) throw new Error("protected model settlement preparation does not match the reservation");
|
|
708
|
+
if (!Array.isArray(settlement.calls)) throw new Error("protected model settlement calls must be an array");
|
|
709
|
+
const callIds = /* @__PURE__ */ new Set();
|
|
710
|
+
const spanIds = /* @__PURE__ */ new Set();
|
|
711
|
+
let accountedInputTokens = 0;
|
|
712
|
+
let outputTokens = 0;
|
|
713
|
+
let cachedInputTokens = 0;
|
|
714
|
+
let reasoningTokens = 0;
|
|
715
|
+
let costUsdNanos = 0;
|
|
716
|
+
let costProvenance = "observed";
|
|
717
|
+
const calls = settlement.calls.map((source, index) => {
|
|
718
|
+
assertExactObjectKeys(source, [
|
|
719
|
+
"callId",
|
|
720
|
+
"generationId",
|
|
721
|
+
"traceSpanId",
|
|
722
|
+
"status",
|
|
723
|
+
"model",
|
|
724
|
+
"startedAtMs",
|
|
725
|
+
"endedAtMs",
|
|
726
|
+
"inputTokens",
|
|
727
|
+
"accountedInputTokens",
|
|
728
|
+
"outputTokens",
|
|
729
|
+
"cachedInputTokens",
|
|
730
|
+
"reasoningTokens",
|
|
731
|
+
"costUsdNanos",
|
|
732
|
+
"costProvenance"
|
|
733
|
+
], `model settlement call ${index}`);
|
|
734
|
+
assertIdentifier(source.callId, `model settlement call ${index} callId`);
|
|
735
|
+
assertIdentifier(source.generationId, `model settlement call ${index} generationId`);
|
|
736
|
+
assertIdentifier(source.traceSpanId, `model settlement call ${index} traceSpanId`);
|
|
737
|
+
if (source.traceSpanId !== source.generationId) throw new Error(`model settlement call ${index} traceSpanId is not its router generationId`);
|
|
738
|
+
if (source.status !== "succeeded" && source.status !== "failed") throw new Error(`model settlement call ${index} has an invalid status`);
|
|
739
|
+
if (callIds.has(source.callId)) throw new Error("protected model settlement has duplicate call ids");
|
|
740
|
+
if (spanIds.has(source.traceSpanId)) throw new Error("protected model settlement has duplicate trace span ids");
|
|
741
|
+
callIds.add(source.callId);
|
|
742
|
+
spanIds.add(source.traceSpanId);
|
|
743
|
+
if (source.model !== expected.model) throw new Error(`protected model settlement call ${index} has an unexpected model`);
|
|
744
|
+
assertTimestamp(source.startedAtMs, `model settlement call ${index} startedAtMs`);
|
|
745
|
+
assertTimestamp(source.endedAtMs, `model settlement call ${index} endedAtMs`);
|
|
746
|
+
if (source.endedAtMs < source.startedAtMs) throw new Error(`model settlement call ${index} ended before it started`);
|
|
747
|
+
assertCount$1(source.inputTokens, `model settlement call ${index} inputTokens`);
|
|
748
|
+
assertCount$1(source.accountedInputTokens, `model settlement call ${index} accountedInputTokens`);
|
|
749
|
+
if (source.accountedInputTokens < source.inputTokens) throw new Error(`model settlement call ${index} accountedInputTokens cannot be less than inputTokens`);
|
|
750
|
+
assertCount$1(source.outputTokens, `model settlement call ${index} outputTokens`);
|
|
751
|
+
assertCount$1(source.cachedInputTokens, `model settlement call ${index} cachedInputTokens`);
|
|
752
|
+
cachedInputTokens = safeAdd(cachedInputTokens, source.cachedInputTokens, "cached input token total");
|
|
753
|
+
assertCount$1(source.reasoningTokens, `model settlement call ${index} reasoningTokens`);
|
|
754
|
+
reasoningTokens = safeAdd(reasoningTokens, source.reasoningTokens, "reasoning token total");
|
|
755
|
+
assertCount$1(source.costUsdNanos, `model settlement call ${index} costUsdNanos`);
|
|
756
|
+
if (source.costProvenance !== "observed" && source.costProvenance !== "estimated") throw new Error(`model settlement call ${index} has an invalid cost provenance`);
|
|
757
|
+
if (source.costProvenance === "estimated") costProvenance = "estimated";
|
|
758
|
+
accountedInputTokens = safeAdd(accountedInputTokens, source.accountedInputTokens, "accounted input token total");
|
|
759
|
+
outputTokens = safeAdd(outputTokens, source.outputTokens, "output token total");
|
|
760
|
+
costUsdNanos = safeAdd(costUsdNanos, source.costUsdNanos, "cost total");
|
|
761
|
+
return Object.freeze({ ...source });
|
|
762
|
+
});
|
|
763
|
+
const usage = Object.freeze({
|
|
764
|
+
costUsdNanos,
|
|
765
|
+
inputTokens: accountedInputTokens,
|
|
766
|
+
outputTokens,
|
|
767
|
+
cachedInputTokens,
|
|
768
|
+
reasoningTokens,
|
|
769
|
+
modelCalls: calls.length,
|
|
770
|
+
costProvenance
|
|
771
|
+
});
|
|
772
|
+
return Object.freeze({
|
|
773
|
+
value: Object.freeze({
|
|
774
|
+
preparationId: settlement.preparationId,
|
|
775
|
+
grantDigest: settlement.grantDigest,
|
|
776
|
+
closed: true,
|
|
777
|
+
usageWithinLimits: true,
|
|
778
|
+
calls: Object.freeze(calls)
|
|
779
|
+
}),
|
|
780
|
+
usage
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Append the only accepted LLM spans from the router's closed ledger.
|
|
785
|
+
* Candidate and executor code may write tool/process spans, but never model usage.
|
|
786
|
+
*/
|
|
787
|
+
async function appendAuthoritativeModelSettlementSpans(traceStore, runId, settlement) {
|
|
788
|
+
const run = await traceStore.getRun(runId);
|
|
789
|
+
if (!run) throw new Error(`protected trace run is missing before model settlement: ${runId}`);
|
|
790
|
+
if (run.status === "running" || run.endedAt === void 0) throw new Error("protected trace run must be terminal before model spans are appended");
|
|
791
|
+
const existing = await traceStore.spans({ runId });
|
|
792
|
+
if (existing.some(isLlmSpan)) throw new Error("protected trace contains a model span not authored from the closed router ledger");
|
|
793
|
+
const occupiedIds = new Set(existing.map((span) => span.spanId));
|
|
794
|
+
for (const call of settlement.value.calls) {
|
|
795
|
+
if (occupiedIds.has(call.traceSpanId)) throw new Error(`protected trace span identity collides with router generation ${call.generationId}`);
|
|
796
|
+
await traceStore.appendSpan({
|
|
797
|
+
runId,
|
|
798
|
+
spanId: call.traceSpanId,
|
|
799
|
+
kind: "llm",
|
|
800
|
+
name: "protected model call",
|
|
801
|
+
model: call.model,
|
|
802
|
+
messages: [],
|
|
803
|
+
startedAt: call.startedAtMs,
|
|
804
|
+
endedAt: call.endedAtMs,
|
|
805
|
+
status: call.status === "succeeded" ? "ok" : "error",
|
|
806
|
+
inputTokens: call.inputTokens,
|
|
807
|
+
outputTokens: call.outputTokens,
|
|
808
|
+
cachedTokens: call.cachedInputTokens,
|
|
809
|
+
reasoningTokens: call.reasoningTokens,
|
|
810
|
+
costUsd: call.costUsdNanos / USD_NANOS,
|
|
811
|
+
attributes: {
|
|
812
|
+
"tangle.protected_model.source": "router-settlement",
|
|
813
|
+
"tangle.router.call_id": call.callId,
|
|
814
|
+
"tangle.router.generation_id": call.generationId
|
|
815
|
+
}
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
/** Match every protected trace span one-for-one against gateway call evidence. */
|
|
820
|
+
function assertTraceMatchesModelSettlement(spans, settlement) {
|
|
821
|
+
if (spans.length !== settlement.value.calls.length) throw new Error(`protected trace model calls ${spans.length} do not match model ledger ${settlement.value.calls.length}`);
|
|
822
|
+
const byId = new Map(spans.map((span) => [span.spanId, span]));
|
|
823
|
+
if (byId.size !== spans.length) throw new Error("protected trace has duplicate model span ids");
|
|
824
|
+
for (const call of settlement.value.calls) {
|
|
825
|
+
const span = byId.get(call.traceSpanId);
|
|
826
|
+
if (!span) throw new Error(`protected trace is missing model ledger span ${call.traceSpanId}`);
|
|
827
|
+
assertTraceCall(span, call);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
function usdToNanos(value, label) {
|
|
831
|
+
if (!Number.isFinite(value) || value < 0) throw new Error(`${label} must be nonnegative`);
|
|
832
|
+
const nanos = Math.round(value * USD_NANOS);
|
|
833
|
+
if (!Number.isSafeInteger(nanos)) throw new Error(`${label} exceeds fixed-point range`);
|
|
834
|
+
return nanos;
|
|
835
|
+
}
|
|
836
|
+
function assertTraceCall(span, call) {
|
|
837
|
+
if (span.model !== call.model) throw new Error(`protected trace span ${span.spanId} model does not match model ledger`);
|
|
838
|
+
if (span.startedAt !== call.startedAtMs || span.endedAt !== call.endedAtMs || span.status !== (call.status === "succeeded" ? "ok" : "error")) throw new Error(`protected trace span ${span.spanId} timing or status does not match model ledger`);
|
|
839
|
+
if (span.attributes?.["tangle.protected_model.source"] !== "router-settlement" || span.attributes?.["tangle.router.call_id"] !== call.callId || span.attributes?.["tangle.router.generation_id"] !== call.generationId) throw new Error(`protected trace span ${span.spanId} lacks router settlement provenance`);
|
|
840
|
+
for (const [name, traced, settled] of [
|
|
841
|
+
[
|
|
842
|
+
"inputTokens",
|
|
843
|
+
span.inputTokens,
|
|
844
|
+
call.inputTokens
|
|
845
|
+
],
|
|
846
|
+
[
|
|
847
|
+
"outputTokens",
|
|
848
|
+
span.outputTokens,
|
|
849
|
+
call.outputTokens
|
|
850
|
+
],
|
|
851
|
+
[
|
|
852
|
+
"cachedInputTokens",
|
|
853
|
+
span.cachedTokens ?? 0,
|
|
854
|
+
call.cachedInputTokens
|
|
855
|
+
],
|
|
856
|
+
[
|
|
857
|
+
"reasoningTokens",
|
|
858
|
+
span.reasoningTokens ?? 0,
|
|
859
|
+
call.reasoningTokens
|
|
860
|
+
]
|
|
861
|
+
]) if (traced === void 0 || traced !== settled) throw new Error(`protected trace span ${span.spanId} ${name} ${traced} does not match model ledger ${settled}`);
|
|
862
|
+
if (span.costUsd === void 0) throw new Error(`protected trace span ${span.spanId} is missing costUsd`);
|
|
863
|
+
const tracedCost = usdToNanos(span.costUsd, `protected trace span ${span.spanId} costUsd`);
|
|
864
|
+
if (tracedCost !== call.costUsdNanos) throw new Error(`protected trace span ${span.spanId} costUsdNanos ${tracedCost} does not match model ledger ${call.costUsdNanos}`);
|
|
865
|
+
}
|
|
866
|
+
function assertIdentifier(value, label) {
|
|
867
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 256) throw new Error(`${label} must be a non-empty bounded string`);
|
|
868
|
+
}
|
|
869
|
+
function assertCount$1(value, label) {
|
|
870
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new Error(`${label} must be a nonnegative safe integer`);
|
|
871
|
+
}
|
|
872
|
+
function assertTimestamp(value, label) {
|
|
873
|
+
if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`${label} must be a positive safe integer`);
|
|
874
|
+
}
|
|
875
|
+
function safeAdd(left, right, label) {
|
|
876
|
+
const total = left + right;
|
|
877
|
+
if (!Number.isSafeInteger(total)) throw new Error(`${label} exceeds safe integer range`);
|
|
878
|
+
return total;
|
|
879
|
+
}
|
|
880
|
+
//#endregion
|
|
881
|
+
//#region src/candidate-execution/protected-model-port.ts
|
|
882
|
+
const MAX_RECENT_SETTLEMENTS = 4096;
|
|
883
|
+
const RECOVERED_SETTLEMENT_RETENTION_MS = 900 * 1e3;
|
|
884
|
+
/**
|
|
885
|
+
* Bind a protected model-grant service to the immutable candidate runtime.
|
|
886
|
+
*
|
|
887
|
+
* The service remains the authority for expiry, admission, revocation, and
|
|
888
|
+
* metering. This adapter independently checks every response before allowing
|
|
889
|
+
* it to cross into candidate execution or durable receipt finalization.
|
|
890
|
+
*/
|
|
891
|
+
function createProtectedAgentCandidateModelPort(options) {
|
|
892
|
+
const gatewayDomain = assertGatewayDomain(options.gatewayDomain);
|
|
893
|
+
const activationEnvNames = exactEnvironmentNames(options.activationEnvNames);
|
|
894
|
+
const reservations = /* @__PURE__ */ new Map();
|
|
895
|
+
const expiredReservations = /* @__PURE__ */ new Map();
|
|
896
|
+
const recentSettlements = /* @__PURE__ */ new Map();
|
|
897
|
+
return {
|
|
898
|
+
resolve: async (input) => {
|
|
899
|
+
const request = immutableCandidateValue(input);
|
|
900
|
+
return immutableCandidateValue(validateResolvedModel(await options.resolveModel(request), request));
|
|
901
|
+
},
|
|
902
|
+
reserveGrant: async (input) => {
|
|
903
|
+
pruneExpiredState(reservations, expiredReservations, recentSettlements, Date.now());
|
|
904
|
+
const request = immutableCandidateValue(input);
|
|
905
|
+
validateReserveInput(request);
|
|
906
|
+
const requestDigest = canonicalCandidateDigest$1(request);
|
|
907
|
+
const key = reservationKey(request.executionId, request.preparationId);
|
|
908
|
+
if (recentSettlements.has(key)) throw new Error("protected model reservation is already settled");
|
|
909
|
+
if (expiredReservations.get(key)) throw new Error("protected model reservation has expired");
|
|
910
|
+
const previous = reservations.get(key);
|
|
911
|
+
if (previous && previous.requestDigest !== requestDigest) throw new Error("protected model reservation retry changed immutable input");
|
|
912
|
+
const reservation = validateReservation(await options.client.reserve(request), request, gatewayDomain);
|
|
913
|
+
const responseDigest = canonicalCandidateDigest$1(reservation);
|
|
914
|
+
if (recentSettlements.has(key)) throw new Error("protected model reservation completed after the grant was settled");
|
|
915
|
+
const recorded = previous ?? reservations.get(key);
|
|
916
|
+
if (recorded && recorded.requestDigest !== requestDigest) throw new Error("protected model reservation retry changed immutable input");
|
|
917
|
+
if (recorded && recorded.responseDigest !== responseDigest) throw new Error("protected model reservation retry returned different evidence");
|
|
918
|
+
if (!recorded) reservations.set(key, {
|
|
919
|
+
requestDigest,
|
|
920
|
+
responseDigest,
|
|
921
|
+
executionId: request.executionId,
|
|
922
|
+
preparationId: request.preparationId,
|
|
923
|
+
grantDigest: reservation.digest,
|
|
924
|
+
expiresAtMs: request.expiresAtMs,
|
|
925
|
+
resolved: immutableCandidateValue(request.resolved),
|
|
926
|
+
limits: immutableCandidateValue(request.limits),
|
|
927
|
+
maxCostUsdNanos: usdToNanos(request.limits.maxCostUsd, "reserved maxCostUsd"),
|
|
928
|
+
activation: "reserved"
|
|
929
|
+
});
|
|
930
|
+
return reservation;
|
|
931
|
+
},
|
|
932
|
+
activateGrant: async (input) => {
|
|
933
|
+
pruneExpiredState(reservations, expiredReservations, recentSettlements, Date.now());
|
|
934
|
+
const request = immutableCandidateValue(input);
|
|
935
|
+
const key = reservationKey(request.executionId, request.preparationId);
|
|
936
|
+
if (recentSettlements.has(key)) throw new Error("protected model grant is already settled");
|
|
937
|
+
const state = reservations.get(key);
|
|
938
|
+
if (!state && expiredReservations.has(key)) throw new Error("protected model grant reservation has expired");
|
|
939
|
+
if (!state) throw new Error("protected model grant was not reserved by this port");
|
|
940
|
+
assertGrantIdentity(state, request);
|
|
941
|
+
if (state.settlementDigest) throw new Error("protected model grant is already settled");
|
|
942
|
+
if (state.activation !== "reserved") throw new Error("protected model grant activation is single-use");
|
|
943
|
+
if (!Number.isSafeInteger(request.deadlineAtMs) || request.deadlineAtMs <= 0) throw new Error("protected model activation deadline must be a positive safe integer");
|
|
944
|
+
if (request.deadlineAtMs <= Date.now()) throw new Error("protected model activation deadline must be in the future");
|
|
945
|
+
if (request.deadlineAtMs > state.expiresAtMs) throw new Error("protected model activation deadline exceeds the reserved expiry");
|
|
946
|
+
state.activation = "activating";
|
|
947
|
+
try {
|
|
948
|
+
const activation = validateActivation(await options.client.activate(request), state.limits.maxModelCalls === 0 ? [] : activationEnvNames);
|
|
949
|
+
if (recentSettlements.has(key) || reservations.get(key) !== state) throw new Error("protected model grant settled while activation was in flight");
|
|
950
|
+
state.activation = "activated";
|
|
951
|
+
return activation;
|
|
952
|
+
} catch (error) {
|
|
953
|
+
state.activation = "reserved";
|
|
954
|
+
throw error;
|
|
955
|
+
}
|
|
956
|
+
},
|
|
957
|
+
settleGrant: async (input) => {
|
|
958
|
+
const now = Date.now();
|
|
959
|
+
pruneExpiredState(reservations, expiredReservations, recentSettlements, now);
|
|
960
|
+
const request = immutableCandidateValue(input);
|
|
961
|
+
const key = reservationKey(request.executionId, request.preparationId);
|
|
962
|
+
const state = reservations.get(key) ?? expiredReservations.get(key)?.state;
|
|
963
|
+
if (state) assertGrantIdentity(state, request);
|
|
964
|
+
const remembered = state ?? recentSettlements.get(key);
|
|
965
|
+
if (remembered?.settlementReason && remembered.settlementReason !== request.reason) throw new Error("protected model settlement retry changed the termination reason");
|
|
966
|
+
const sealed = sealAgentCandidateModelSettlement(await options.client.settle(request), {
|
|
967
|
+
preparationId: request.preparationId,
|
|
968
|
+
grantDigest: request.grantDigest,
|
|
969
|
+
model: request.resolved.model
|
|
970
|
+
});
|
|
971
|
+
if (state) assertWithinReservedLimits(sealed.usage, state);
|
|
972
|
+
const settlementDigest = canonicalCandidateDigest$1(sealed.value);
|
|
973
|
+
if (remembered?.settlementDigest && remembered.settlementDigest !== settlementDigest) throw new Error("protected model settlement retry returned a different final ledger");
|
|
974
|
+
if (remembered?.settlementReason && remembered.settlementReason !== request.reason) throw new Error("protected model settlement retry changed the termination reason");
|
|
975
|
+
const expiresAtMs = state?.expiresAtMs ?? now + RECOVERED_SETTLEMENT_RETENTION_MS;
|
|
976
|
+
if (state) {
|
|
977
|
+
state.settlementDigest = settlementDigest;
|
|
978
|
+
state.settlementReason = request.reason;
|
|
979
|
+
}
|
|
980
|
+
reservations.delete(key);
|
|
981
|
+
expiredReservations.delete(key);
|
|
982
|
+
rememberSettlement(recentSettlements, key, {
|
|
983
|
+
settlementDigest,
|
|
984
|
+
settlementReason: request.reason,
|
|
985
|
+
expiresAtMs
|
|
986
|
+
});
|
|
987
|
+
return sealed.value;
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
function validateReserveInput(input) {
|
|
992
|
+
if (!Number.isSafeInteger(input.expiresAtMs) || input.expiresAtMs <= 0) throw new Error("protected model reservation expiry must be a positive safe integer");
|
|
993
|
+
if (input.expiresAtMs <= Date.now()) throw new Error("protected model reservation expiry must be in the future");
|
|
994
|
+
for (const [name, value] of [
|
|
995
|
+
["maxModelCalls", input.limits.maxModelCalls],
|
|
996
|
+
["maxInputTokens", input.limits.maxInputTokens],
|
|
997
|
+
["maxOutputTokens", input.limits.maxOutputTokens],
|
|
998
|
+
["maxTotalTokens", input.limits.maxTotalTokens]
|
|
999
|
+
]) {
|
|
1000
|
+
if (value === void 0) continue;
|
|
1001
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new Error(`protected model reservation ${name} must be a nonnegative safe integer`);
|
|
1002
|
+
}
|
|
1003
|
+
usdToNanos(input.limits.maxCostUsd, "reserved maxCostUsd");
|
|
1004
|
+
}
|
|
1005
|
+
function validateReservation(value, expected, gatewayDomain) {
|
|
1006
|
+
const source = exactRecord(value, [
|
|
1007
|
+
"preparationId",
|
|
1008
|
+
"digest",
|
|
1009
|
+
"expiresAtMs",
|
|
1010
|
+
"enforcedLimits",
|
|
1011
|
+
"network"
|
|
1012
|
+
], "protected model reservation response");
|
|
1013
|
+
if (source.preparationId !== expected.preparationId) throw new Error("protected model reservation response changed preparation identity");
|
|
1014
|
+
if (!isSha256Digest(source.digest)) throw new Error("protected model reservation response has an invalid digest");
|
|
1015
|
+
if (source.expiresAtMs !== expected.expiresAtMs) throw new Error("protected model reservation response changed expiry");
|
|
1016
|
+
exactRecord(source.enforcedLimits, modelLimitKeys(expected.limits), "protected model reservation enforced limits");
|
|
1017
|
+
if (canonicalCandidateDigest$1(source.enforcedLimits) !== canonicalCandidateDigest$1(expected.limits)) throw new Error("protected model reservation response changed enforced limits");
|
|
1018
|
+
const network = validateReservationNetwork(source.network, expected.limits.maxModelCalls, gatewayDomain);
|
|
1019
|
+
return immutableCandidateValue({
|
|
1020
|
+
preparationId: source.preparationId,
|
|
1021
|
+
digest: source.digest,
|
|
1022
|
+
expiresAtMs: source.expiresAtMs,
|
|
1023
|
+
enforcedLimits: source.enforcedLimits,
|
|
1024
|
+
network
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
function validateReservationNetwork(value, maxModelCalls, gatewayDomain) {
|
|
1028
|
+
if (maxModelCalls === 0) {
|
|
1029
|
+
if (exactRecord(value, ["mode"], "protected model reservation network").mode !== "disabled") throw new Error("zero-call protected model reservation must disable gateway access");
|
|
1030
|
+
return { mode: "disabled" };
|
|
1031
|
+
}
|
|
1032
|
+
const source = exactRecord(value, ["mode", "domains"], "protected model reservation network");
|
|
1033
|
+
if (source.mode !== "gateway-only") throw new Error("protected model reservation must allow only its model gateway");
|
|
1034
|
+
if (!Array.isArray(source.domains) || source.domains.length !== 1 || source.domains[0] !== gatewayDomain) throw new Error("protected model reservation returned an unexpected gateway domain");
|
|
1035
|
+
return {
|
|
1036
|
+
mode: "gateway-only",
|
|
1037
|
+
domains: [gatewayDomain]
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
function validateActivation(value, expectedNames) {
|
|
1041
|
+
const env = record(exactRecord(value, ["env"], "protected model activation response").env, "protected model activation environment");
|
|
1042
|
+
const actualNames = Object.keys(env).sort();
|
|
1043
|
+
if (actualNames.length !== expectedNames.length || actualNames.some((name, index) => name !== expectedNames[index])) throw new Error("protected model activation returned unexpected environment names");
|
|
1044
|
+
const detached = Object.create(null);
|
|
1045
|
+
for (const name of expectedNames) {
|
|
1046
|
+
const value = env[name];
|
|
1047
|
+
if (typeof value !== "string" || value.length < 8 || value.length > 65536) throw new Error(`protected model activation ${name} must be a non-empty protected value`);
|
|
1048
|
+
detached[name] = value;
|
|
1049
|
+
}
|
|
1050
|
+
return Object.freeze({ env: Object.freeze(detached) });
|
|
1051
|
+
}
|
|
1052
|
+
function validateResolvedModel(value, expected) {
|
|
1053
|
+
const source = exactRecord(value, [
|
|
1054
|
+
"requested",
|
|
1055
|
+
"provider",
|
|
1056
|
+
"model",
|
|
1057
|
+
"snapshot",
|
|
1058
|
+
"reasoningEffort"
|
|
1059
|
+
], "resolved candidate model");
|
|
1060
|
+
if (source.requested !== expected.requested) throw new Error("model resolver changed the evaluator-owned request");
|
|
1061
|
+
if (source.reasoningEffort !== expected.reasoningEffort) throw new Error("model resolver changed the evaluator-owned reasoning effort");
|
|
1062
|
+
for (const name of [
|
|
1063
|
+
"requested",
|
|
1064
|
+
"provider",
|
|
1065
|
+
"model",
|
|
1066
|
+
"snapshot"
|
|
1067
|
+
]) if (typeof source[name] !== "string" || source[name].length === 0) throw new Error(`resolved candidate model ${name} must be non-empty`);
|
|
1068
|
+
return source;
|
|
1069
|
+
}
|
|
1070
|
+
function assertGrantIdentity(expected, actual) {
|
|
1071
|
+
if (actual.executionId !== expected.executionId || actual.preparationId !== expected.preparationId || actual.grantDigest !== expected.grantDigest || canonicalCandidateDigest$1(actual.resolved) !== canonicalCandidateDigest$1(expected.resolved)) throw new Error("protected model grant input does not match its immutable reservation");
|
|
1072
|
+
}
|
|
1073
|
+
function assertWithinReservedLimits(usage, reservation) {
|
|
1074
|
+
for (const [name, actual, limit] of [
|
|
1075
|
+
[
|
|
1076
|
+
"model calls",
|
|
1077
|
+
usage.modelCalls,
|
|
1078
|
+
reservation.limits.maxModelCalls
|
|
1079
|
+
],
|
|
1080
|
+
[
|
|
1081
|
+
"input tokens",
|
|
1082
|
+
usage.inputTokens,
|
|
1083
|
+
reservation.limits.maxInputTokens
|
|
1084
|
+
],
|
|
1085
|
+
[
|
|
1086
|
+
"output tokens",
|
|
1087
|
+
usage.outputTokens,
|
|
1088
|
+
reservation.limits.maxOutputTokens
|
|
1089
|
+
],
|
|
1090
|
+
[
|
|
1091
|
+
"cost USD nanos",
|
|
1092
|
+
usage.costUsdNanos,
|
|
1093
|
+
reservation.maxCostUsdNanos
|
|
1094
|
+
]
|
|
1095
|
+
]) if (actual > limit) throw new Error(`protected model settlement ${name} ${actual} exceeds reserved ${limit}`);
|
|
1096
|
+
const maxTotalTokens = reservation.limits.maxTotalTokens;
|
|
1097
|
+
if (maxTotalTokens !== void 0) {
|
|
1098
|
+
const totalTokens = usage.inputTokens + usage.outputTokens;
|
|
1099
|
+
if (!Number.isSafeInteger(totalTokens)) throw new Error("protected model settlement total token usage is incomplete");
|
|
1100
|
+
if (totalTokens > maxTotalTokens) throw new Error(`protected model settlement total tokens ${totalTokens} exceeds reserved ${maxTotalTokens}`);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
function modelLimitKeys(limits) {
|
|
1104
|
+
return limits.maxTotalTokens === void 0 ? [
|
|
1105
|
+
"maxModelCalls",
|
|
1106
|
+
"maxInputTokens",
|
|
1107
|
+
"maxOutputTokens",
|
|
1108
|
+
"maxCostUsd"
|
|
1109
|
+
] : [
|
|
1110
|
+
"maxModelCalls",
|
|
1111
|
+
"maxInputTokens",
|
|
1112
|
+
"maxOutputTokens",
|
|
1113
|
+
"maxTotalTokens",
|
|
1114
|
+
"maxCostUsd"
|
|
1115
|
+
];
|
|
1116
|
+
}
|
|
1117
|
+
function exactEnvironmentNames(values) {
|
|
1118
|
+
if (values.length === 0) throw new Error("protected model activation environment names must not be empty");
|
|
1119
|
+
const names = [...values].sort();
|
|
1120
|
+
if (new Set(names).size !== names.length) throw new Error("protected model activation environment names must be unique");
|
|
1121
|
+
for (const name of names) if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || [
|
|
1122
|
+
"__proto__",
|
|
1123
|
+
"constructor",
|
|
1124
|
+
"prototype"
|
|
1125
|
+
].includes(name)) throw new Error(`protected model activation environment name is invalid: ${name}`);
|
|
1126
|
+
return Object.freeze(names);
|
|
1127
|
+
}
|
|
1128
|
+
function assertGatewayDomain(value) {
|
|
1129
|
+
if (value.length > 253 || value !== value.toLowerCase() || value.endsWith(".") || value.includes(":") || value === "localhost" || value.endsWith(".localhost") || value === "metadata.google" || value === "metadata.google.internal") throw new Error("protected model gateway must be an exact lowercase public DNS name");
|
|
1130
|
+
const labels = value.split(".");
|
|
1131
|
+
if (labels.length < 2 || !labels.every((label) => label.length >= 1 && label.length <= 63 && /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(label)) || !/[a-z]/.test(labels.at(-1) ?? "")) throw new Error("protected model gateway must be an exact lowercase public DNS name");
|
|
1132
|
+
return value;
|
|
1133
|
+
}
|
|
1134
|
+
function reservationKey(executionId, preparationId) {
|
|
1135
|
+
return canonicalCandidateDigest$1({
|
|
1136
|
+
executionId,
|
|
1137
|
+
preparationId
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
function pruneExpiredState(reservations, expiredReservations, settlements, now) {
|
|
1141
|
+
for (const [key, value] of reservations) if (value.expiresAtMs <= now) {
|
|
1142
|
+
reservations.delete(key);
|
|
1143
|
+
expiredReservations.set(key, {
|
|
1144
|
+
state: value,
|
|
1145
|
+
retainedUntilMs: now + RECOVERED_SETTLEMENT_RETENTION_MS
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
for (const [key, value] of expiredReservations) if (value.retainedUntilMs <= now) expiredReservations.delete(key);
|
|
1149
|
+
for (const [key, value] of settlements) if (value.expiresAtMs <= now) settlements.delete(key);
|
|
1150
|
+
}
|
|
1151
|
+
function rememberSettlement(settlements, key, value) {
|
|
1152
|
+
settlements.delete(key);
|
|
1153
|
+
settlements.set(key, value);
|
|
1154
|
+
while (settlements.size > MAX_RECENT_SETTLEMENTS) {
|
|
1155
|
+
const oldest = settlements.keys().next().value;
|
|
1156
|
+
if (oldest === void 0) return;
|
|
1157
|
+
settlements.delete(oldest);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
function isSha256Digest(value) {
|
|
1161
|
+
return typeof value === "string" && /^sha256:[a-f0-9]{64}$/.test(value);
|
|
1162
|
+
}
|
|
1163
|
+
function exactRecord(value, keys, label) {
|
|
1164
|
+
const source = record(value, label);
|
|
1165
|
+
assertExactObjectKeys(source, keys, label);
|
|
1166
|
+
return source;
|
|
1167
|
+
}
|
|
1168
|
+
function record(value, label) {
|
|
1169
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
|
|
1170
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1171
|
+
if (prototype !== Object.prototype && prototype !== null) throw new Error(`${label} must be a plain object`);
|
|
1172
|
+
return value;
|
|
1173
|
+
}
|
|
1174
|
+
function modelLimits(limits) {
|
|
1175
|
+
return {
|
|
1176
|
+
maxModelCalls: limits.maxModelCalls,
|
|
1177
|
+
maxInputTokens: limits.maxInputTokens,
|
|
1178
|
+
maxOutputTokens: limits.maxOutputTokens,
|
|
1179
|
+
maxCostUsd: limits.maxCostUsd
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
//#endregion
|
|
12
1183
|
//#region src/candidate-execution/claim-file-formats.ts
|
|
13
1184
|
function sealCandidatePreparationEvidence(value, executionPlanDigest) {
|
|
14
1185
|
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("candidate execution preparation evidence must be an object");
|
|
@@ -249,7 +1420,7 @@ function sealUsage(usage) {
|
|
|
249
1420
|
"cachedInputTokens",
|
|
250
1421
|
"reasoningTokens",
|
|
251
1422
|
"modelCalls"
|
|
252
|
-
]) assertCount
|
|
1423
|
+
]) assertCount(usage[field], `terminal usage ${field}`);
|
|
253
1424
|
if (usage.costProvenance !== "observed" && usage.costProvenance !== "estimated") throw new Error("terminal usage costProvenance must be observed or estimated");
|
|
254
1425
|
return Object.freeze({
|
|
255
1426
|
costUsdNanos: usage.costUsdNanos,
|
|
@@ -302,7 +1473,7 @@ function assertExecutionId$1(value) {
|
|
|
302
1473
|
function assertSha256Digest$1(value, field) {
|
|
303
1474
|
if (!SHA256_PATTERN$1.test(value)) throw new Error(`candidate execution claim ${field} must be a lowercase sha256 digest`);
|
|
304
1475
|
}
|
|
305
|
-
function assertCount
|
|
1476
|
+
function assertCount(value, label) {
|
|
306
1477
|
if (!Number.isSafeInteger(value) || value < 0) throw new Error(`candidate execution ${label} must be a non-negative safe integer`);
|
|
307
1478
|
}
|
|
308
1479
|
function candidateCleanupTimeout(timeoutMs) {
|
|
@@ -767,13 +1938,6 @@ function assertExecutionId(value) {
|
|
|
767
1938
|
function assertBoundedIdentifier(value, label, maxLength) {
|
|
768
1939
|
if (typeof value !== "string" || value.length === 0 || value.length > maxLength || hasControlCharacter(value)) throw new Error(`candidate execution ${label} is invalid`);
|
|
769
1940
|
}
|
|
770
|
-
function hasControlCharacter(value) {
|
|
771
|
-
for (let index = 0; index < value.length; index++) {
|
|
772
|
-
const code = value.charCodeAt(index);
|
|
773
|
-
if (code < 32 || code === 127) return true;
|
|
774
|
-
}
|
|
775
|
-
return false;
|
|
776
|
-
}
|
|
777
1941
|
function assertPositiveTimestamp(value, label) {
|
|
778
1942
|
if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`candidate execution ${label} must be a positive safe timestamp`);
|
|
779
1943
|
}
|
|
@@ -820,77 +1984,26 @@ const candidateClaimFileInternals = Object.freeze({
|
|
|
820
1984
|
});
|
|
821
1985
|
//#endregion
|
|
822
1986
|
//#region src/candidate-execution/execution-window.ts
|
|
823
|
-
const CANDIDATE_TERMINAL_PERSISTENCE_MARGIN_MS = 1e4;
|
|
824
|
-
const CANDIDATE_POST_RUN_CLEANUP_PHASES = 4;
|
|
825
|
-
/** Process stop, access closure, model evidence, and task/result evidence. */
|
|
826
|
-
function candidatePostRunWindowMs(cleanupTimeoutMs, resultTimeoutMs) {
|
|
827
|
-
const windowMs = cleanupTimeoutMs * CANDIDATE_POST_RUN_CLEANUP_PHASES + resultTimeoutMs + CANDIDATE_TERMINAL_PERSISTENCE_MARGIN_MS;
|
|
828
|
-
if (!Number.isSafeInteger(windowMs) || windowMs > 2147483647) throw new Error("candidate post-run window exceeds the supported timer range");
|
|
829
|
-
return windowMs;
|
|
830
|
-
}
|
|
831
|
-
/** Maximum owner lifetime from claim through process stop, access closure, and terminal write. */
|
|
832
|
-
function candidateExecutionOwnerWindowMs(timeoutMs, cleanupTimeoutMs, resultTimeoutMs) {
|
|
833
|
-
const windowMs = timeoutMs + candidatePostRunWindowMs(cleanupTimeoutMs, resultTimeoutMs);
|
|
834
|
-
if (!Number.isSafeInteger(windowMs) || windowMs > 2147483647) throw new Error("candidate execution and cleanup window exceeds the supported timer range");
|
|
835
|
-
return windowMs;
|
|
836
|
-
}
|
|
837
|
-
/** Time reserved after scoring for failure evidence plus terminal publication. */
|
|
838
|
-
function candidateTerminalWindowMs(cleanupTimeoutMs) {
|
|
839
|
-
const windowMs = cleanupTimeoutMs + CANDIDATE_TERMINAL_PERSISTENCE_MARGIN_MS;
|
|
840
|
-
if (!Number.isSafeInteger(windowMs) || windowMs > 2147483647) throw new Error("candidate terminal window exceeds the supported timer range");
|
|
841
|
-
return windowMs;
|
|
842
|
-
}
|
|
843
|
-
//#endregion
|
|
844
|
-
//#region src/candidate-execution/execution-roots.ts
|
|
845
|
-
/** Validate the canonical identity and isolation boundary for execution roots. */
|
|
846
|
-
function assertAgentCandidateExecutionRoots(roots) {
|
|
847
|
-
const entries = [
|
|
848
|
-
["task", roots.taskRoot],
|
|
849
|
-
["candidate", roots.candidateRoot],
|
|
850
|
-
["profile", roots.profileRoot]
|
|
851
|
-
];
|
|
852
|
-
for (const [name, root] of entries) {
|
|
853
|
-
if (root === void 0) continue;
|
|
854
|
-
if (!posix.isAbsolute(root) || posix.normalize(root) !== root) throw new Error(`execution ${name} root must be a canonical absolute path`);
|
|
855
|
-
}
|
|
856
|
-
for (let left = 0; left < entries.length; left++) {
|
|
857
|
-
const entry = entries[left];
|
|
858
|
-
if (!entry) continue;
|
|
859
|
-
const [leftName, leftRoot] = entry;
|
|
860
|
-
if (leftRoot === void 0) continue;
|
|
861
|
-
for (let right = left + 1; right < entries.length; right++) {
|
|
862
|
-
const rightEntry = entries[right];
|
|
863
|
-
if (!rightEntry) continue;
|
|
864
|
-
const [rightName, rightRoot] = rightEntry;
|
|
865
|
-
if (rightRoot !== void 0 && executionPathsOverlap(leftRoot, rightRoot)) throw new Error(`execution ${leftName} and ${rightName} roots must be distinct and non-overlapping`);
|
|
866
|
-
}
|
|
867
|
-
}
|
|
1987
|
+
const CANDIDATE_TERMINAL_PERSISTENCE_MARGIN_MS = 1e4;
|
|
1988
|
+
const CANDIDATE_POST_RUN_CLEANUP_PHASES = 4;
|
|
1989
|
+
/** Process stop, access closure, model evidence, and task/result evidence. */
|
|
1990
|
+
function candidatePostRunWindowMs(cleanupTimeoutMs, resultTimeoutMs) {
|
|
1991
|
+
const windowMs = cleanupTimeoutMs * CANDIDATE_POST_RUN_CLEANUP_PHASES + resultTimeoutMs + CANDIDATE_TERMINAL_PERSISTENCE_MARGIN_MS;
|
|
1992
|
+
if (!Number.isSafeInteger(windowMs) || windowMs > 2147483647) throw new Error("candidate post-run window exceeds the supported timer range");
|
|
1993
|
+
return windowMs;
|
|
868
1994
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
const
|
|
872
|
-
|
|
1995
|
+
/** Maximum owner lifetime from claim through process stop, access closure, and terminal write. */
|
|
1996
|
+
function candidateExecutionOwnerWindowMs(timeoutMs, cleanupTimeoutMs, resultTimeoutMs) {
|
|
1997
|
+
const windowMs = timeoutMs + candidatePostRunWindowMs(cleanupTimeoutMs, resultTimeoutMs);
|
|
1998
|
+
if (!Number.isSafeInteger(windowMs) || windowMs > 2147483647) throw new Error("candidate execution and cleanup window exceeds the supported timer range");
|
|
1999
|
+
return windowMs;
|
|
2000
|
+
}
|
|
2001
|
+
/** Time reserved after scoring for failure evidence plus terminal publication. */
|
|
2002
|
+
function candidateTerminalWindowMs(cleanupTimeoutMs) {
|
|
2003
|
+
const windowMs = cleanupTimeoutMs + CANDIDATE_TERMINAL_PERSISTENCE_MARGIN_MS;
|
|
2004
|
+
if (!Number.isSafeInteger(windowMs) || windowMs > 2147483647) throw new Error("candidate terminal window exceeds the supported timer range");
|
|
2005
|
+
return windowMs;
|
|
873
2006
|
}
|
|
874
|
-
//#endregion
|
|
875
|
-
//#region src/candidate-execution/types.ts
|
|
876
|
-
const verifiedCandidateBrand = Symbol("verifiedAgentCandidate");
|
|
877
|
-
const preparedCandidateBrand = Symbol("preparedAgentCandidate");
|
|
878
|
-
const verifiedTaskOutcomeBrand = Symbol("verifiedTaskOutcome");
|
|
879
|
-
/** Protected trace tags that bind a run to one prepared candidate execution. */
|
|
880
|
-
const CANDIDATE_TRACE_TAGS = {
|
|
881
|
-
executionId: "tangle.candidate.execution_id",
|
|
882
|
-
bundleDigest: "tangle.candidate.bundle_digest",
|
|
883
|
-
executionPlanDigest: "tangle.candidate.execution_plan_digest",
|
|
884
|
-
materializationReceiptDigest: "tangle.candidate.materialization_receipt_digest"
|
|
885
|
-
};
|
|
886
|
-
/** Environment keys used to propagate immutable candidate trace identity. */
|
|
887
|
-
const CANDIDATE_TRACE_ENV = {
|
|
888
|
-
executionId: "TANGLE_CANDIDATE_EXECUTION_ID",
|
|
889
|
-
bundleDigest: "TANGLE_CANDIDATE_BUNDLE_DIGEST",
|
|
890
|
-
executionPlanDigest: "TANGLE_CANDIDATE_EXECUTION_PLAN_DIGEST",
|
|
891
|
-
materializationReceiptDigest: "TANGLE_CANDIDATE_MATERIALIZATION_RECEIPT_DIGEST",
|
|
892
|
-
traceRunId: "TANGLE_TRACE_RUN_ID"
|
|
893
|
-
};
|
|
894
2007
|
//#endregion
|
|
895
2008
|
//#region src/candidate-execution/prepared-state.ts
|
|
896
2009
|
const stateByExecution = /* @__PURE__ */ new WeakMap();
|
|
@@ -1088,7 +2201,7 @@ function assertPrivateCandidateIntegrity(state) {
|
|
|
1088
2201
|
assertSignedBenchmarkInput(state);
|
|
1089
2202
|
const instruction = Buffer.from(state.benchmarkTask.instruction, "utf8");
|
|
1090
2203
|
if (!Buffer.from(state.instruction.bytes).equals(instruction) || JSON.stringify(state.instruction.delivery) !== JSON.stringify(state.executionPlan.value.material.instructionDelivery)) throw new Error("prepared instruction no longer matches the signed execution plan");
|
|
1091
|
-
if (!/^candidate-preparation\.[A-Za-z0-9_-]{43}$/.test(state.preparationId) || !Number.isSafeInteger(state.reservationExpiresAtMs) || state.reservationExpiresAtMs <= 0 || !Number.isSafeInteger(state.cleanupTimeoutMs) || state.cleanupTimeoutMs <= 0 || !Number.isSafeInteger(state.resultTimeoutMs) || state.resultTimeoutMs <= 0 || JSON.stringify(state.resolvedModel) !== JSON.stringify(state.executionPlan.value.material.model.resolved) || state.modelReservation.digest !== state.executionPlan.value.material.model.access.grantDigest || state.modelReservation.preparationId !== state.preparationId || state.modelReservation.expiresAtMs !== state.reservationExpiresAtMs || canonicalCandidateDigest$1(state.modelReservation.network) !== canonicalCandidateDigest$1(state.executionPlan.value.material.model.access.network) || canonicalCandidateDigest$1(state.modelReservation.enforcedLimits) !== canonicalCandidateDigest$1(modelLimits
|
|
2204
|
+
if (!/^candidate-preparation\.[A-Za-z0-9_-]{43}$/.test(state.preparationId) || !Number.isSafeInteger(state.reservationExpiresAtMs) || state.reservationExpiresAtMs <= 0 || !Number.isSafeInteger(state.cleanupTimeoutMs) || state.cleanupTimeoutMs <= 0 || !Number.isSafeInteger(state.resultTimeoutMs) || state.resultTimeoutMs <= 0 || JSON.stringify(state.resolvedModel) !== JSON.stringify(state.executionPlan.value.material.model.resolved) || state.modelReservation.digest !== state.executionPlan.value.material.model.access.grantDigest || state.modelReservation.preparationId !== state.preparationId || state.modelReservation.expiresAtMs !== state.reservationExpiresAtMs || canonicalCandidateDigest$1(state.modelReservation.network) !== canonicalCandidateDigest$1(state.executionPlan.value.material.model.access.network) || canonicalCandidateDigest$1(state.modelReservation.enforcedLimits) !== canonicalCandidateDigest$1(modelLimits(state.executionPlan.value.material.limits))) throw new Error("prepared model access no longer matches the signed execution plan");
|
|
1092
2205
|
assertExecutorInputs(state);
|
|
1093
2206
|
assertPreparedKnowledge(state);
|
|
1094
2207
|
if (state.memory.mode === "isolated" && !state.memoryReservation || state.memory.mode === "disabled" && state.memoryReservation) throw new Error("prepared memory reservation does not match the signed execution plan");
|
|
@@ -1225,14 +2338,6 @@ function immutableExecutorFiles(files) {
|
|
|
1225
2338
|
bytes: Uint8Array.from(file.bytes)
|
|
1226
2339
|
})));
|
|
1227
2340
|
}
|
|
1228
|
-
function modelLimits$1(limits) {
|
|
1229
|
-
return {
|
|
1230
|
-
maxModelCalls: limits.maxModelCalls,
|
|
1231
|
-
maxInputTokens: limits.maxInputTokens,
|
|
1232
|
-
maxOutputTokens: limits.maxOutputTokens,
|
|
1233
|
-
maxCostUsd: limits.maxCostUsd
|
|
1234
|
-
};
|
|
1235
|
-
}
|
|
1236
2341
|
function transitionLifecycle(prepared, expected, next) {
|
|
1237
2342
|
const lifecycle = lifecycleByExecution.get(prepared);
|
|
1238
2343
|
if (!lifecycle || !expected.includes(lifecycle.status)) throw new Error(`prepared candidate execution is already ${lifecycle?.status ?? "unknown"}`);
|
|
@@ -1383,224 +2488,36 @@ function sealTaskOutcomeCapture(value, expected) {
|
|
|
1383
2488
|
if (capture.kind === "output") {
|
|
1384
2489
|
assertExactObjectKeys(capture, ["kind", "bytes"], "candidate task output capture");
|
|
1385
2490
|
if (expected.kind !== "output") throw new Error("candidate task output capture does not match the signed outcome kind");
|
|
1386
|
-
if (!(capture.bytes instanceof Uint8Array)) throw new Error("candidate task output capture must contain a byte array");
|
|
1387
|
-
if (capture.bytes.byteLength > expected.maxBytes) throw new Error(`candidate task output exceeds the frozen ${expected.maxBytes}-byte maximum`);
|
|
1388
|
-
return Object.freeze({
|
|
1389
|
-
kind: "output",
|
|
1390
|
-
bytes: Uint8Array.from(capture.bytes)
|
|
1391
|
-
});
|
|
1392
|
-
}
|
|
1393
|
-
assertExactObjectKeys(capture, [
|
|
1394
|
-
"kind",
|
|
1395
|
-
"resultTree",
|
|
1396
|
-
"afterState",
|
|
1397
|
-
"archive",
|
|
1398
|
-
"gitDiff"
|
|
1399
|
-
], "candidate task capture");
|
|
1400
|
-
if (capture.kind !== "workspace") throw new Error("candidate task capture has an invalid outcome kind");
|
|
1401
|
-
if (expected.kind !== "workspace") throw new Error("candidate workspace capture does not match the signed outcome kind");
|
|
1402
|
-
if (typeof capture.resultTree !== "string" || !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(capture.resultTree)) throw new Error("candidate task capture resultTree is not a Git object id");
|
|
1403
|
-
if (!(capture.archive instanceof Uint8Array) || !(capture.gitDiff instanceof Uint8Array)) throw new Error("candidate task capture archive and gitDiff must be byte arrays");
|
|
1404
|
-
const afterState = agentCandidateWorkspaceManifestMaterialSchema.parse(capture.afterState);
|
|
1405
|
-
return Object.freeze({
|
|
1406
|
-
kind: "workspace",
|
|
1407
|
-
resultTree: capture.resultTree,
|
|
1408
|
-
afterState: Object.freeze(afterState),
|
|
1409
|
-
archive: Uint8Array.from(capture.archive),
|
|
1410
|
-
gitDiff: Uint8Array.from(capture.gitDiff)
|
|
1411
|
-
});
|
|
1412
|
-
}
|
|
1413
|
-
function requireRecord(value, label) {
|
|
1414
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
|
|
1415
|
-
return value;
|
|
1416
|
-
}
|
|
1417
|
-
//#endregion
|
|
1418
|
-
//#region src/candidate-execution/model-settlement.ts
|
|
1419
|
-
const USD_NANOS = 1e9;
|
|
1420
|
-
/** Validate and detach the evaluator gateway's terminal, revoked call ledger. */
|
|
1421
|
-
function sealAgentCandidateModelSettlement(settlement, expected) {
|
|
1422
|
-
assertExactObjectKeys(settlement, [
|
|
1423
|
-
"preparationId",
|
|
1424
|
-
"grantDigest",
|
|
1425
|
-
"closed",
|
|
1426
|
-
"usageWithinLimits",
|
|
1427
|
-
"calls"
|
|
1428
|
-
], "model settlement");
|
|
1429
|
-
if (settlement.closed !== true) throw new Error("protected model grant is not closed");
|
|
1430
|
-
if (settlement.usageWithinLimits !== true) throw new Error("protected model settlement reports usage outside frozen limits");
|
|
1431
|
-
if (settlement.grantDigest !== expected.grantDigest) throw new Error("protected model settlement grant digest does not match the reservation");
|
|
1432
|
-
if (settlement.preparationId !== expected.preparationId) throw new Error("protected model settlement preparation does not match the reservation");
|
|
1433
|
-
if (!Array.isArray(settlement.calls)) throw new Error("protected model settlement calls must be an array");
|
|
1434
|
-
const callIds = /* @__PURE__ */ new Set();
|
|
1435
|
-
const spanIds = /* @__PURE__ */ new Set();
|
|
1436
|
-
let accountedInputTokens = 0;
|
|
1437
|
-
let outputTokens = 0;
|
|
1438
|
-
let cachedInputTokens = 0;
|
|
1439
|
-
let reasoningTokens = 0;
|
|
1440
|
-
let costUsdNanos = 0;
|
|
1441
|
-
let costProvenance = "observed";
|
|
1442
|
-
const calls = settlement.calls.map((source, index) => {
|
|
1443
|
-
assertExactObjectKeys(source, [
|
|
1444
|
-
"callId",
|
|
1445
|
-
"generationId",
|
|
1446
|
-
"traceSpanId",
|
|
1447
|
-
"status",
|
|
1448
|
-
"model",
|
|
1449
|
-
"startedAtMs",
|
|
1450
|
-
"endedAtMs",
|
|
1451
|
-
"inputTokens",
|
|
1452
|
-
"accountedInputTokens",
|
|
1453
|
-
"outputTokens",
|
|
1454
|
-
"cachedInputTokens",
|
|
1455
|
-
"reasoningTokens",
|
|
1456
|
-
"costUsdNanos",
|
|
1457
|
-
"costProvenance"
|
|
1458
|
-
], `model settlement call ${index}`);
|
|
1459
|
-
assertIdentifier(source.callId, `model settlement call ${index} callId`);
|
|
1460
|
-
assertIdentifier(source.generationId, `model settlement call ${index} generationId`);
|
|
1461
|
-
assertIdentifier(source.traceSpanId, `model settlement call ${index} traceSpanId`);
|
|
1462
|
-
if (source.traceSpanId !== source.generationId) throw new Error(`model settlement call ${index} traceSpanId is not its router generationId`);
|
|
1463
|
-
if (source.status !== "succeeded" && source.status !== "failed") throw new Error(`model settlement call ${index} has an invalid status`);
|
|
1464
|
-
if (callIds.has(source.callId)) throw new Error("protected model settlement has duplicate call ids");
|
|
1465
|
-
if (spanIds.has(source.traceSpanId)) throw new Error("protected model settlement has duplicate trace span ids");
|
|
1466
|
-
callIds.add(source.callId);
|
|
1467
|
-
spanIds.add(source.traceSpanId);
|
|
1468
|
-
if (source.model !== expected.model) throw new Error(`protected model settlement call ${index} has an unexpected model`);
|
|
1469
|
-
assertTimestamp(source.startedAtMs, `model settlement call ${index} startedAtMs`);
|
|
1470
|
-
assertTimestamp(source.endedAtMs, `model settlement call ${index} endedAtMs`);
|
|
1471
|
-
if (source.endedAtMs < source.startedAtMs) throw new Error(`model settlement call ${index} ended before it started`);
|
|
1472
|
-
assertCount(source.inputTokens, `model settlement call ${index} inputTokens`);
|
|
1473
|
-
assertCount(source.accountedInputTokens, `model settlement call ${index} accountedInputTokens`);
|
|
1474
|
-
if (source.accountedInputTokens < source.inputTokens) throw new Error(`model settlement call ${index} accountedInputTokens cannot be less than inputTokens`);
|
|
1475
|
-
assertCount(source.outputTokens, `model settlement call ${index} outputTokens`);
|
|
1476
|
-
assertCount(source.cachedInputTokens, `model settlement call ${index} cachedInputTokens`);
|
|
1477
|
-
cachedInputTokens = safeAdd(cachedInputTokens, source.cachedInputTokens, "cached input token total");
|
|
1478
|
-
assertCount(source.reasoningTokens, `model settlement call ${index} reasoningTokens`);
|
|
1479
|
-
reasoningTokens = safeAdd(reasoningTokens, source.reasoningTokens, "reasoning token total");
|
|
1480
|
-
assertCount(source.costUsdNanos, `model settlement call ${index} costUsdNanos`);
|
|
1481
|
-
if (source.costProvenance !== "observed" && source.costProvenance !== "estimated") throw new Error(`model settlement call ${index} has an invalid cost provenance`);
|
|
1482
|
-
if (source.costProvenance === "estimated") costProvenance = "estimated";
|
|
1483
|
-
accountedInputTokens = safeAdd(accountedInputTokens, source.accountedInputTokens, "accounted input token total");
|
|
1484
|
-
outputTokens = safeAdd(outputTokens, source.outputTokens, "output token total");
|
|
1485
|
-
costUsdNanos = safeAdd(costUsdNanos, source.costUsdNanos, "cost total");
|
|
1486
|
-
return Object.freeze({ ...source });
|
|
1487
|
-
});
|
|
1488
|
-
const usage = Object.freeze({
|
|
1489
|
-
costUsdNanos,
|
|
1490
|
-
inputTokens: accountedInputTokens,
|
|
1491
|
-
outputTokens,
|
|
1492
|
-
cachedInputTokens,
|
|
1493
|
-
reasoningTokens,
|
|
1494
|
-
modelCalls: calls.length,
|
|
1495
|
-
costProvenance
|
|
1496
|
-
});
|
|
1497
|
-
return Object.freeze({
|
|
1498
|
-
value: Object.freeze({
|
|
1499
|
-
preparationId: settlement.preparationId,
|
|
1500
|
-
grantDigest: settlement.grantDigest,
|
|
1501
|
-
closed: true,
|
|
1502
|
-
usageWithinLimits: true,
|
|
1503
|
-
calls: Object.freeze(calls)
|
|
1504
|
-
}),
|
|
1505
|
-
usage
|
|
1506
|
-
});
|
|
1507
|
-
}
|
|
1508
|
-
/**
|
|
1509
|
-
* Append the only accepted LLM spans from the router's closed ledger.
|
|
1510
|
-
* Candidate and executor code may write tool/process spans, but never model usage.
|
|
1511
|
-
*/
|
|
1512
|
-
async function appendAuthoritativeModelSettlementSpans(traceStore, runId, settlement) {
|
|
1513
|
-
const run = await traceStore.getRun(runId);
|
|
1514
|
-
if (!run) throw new Error(`protected trace run is missing before model settlement: ${runId}`);
|
|
1515
|
-
if (run.status === "running" || run.endedAt === void 0) throw new Error("protected trace run must be terminal before model spans are appended");
|
|
1516
|
-
const existing = await traceStore.spans({ runId });
|
|
1517
|
-
if (existing.some(isLlmSpan)) throw new Error("protected trace contains a model span not authored from the closed router ledger");
|
|
1518
|
-
const occupiedIds = new Set(existing.map((span) => span.spanId));
|
|
1519
|
-
for (const call of settlement.value.calls) {
|
|
1520
|
-
if (occupiedIds.has(call.traceSpanId)) throw new Error(`protected trace span identity collides with router generation ${call.generationId}`);
|
|
1521
|
-
await traceStore.appendSpan({
|
|
1522
|
-
runId,
|
|
1523
|
-
spanId: call.traceSpanId,
|
|
1524
|
-
kind: "llm",
|
|
1525
|
-
name: "protected model call",
|
|
1526
|
-
model: call.model,
|
|
1527
|
-
messages: [],
|
|
1528
|
-
startedAt: call.startedAtMs,
|
|
1529
|
-
endedAt: call.endedAtMs,
|
|
1530
|
-
status: call.status === "succeeded" ? "ok" : "error",
|
|
1531
|
-
inputTokens: call.inputTokens,
|
|
1532
|
-
outputTokens: call.outputTokens,
|
|
1533
|
-
cachedTokens: call.cachedInputTokens,
|
|
1534
|
-
reasoningTokens: call.reasoningTokens,
|
|
1535
|
-
costUsd: call.costUsdNanos / USD_NANOS,
|
|
1536
|
-
attributes: {
|
|
1537
|
-
"tangle.protected_model.source": "router-settlement",
|
|
1538
|
-
"tangle.router.call_id": call.callId,
|
|
1539
|
-
"tangle.router.generation_id": call.generationId
|
|
1540
|
-
}
|
|
1541
|
-
});
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
/** Match every protected trace span one-for-one against gateway call evidence. */
|
|
1545
|
-
function assertTraceMatchesModelSettlement(spans, settlement) {
|
|
1546
|
-
if (spans.length !== settlement.value.calls.length) throw new Error(`protected trace model calls ${spans.length} do not match model ledger ${settlement.value.calls.length}`);
|
|
1547
|
-
const byId = new Map(spans.map((span) => [span.spanId, span]));
|
|
1548
|
-
if (byId.size !== spans.length) throw new Error("protected trace has duplicate model span ids");
|
|
1549
|
-
for (const call of settlement.value.calls) {
|
|
1550
|
-
const span = byId.get(call.traceSpanId);
|
|
1551
|
-
if (!span) throw new Error(`protected trace is missing model ledger span ${call.traceSpanId}`);
|
|
1552
|
-
assertTraceCall(span, call);
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
1555
|
-
function usdToNanos(value, label) {
|
|
1556
|
-
if (!Number.isFinite(value) || value < 0) throw new Error(`${label} must be nonnegative`);
|
|
1557
|
-
const nanos = Math.round(value * USD_NANOS);
|
|
1558
|
-
if (!Number.isSafeInteger(nanos)) throw new Error(`${label} exceeds fixed-point range`);
|
|
1559
|
-
return nanos;
|
|
1560
|
-
}
|
|
1561
|
-
function assertTraceCall(span, call) {
|
|
1562
|
-
if (span.model !== call.model) throw new Error(`protected trace span ${span.spanId} model does not match model ledger`);
|
|
1563
|
-
if (span.startedAt !== call.startedAtMs || span.endedAt !== call.endedAtMs || span.status !== (call.status === "succeeded" ? "ok" : "error")) throw new Error(`protected trace span ${span.spanId} timing or status does not match model ledger`);
|
|
1564
|
-
if (span.attributes?.["tangle.protected_model.source"] !== "router-settlement" || span.attributes?.["tangle.router.call_id"] !== call.callId || span.attributes?.["tangle.router.generation_id"] !== call.generationId) throw new Error(`protected trace span ${span.spanId} lacks router settlement provenance`);
|
|
1565
|
-
for (const [name, traced, settled] of [
|
|
1566
|
-
[
|
|
1567
|
-
"inputTokens",
|
|
1568
|
-
span.inputTokens,
|
|
1569
|
-
call.inputTokens
|
|
1570
|
-
],
|
|
1571
|
-
[
|
|
1572
|
-
"outputTokens",
|
|
1573
|
-
span.outputTokens,
|
|
1574
|
-
call.outputTokens
|
|
1575
|
-
],
|
|
1576
|
-
[
|
|
1577
|
-
"cachedInputTokens",
|
|
1578
|
-
span.cachedTokens ?? 0,
|
|
1579
|
-
call.cachedInputTokens
|
|
1580
|
-
],
|
|
1581
|
-
[
|
|
1582
|
-
"reasoningTokens",
|
|
1583
|
-
span.reasoningTokens ?? 0,
|
|
1584
|
-
call.reasoningTokens
|
|
1585
|
-
]
|
|
1586
|
-
]) if (traced === void 0 || traced !== settled) throw new Error(`protected trace span ${span.spanId} ${name} ${traced} does not match model ledger ${settled}`);
|
|
1587
|
-
if (span.costUsd === void 0) throw new Error(`protected trace span ${span.spanId} is missing costUsd`);
|
|
1588
|
-
const tracedCost = usdToNanos(span.costUsd, `protected trace span ${span.spanId} costUsd`);
|
|
1589
|
-
if (tracedCost !== call.costUsdNanos) throw new Error(`protected trace span ${span.spanId} costUsdNanos ${tracedCost} does not match model ledger ${call.costUsdNanos}`);
|
|
1590
|
-
}
|
|
1591
|
-
function assertIdentifier(value, label) {
|
|
1592
|
-
if (typeof value !== "string" || value.length === 0 || value.length > 256) throw new Error(`${label} must be a non-empty bounded string`);
|
|
1593
|
-
}
|
|
1594
|
-
function assertCount(value, label) {
|
|
1595
|
-
if (!Number.isSafeInteger(value) || value < 0) throw new Error(`${label} must be a nonnegative safe integer`);
|
|
1596
|
-
}
|
|
1597
|
-
function assertTimestamp(value, label) {
|
|
1598
|
-
if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`${label} must be a positive safe integer`);
|
|
2491
|
+
if (!(capture.bytes instanceof Uint8Array)) throw new Error("candidate task output capture must contain a byte array");
|
|
2492
|
+
if (capture.bytes.byteLength > expected.maxBytes) throw new Error(`candidate task output exceeds the frozen ${expected.maxBytes}-byte maximum`);
|
|
2493
|
+
return Object.freeze({
|
|
2494
|
+
kind: "output",
|
|
2495
|
+
bytes: Uint8Array.from(capture.bytes)
|
|
2496
|
+
});
|
|
2497
|
+
}
|
|
2498
|
+
assertExactObjectKeys(capture, [
|
|
2499
|
+
"kind",
|
|
2500
|
+
"resultTree",
|
|
2501
|
+
"afterState",
|
|
2502
|
+
"archive",
|
|
2503
|
+
"gitDiff"
|
|
2504
|
+
], "candidate task capture");
|
|
2505
|
+
if (capture.kind !== "workspace") throw new Error("candidate task capture has an invalid outcome kind");
|
|
2506
|
+
if (expected.kind !== "workspace") throw new Error("candidate workspace capture does not match the signed outcome kind");
|
|
2507
|
+
if (typeof capture.resultTree !== "string" || !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(capture.resultTree)) throw new Error("candidate task capture resultTree is not a Git object id");
|
|
2508
|
+
if (!(capture.archive instanceof Uint8Array) || !(capture.gitDiff instanceof Uint8Array)) throw new Error("candidate task capture archive and gitDiff must be byte arrays");
|
|
2509
|
+
const afterState = agentCandidateWorkspaceManifestMaterialSchema.parse(capture.afterState);
|
|
2510
|
+
return Object.freeze({
|
|
2511
|
+
kind: "workspace",
|
|
2512
|
+
resultTree: capture.resultTree,
|
|
2513
|
+
afterState: Object.freeze(afterState),
|
|
2514
|
+
archive: Uint8Array.from(capture.archive),
|
|
2515
|
+
gitDiff: Uint8Array.from(capture.gitDiff)
|
|
2516
|
+
});
|
|
1599
2517
|
}
|
|
1600
|
-
function
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
return total;
|
|
2518
|
+
function requireRecord(value, label) {
|
|
2519
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
|
|
2520
|
+
return value;
|
|
1604
2521
|
}
|
|
1605
2522
|
//#endregion
|
|
1606
2523
|
//#region src/candidate-execution/finalize.ts
|
|
@@ -2286,6 +3203,22 @@ function normalizeEvaluation(evaluation, termination) {
|
|
|
2286
3203
|
function assertUnitScore(value, label) {
|
|
2287
3204
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1) throw new Error(`${label} must be finite and within [0, 1]`);
|
|
2288
3205
|
}
|
|
3206
|
+
async function persistFailureEvidence(state, reason, failureClass, termination, outputArtifacts) {
|
|
3207
|
+
const bytes = canonicalCandidateBytes$1({
|
|
3208
|
+
kind: "agent-candidate-execution-failure",
|
|
3209
|
+
executionId: state.executionId,
|
|
3210
|
+
bundleDigest: state.bundle.digest,
|
|
3211
|
+
executionPlanDigest: state.executionPlan.value.digest,
|
|
3212
|
+
failureClass,
|
|
3213
|
+
reason,
|
|
3214
|
+
...termination ? { termination } : {}
|
|
3215
|
+
});
|
|
3216
|
+
return await persistCandidateOutputArtifact(outputArtifacts, {
|
|
3217
|
+
executionId: state.executionId,
|
|
3218
|
+
purpose: "failure-evidence",
|
|
3219
|
+
bytes
|
|
3220
|
+
});
|
|
3221
|
+
}
|
|
2289
3222
|
//#endregion
|
|
2290
3223
|
//#region src/candidate-execution/protected-trace-store.ts
|
|
2291
3224
|
/** Keep the identity key in evaluator-owned closures and expose only the redacting store to code. */
|
|
@@ -2791,22 +3724,6 @@ async function finishClaim(store, lease, terminal, deadlineAtMs) {
|
|
|
2791
3724
|
return false;
|
|
2792
3725
|
}
|
|
2793
3726
|
}
|
|
2794
|
-
async function persistFailureEvidence(state, reason, failureClass, termination, outputArtifacts) {
|
|
2795
|
-
const bytes = canonicalCandidateBytes$1({
|
|
2796
|
-
kind: "agent-candidate-execution-failure",
|
|
2797
|
-
executionId: state.executionId,
|
|
2798
|
-
bundleDigest: state.bundle.digest,
|
|
2799
|
-
executionPlanDigest: state.executionPlan.value.digest,
|
|
2800
|
-
failureClass,
|
|
2801
|
-
reason,
|
|
2802
|
-
...termination ? { termination } : {}
|
|
2803
|
-
});
|
|
2804
|
-
return await persistCandidateOutputArtifact(outputArtifacts, {
|
|
2805
|
-
executionId: state.executionId,
|
|
2806
|
-
purpose: "failure-evidence",
|
|
2807
|
-
bytes
|
|
2808
|
-
});
|
|
2809
|
-
}
|
|
2810
3727
|
function protectedEnvironmentValues(activation, memoryActivation) {
|
|
2811
3728
|
return [...Object.values(activation?.env ?? {}), ...Object.values(memoryActivation?.env ?? {})];
|
|
2812
3729
|
}
|
|
@@ -2820,171 +3737,6 @@ var CandidateExecutionDeadlineError = class extends Error {
|
|
|
2820
3737
|
}
|
|
2821
3738
|
};
|
|
2822
3739
|
//#endregion
|
|
2823
|
-
//#region src/candidate-execution/verify.ts
|
|
2824
|
-
const verifiedCandidateState = /* @__PURE__ */ new WeakMap();
|
|
2825
|
-
/** Surfaces admitted by Runtime's verifier before an environment adapter is selected. */
|
|
2826
|
-
const AGENT_CANDIDATE_EXECUTION_SUPPORT = Object.freeze({
|
|
2827
|
-
outcomes: Object.freeze(["workspace", "output"]),
|
|
2828
|
-
code: Object.freeze([
|
|
2829
|
-
"disabled",
|
|
2830
|
-
"no-op",
|
|
2831
|
-
"git-patch"
|
|
2832
|
-
]),
|
|
2833
|
-
memory: Object.freeze(["disabled", "isolated"]),
|
|
2834
|
-
knowledge: true,
|
|
2835
|
-
profile: Object.freeze({
|
|
2836
|
-
mcpTransports: Object.freeze(["stdio"]),
|
|
2837
|
-
remoteMcp: false,
|
|
2838
|
-
tools: false,
|
|
2839
|
-
permissions: false,
|
|
2840
|
-
modes: false,
|
|
2841
|
-
confidential: false
|
|
2842
|
-
})
|
|
2843
|
-
});
|
|
2844
|
-
/** Verifies every digest, resource, workspace, and Git object in a candidate bundle. */
|
|
2845
|
-
async function verifyAgentCandidateBundle(input, ports) {
|
|
2846
|
-
const parsed = agentCandidateBundleSchema.parse(input);
|
|
2847
|
-
const withoutDigest = omitTopLevelDigest(parsed);
|
|
2848
|
-
const actualDigest = canonicalCandidateDigest$1(withoutDigest);
|
|
2849
|
-
if (actualDigest !== parsed.digest) throw new Error(`candidate bundle digest ${parsed.digest} does not match ${actualDigest}`);
|
|
2850
|
-
const canonicalBytes = canonicalCandidateBytes$1(withoutDigest);
|
|
2851
|
-
verifyBytes(canonicalBytes, parsed.digest, canonicalBytes.byteLength, "candidate bundle");
|
|
2852
|
-
assertCandidateProfileExecutionSupport(parsed.profile);
|
|
2853
|
-
const artifactBytes = /* @__PURE__ */ new Map();
|
|
2854
|
-
const readArtifact = async (artifact) => {
|
|
2855
|
-
const key = artifactCacheKey(artifact);
|
|
2856
|
-
const existing = artifactBytes.get(key);
|
|
2857
|
-
if (existing) return Uint8Array.from(existing);
|
|
2858
|
-
const bytes = await readVerifiedArtifact(artifact, ports.artifacts);
|
|
2859
|
-
artifactBytes.set(key, Uint8Array.from(bytes));
|
|
2860
|
-
return bytes;
|
|
2861
|
-
};
|
|
2862
|
-
let patchBytes;
|
|
2863
|
-
if (parsed.code.kind === "git-patch") patchBytes = await readArtifact(parsed.code.patch.artifact);
|
|
2864
|
-
const materializedTree = await verifyCandidateCode(parsed.code, ports.repositories, patchBytes);
|
|
2865
|
-
const resourceBytes = /* @__PURE__ */ new Map();
|
|
2866
|
-
for (const resource of candidateResources(parsed)) {
|
|
2867
|
-
const bytes = resource.kind === "inline" ? Buffer.from(resource.content, "utf8") : await readCandidateGitHubResource(resource, ports.repositories);
|
|
2868
|
-
verifyBytes(bytes, resource.sha256, resource.byteLength, `candidate resource ${resource.name ?? (resource.kind === "github" ? resource.path : "<unnamed>")}`);
|
|
2869
|
-
resourceBytes.set(resourceKey(resource), Uint8Array.from(bytes));
|
|
2870
|
-
resourceBytes.set(resource.sha256, Uint8Array.from(bytes));
|
|
2871
|
-
}
|
|
2872
|
-
if (parsed.execution.workspace) {
|
|
2873
|
-
const workspace = await verifyWorkspaceSnapshotArtifacts(parsed.execution.workspace, ports.artifacts);
|
|
2874
|
-
artifactBytes.set(artifactCacheKey(parsed.execution.workspace.manifest), workspace.manifest);
|
|
2875
|
-
artifactBytes.set(artifactCacheKey(parsed.execution.workspace.archive), workspace.archive);
|
|
2876
|
-
}
|
|
2877
|
-
if (parsed.knowledge) {
|
|
2878
|
-
const knowledge = await verifyWorkspaceSnapshotArtifacts(parsed.knowledge.snapshot, ports.artifacts);
|
|
2879
|
-
artifactBytes.set(artifactCacheKey(parsed.knowledge.snapshot.manifest), knowledge.manifest);
|
|
2880
|
-
artifactBytes.set(artifactCacheKey(parsed.knowledge.snapshot.archive), knowledge.archive);
|
|
2881
|
-
if (parsed.knowledge.retrievalConfig) await readArtifact(parsed.knowledge.retrievalConfig);
|
|
2882
|
-
await readArtifact(parsed.knowledge.evaluation);
|
|
2883
|
-
}
|
|
2884
|
-
if (parsed.memory.mode === "isolated" && parsed.memory.seed) await readArtifact(parsed.memory.seed);
|
|
2885
|
-
deepFreezeCandidate(parsed);
|
|
2886
|
-
const verified = Object.freeze({
|
|
2887
|
-
bundle: parsed,
|
|
2888
|
-
...materializedTree === void 0 ? {} : { materializedTree },
|
|
2889
|
-
[verifiedCandidateBrand]: true
|
|
2890
|
-
});
|
|
2891
|
-
verifiedCandidateState.set(verified, {
|
|
2892
|
-
ports,
|
|
2893
|
-
artifactBytes,
|
|
2894
|
-
resourceBytes
|
|
2895
|
-
});
|
|
2896
|
-
return verified;
|
|
2897
|
-
}
|
|
2898
|
-
function getVerifiedCandidateState(candidate) {
|
|
2899
|
-
const state = verifiedCandidateState.get(candidate);
|
|
2900
|
-
if (!state || candidate[verifiedCandidateBrand] !== true) throw new Error("candidate must come from verifyAgentCandidateBundle");
|
|
2901
|
-
return state;
|
|
2902
|
-
}
|
|
2903
|
-
async function verifiedArtifactBytes(candidate, artifact) {
|
|
2904
|
-
const state = getVerifiedCandidateState(candidate);
|
|
2905
|
-
const key = artifactCacheKey(artifact);
|
|
2906
|
-
const existing = state.artifactBytes.get(key);
|
|
2907
|
-
if (existing) return Uint8Array.from(existing);
|
|
2908
|
-
const bytes = await readVerifiedArtifact(artifact, state.ports.artifacts);
|
|
2909
|
-
state.artifactBytes.set(key, Uint8Array.from(bytes));
|
|
2910
|
-
return bytes;
|
|
2911
|
-
}
|
|
2912
|
-
function verifiedResourceTextByDigest(candidate) {
|
|
2913
|
-
const output = /* @__PURE__ */ new Map();
|
|
2914
|
-
for (const resource of candidateResources(candidate.bundle)) {
|
|
2915
|
-
const bytes = getVerifiedCandidateState(candidate).resourceBytes.get(resource.sha256);
|
|
2916
|
-
if (!bytes) throw new Error(`candidate resource digest was not verified: ${resource.sha256}`);
|
|
2917
|
-
output.set(resource.sha256, new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
2918
|
-
}
|
|
2919
|
-
return output;
|
|
2920
|
-
}
|
|
2921
|
-
function candidateResources(bundle) {
|
|
2922
|
-
const resources = bundle.profile.resources;
|
|
2923
|
-
if (!resources) return [];
|
|
2924
|
-
const output = [];
|
|
2925
|
-
for (const mount of resources.files ?? []) output.push(mount.resource);
|
|
2926
|
-
output.push(...resources.tools ?? []);
|
|
2927
|
-
output.push(...resources.skills ?? []);
|
|
2928
|
-
output.push(...resources.agents ?? []);
|
|
2929
|
-
output.push(...resources.commands ?? []);
|
|
2930
|
-
if (typeof resources.instructions === "object") output.push(resources.instructions);
|
|
2931
|
-
return output;
|
|
2932
|
-
}
|
|
2933
|
-
function resourceKey(resource) {
|
|
2934
|
-
return canonicalCandidateDigest$1(resource);
|
|
2935
|
-
}
|
|
2936
|
-
//#endregion
|
|
2937
|
-
//#region src/candidate-execution/knowledge.ts
|
|
2938
|
-
/** Environment variable containing the materialized candidate knowledge root. */
|
|
2939
|
-
const CANDIDATE_KNOWLEDGE_ROOT_ENV = "TANGLE_CANDIDATE_KNOWLEDGE_ROOT";
|
|
2940
|
-
/** Environment variable containing the materialized retrieval configuration path. */
|
|
2941
|
-
const CANDIDATE_KNOWLEDGE_RETRIEVAL_CONFIG_ENV = "TANGLE_CANDIDATE_KNOWLEDGE_RETRIEVAL_CONFIG";
|
|
2942
|
-
/** Deterministic, signed locations used by every candidate executor. */
|
|
2943
|
-
function candidateKnowledgeExecutionPaths(taskRoot, hasRetrievalConfig) {
|
|
2944
|
-
if (!posix.isAbsolute(taskRoot) || posix.normalize(taskRoot) !== taskRoot) throw new Error("candidate knowledge requires a normalized absolute task root");
|
|
2945
|
-
const parent = posix.join(taskRoot, ".tangle");
|
|
2946
|
-
return {
|
|
2947
|
-
root: posix.join(parent, "knowledge"),
|
|
2948
|
-
...hasRetrievalConfig ? { retrievalConfig: posix.join(parent, "knowledge-retrieval-config.json") } : {}
|
|
2949
|
-
};
|
|
2950
|
-
}
|
|
2951
|
-
/** Verify and detach the exact file-backed knowledge admitted by the bundle. */
|
|
2952
|
-
async function prepareAgentCandidateKnowledge(candidate, ports, arm) {
|
|
2953
|
-
const knowledge = candidate.bundle.knowledge;
|
|
2954
|
-
if (!knowledge) return void 0;
|
|
2955
|
-
const archive = await verifiedArtifactBytes(candidate, knowledge.snapshot.archive);
|
|
2956
|
-
const retrievalConfig = knowledge.retrievalConfig ? await verifiedArtifactBytes(candidate, knowledge.retrievalConfig) : void 0;
|
|
2957
|
-
const root = await mkdtemp(join(tmpdir(), "agent-candidate-knowledge-"));
|
|
2958
|
-
try {
|
|
2959
|
-
await ports.workspaces.materialize({
|
|
2960
|
-
role: "knowledge",
|
|
2961
|
-
snapshot: knowledge.snapshot,
|
|
2962
|
-
archive,
|
|
2963
|
-
destination: root
|
|
2964
|
-
});
|
|
2965
|
-
const expectedHash = arm === "baseline" ? knowledge.candidate.baseHash : knowledge.candidate.candidateHash;
|
|
2966
|
-
const actualHash = `sha256:${await hashKnowledgeBase(root, knowledge.stateScope)}`;
|
|
2967
|
-
if (actualHash !== expectedHash) throw new Error(`materialized ${arm} knowledge does not match its measured content: expected ${expectedHash}, got ${actualHash}`);
|
|
2968
|
-
const files = await readMaterializedWorkspaceFiles(root, knowledge.snapshot.material);
|
|
2969
|
-
return Object.freeze({
|
|
2970
|
-
candidate: knowledge.candidate,
|
|
2971
|
-
...knowledge.stateScope ? { stateScope: Object.freeze({ ...knowledge.stateScope }) } : {},
|
|
2972
|
-
snapshot: knowledge.snapshot,
|
|
2973
|
-
files: Object.freeze(files.map((file) => Object.freeze({
|
|
2974
|
-
path: file.path,
|
|
2975
|
-
mode: file.mode,
|
|
2976
|
-
bytes: Uint8Array.from(file.bytes)
|
|
2977
|
-
}))),
|
|
2978
|
-
...retrievalConfig ? { retrievalConfig: Uint8Array.from(retrievalConfig) } : {}
|
|
2979
|
-
});
|
|
2980
|
-
} finally {
|
|
2981
|
-
await rm(root, {
|
|
2982
|
-
recursive: true,
|
|
2983
|
-
force: true
|
|
2984
|
-
});
|
|
2985
|
-
}
|
|
2986
|
-
}
|
|
2987
|
-
//#endregion
|
|
2988
3740
|
//#region src/candidate-execution/prepare.ts
|
|
2989
3741
|
const MIN_RESERVATION_TTL_MS = 15 * 6e4;
|
|
2990
3742
|
const PREPARED_HOLD_MARGIN_MS = 5 * 6e4;
|
|
@@ -3454,32 +4206,6 @@ async function prepareMemory(candidate, task, ports, preparationId, expiresAtMs,
|
|
|
3454
4206
|
throw error;
|
|
3455
4207
|
}
|
|
3456
4208
|
}
|
|
3457
|
-
function buildLaunch(candidate, task, profileFlags) {
|
|
3458
|
-
const launch = candidate.bundle.execution.launch;
|
|
3459
|
-
if (launch.kind === "container-command") return {
|
|
3460
|
-
executable: launch.executable,
|
|
3461
|
-
args: [...launch.args ?? [], ...profileFlags]
|
|
3462
|
-
};
|
|
3463
|
-
const candidateRoot = task.executionRoots.candidateRoot;
|
|
3464
|
-
if (!candidateRoot) throw new Error("candidate entrypoint requires a container candidate root");
|
|
3465
|
-
const entrypoint = posix.join(candidateRoot, launch.entrypoint);
|
|
3466
|
-
const candidateArgs = launch.args ?? [];
|
|
3467
|
-
if (launch.interpreter) return {
|
|
3468
|
-
executable: launch.interpreter,
|
|
3469
|
-
args: [
|
|
3470
|
-
{
|
|
3471
|
-
kind: "public",
|
|
3472
|
-
value: entrypoint
|
|
3473
|
-
},
|
|
3474
|
-
...candidateArgs,
|
|
3475
|
-
...profileFlags
|
|
3476
|
-
]
|
|
3477
|
-
};
|
|
3478
|
-
return {
|
|
3479
|
-
executable: entrypoint,
|
|
3480
|
-
args: [...candidateArgs, ...profileFlags]
|
|
3481
|
-
};
|
|
3482
|
-
}
|
|
3483
4209
|
function mergePublicEnvironment(...records) {
|
|
3484
4210
|
const output = {};
|
|
3485
4211
|
for (const record of records) for (const [name, value] of Object.entries(record)) {
|
|
@@ -3493,50 +4219,6 @@ function mergePublicEnvironment(...records) {
|
|
|
3493
4219
|
function unwrapPublicEnvironment(values) {
|
|
3494
4220
|
return Object.fromEntries(Object.entries(values).map(([name, value]) => [name, requirePublicCandidateConfigValue(value, `env.${name}`)]));
|
|
3495
4221
|
}
|
|
3496
|
-
function modelRoutes(profile, requested) {
|
|
3497
|
-
const routes = [{
|
|
3498
|
-
kind: "primary",
|
|
3499
|
-
requested
|
|
3500
|
-
}];
|
|
3501
|
-
if (profile.model?.small) routes.push({
|
|
3502
|
-
kind: "small",
|
|
3503
|
-
requested
|
|
3504
|
-
});
|
|
3505
|
-
for (const name of Object.keys(profile.modes ?? {}).sort()) if (profile.modes?.[name]?.model) routes.push({
|
|
3506
|
-
kind: "mode",
|
|
3507
|
-
name,
|
|
3508
|
-
requested
|
|
3509
|
-
});
|
|
3510
|
-
for (const name of Object.keys(profile.subagents ?? {}).sort()) if (profile.subagents?.[name]?.model) routes.push({
|
|
3511
|
-
kind: "subagent",
|
|
3512
|
-
name,
|
|
3513
|
-
requested
|
|
3514
|
-
});
|
|
3515
|
-
return routes;
|
|
3516
|
-
}
|
|
3517
|
-
function candidateEntrypointReceipt(candidate) {
|
|
3518
|
-
const launch = candidate.bundle.execution.launch;
|
|
3519
|
-
const workspace = candidate.bundle.execution.workspace;
|
|
3520
|
-
if (launch.kind !== "candidate-entrypoint" || !workspace) return void 0;
|
|
3521
|
-
const file = workspace.material.files.find((entry) => entry.path === launch.entrypoint);
|
|
3522
|
-
if (!file) throw new Error("candidate entrypoint is absent from the verified workspace");
|
|
3523
|
-
return {
|
|
3524
|
-
path: file.path,
|
|
3525
|
-
sha256: file.sha256,
|
|
3526
|
-
byteLength: file.byteLength
|
|
3527
|
-
};
|
|
3528
|
-
}
|
|
3529
|
-
function absoluteExecutionCwd(cwd, roots) {
|
|
3530
|
-
const root = workspaceExecutionRoot(cwd.workspace, roots);
|
|
3531
|
-
const absolute = cwd.path === "." ? root : posix.join(root, cwd.path);
|
|
3532
|
-
if (absolute !== root && !absolute.startsWith(`${root}/`)) throw new Error("candidate cwd escapes its execution workspace");
|
|
3533
|
-
return absolute;
|
|
3534
|
-
}
|
|
3535
|
-
function workspaceExecutionRoot(workspace, roots) {
|
|
3536
|
-
const root = workspace === "task" ? roots.taskRoot : roots.candidateRoot;
|
|
3537
|
-
if (!root) throw new Error(`candidate ${workspace} workspace root is missing`);
|
|
3538
|
-
return root;
|
|
3539
|
-
}
|
|
3540
4222
|
function validateProtectedModelReservation(reservation, expectedLimits, preparationId, expiresAtMs) {
|
|
3541
4223
|
if (!/^sha256:[a-f0-9]{64}$/.test(reservation.digest)) throw new Error("protected model reservation has an invalid identity digest");
|
|
3542
4224
|
if (reservation.preparationId !== preparationId || reservation.expiresAtMs !== expiresAtMs) throw new Error("protected model reservation is not scoped to this preparation");
|
|
@@ -3552,34 +4234,7 @@ function assertEnvironmentDisjoint(publicEnv, traceEnv) {
|
|
|
3552
4234
|
seen.add(name);
|
|
3553
4235
|
}
|
|
3554
4236
|
}
|
|
3555
|
-
function modelLimits(limits) {
|
|
3556
|
-
return {
|
|
3557
|
-
maxModelCalls: limits.maxModelCalls,
|
|
3558
|
-
maxInputTokens: limits.maxInputTokens,
|
|
3559
|
-
maxOutputTokens: limits.maxOutputTokens,
|
|
3560
|
-
maxCostUsd: limits.maxCostUsd
|
|
3561
|
-
};
|
|
3562
|
-
}
|
|
3563
|
-
function exactProfileExecutorFiles(sourceFiles, expectedFiles) {
|
|
3564
|
-
const byIdentity = new Map(sourceFiles.map((file) => [profileFileIdentity(file.root, file.relPath), file]));
|
|
3565
|
-
if (byIdentity.size !== sourceFiles.length || sourceFiles.length !== expectedFiles.length) throw new Error("profile source files do not match the signed profile plan");
|
|
3566
|
-
return expectedFiles.map((expected) => {
|
|
3567
|
-
const source = byIdentity.get(profileFileIdentity(expected.root, expected.relPath));
|
|
3568
|
-
const mode = source?.mode ?? 420;
|
|
3569
|
-
const bytes = Buffer.from(source?.content ?? "", "utf8");
|
|
3570
|
-
if (!source || !Number.isInteger(mode) || mode < 0 || mode > 511 || mode !== expected.mode || sha256Bytes$1(bytes) !== expected.contentSha256) throw new Error("profile source files do not match the signed profile plan");
|
|
3571
|
-
return {
|
|
3572
|
-
path: expected.relPath,
|
|
3573
|
-
mode,
|
|
3574
|
-
...expected.root === "agent" ? { root: "agent" } : {},
|
|
3575
|
-
bytes: Uint8Array.from(bytes)
|
|
3576
|
-
};
|
|
3577
|
-
});
|
|
3578
|
-
}
|
|
3579
|
-
function profileFileIdentity(root, relPath) {
|
|
3580
|
-
return `${root ?? "workspace"}\0${relPath}`;
|
|
3581
|
-
}
|
|
3582
4237
|
//#endregion
|
|
3583
|
-
export {
|
|
4238
|
+
export { candidateKnowledgeExecutionPaths as A, sealTerminalDigest as C, exactProcessProviderAsCandidateExecutor as D, sealAgentCandidateModelSettlement as E, CANDIDATE_TRACE_TAGS as F, verifiedResourceTextByDigest as M, verifyAgentCandidateBundle as N, CANDIDATE_KNOWLEDGE_RETRIEVAL_CONFIG_ENV as O, CANDIDATE_TRACE_ENV as P, requireStagedTerminal as S, createProtectedAgentCandidateModelPort as T, assertTerminalMatchesClaim as _, sealAgentCandidateExecutorStopAcknowledgement as a, rejectedFinish as b, beginPreparedCandidateDisposal as c, candidateClaimFileInternals as d, candidateCleanupDeadline as f, assertTerminalAllowedInPhase as g, assertRecoveryMatchesStaged as h, persistCandidateModelSettlementEvidence as i, AGENT_CANDIDATE_EXECUTION_SUPPORT as j, CANDIDATE_KNOWLEDGE_ROOT_ENV as k, consumePreparedCandidateExecution as l, withinCandidateCleanupDeadline as m, executePreparedAgentCandidate as n, candidateExecutionClaim as o, candidateCleanupTimeout as p, RecoveryAgentCandidateTraceStore as r, assertPreparedCandidateIntegrity as s, prepareAgentCandidateExecution as t, InMemoryAgentCandidateExecutionClaimStore as u, assertTerminalMatchesStaged as v, terminalRecord as w, rejectedStage as x, recoveredTerminalRecord as y };
|
|
3584
4239
|
|
|
3585
|
-
//# sourceMappingURL=prepare-
|
|
4240
|
+
//# sourceMappingURL=prepare-Bt0IouPq.js.map
|