@tangle-network/agent-runtime 0.104.0 → 0.106.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/README.md +176 -42
- package/dist/activation-CRjVfOiy.js +203 -0
- package/dist/activation-CRjVfOiy.js.map +1 -0
- package/dist/activation-DyWB0K6E.d.ts +98 -0
- package/dist/agent.d.ts +216 -234
- package/dist/agent.js +615 -547
- package/dist/agent.js.map +1 -1
- package/dist/analyst-loop-DGdtVENg.js +517 -0
- package/dist/analyst-loop-DGdtVENg.js.map +1 -0
- package/dist/analyst-loop.d.ts +10 -43
- package/dist/analyst-loop.js +2 -11
- package/dist/backends-Dy8NujB4.js +713 -0
- package/dist/backends-Dy8NujB4.js.map +1 -0
- package/dist/candidate-execution/index.d.ts +4 -192
- package/dist/candidate-execution/index.js +5 -66
- package/dist/candidate-execution-DqMbv1rU.js +488 -0
- package/dist/candidate-execution-DqMbv1rU.js.map +1 -0
- package/dist/coder-yhVWbdWc.js +40 -0
- package/dist/coder-yhVWbdWc.js.map +1 -0
- package/dist/conversation-XucG_K3l.js +1265 -0
- package/dist/conversation-XucG_K3l.js.map +1 -0
- package/dist/conversation.d.ts +2 -804
- package/dist/conversation.js +2 -55
- package/dist/environment-provider-CfLUmKmf.js +1168 -0
- package/dist/environment-provider-CfLUmKmf.js.map +1 -0
- package/dist/environment-provider-IUGU3epE.d.ts +964 -0
- package/dist/environment-provider.d.ts +2 -78
- package/dist/environment-provider.js +2 -18
- package/dist/errors-DEAvWQPy.js +111 -0
- package/dist/errors-DEAvWQPy.js.map +1 -0
- package/dist/improvement-adapter-Cr5TBSji.d.ts +166 -0
- package/dist/improvement-cycle-C095kS_g.js +2624 -0
- package/dist/improvement-cycle-C095kS_g.js.map +1 -0
- package/dist/index-BGdxAwfO.d.ts +838 -0
- package/dist/index-C-FYUuFG.d.ts +193 -0
- package/dist/index-CbNM40xG.d.ts +140 -0
- package/dist/index-DruSKs3Z.d.ts +7224 -0
- package/dist/index-Efjb3nrQ.d.ts +671 -0
- package/dist/index.d.ts +14 -1134
- package/dist/index.js +2134 -1972
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +776 -888
- package/dist/intelligence.js +1466 -1410
- package/dist/intelligence.js.map +1 -1
- package/dist/{kb-gate-CwHO0vz6.d.ts → kb-gate-C8z2juK8.d.ts} +33 -31
- package/dist/kb-gate-DpaSwXVx.js +80 -0
- package/dist/kb-gate-DpaSwXVx.js.map +1 -0
- package/dist/knowledge-Ku-mPP4f.js +433 -0
- package/dist/knowledge-Ku-mPP4f.js.map +1 -0
- package/dist/knowledge.d.ts +2 -150
- package/dist/knowledge.js +2 -40
- package/dist/local-harness-Dh8PJ0ot.d.ts +455 -0
- package/dist/loop-runner-bin-BXsFEUcf.d.ts +162 -0
- package/dist/loop-runner-bin-BwjQ1NfH.js +268 -0
- package/dist/loop-runner-bin-BwjQ1NfH.js.map +1 -0
- package/dist/loop-runner-bin.d.ts +2 -17
- package/dist/loop-runner-bin.js +2 -24
- package/dist/loops.d.ts +5 -4164
- package/dist/loops.js +8 -419
- package/dist/mcp/bin.d.ts +1 -1
- package/dist/mcp/bin.js +116 -146
- package/dist/mcp/bin.js.map +1 -1
- package/dist/mcp/index.d.ts +295 -437
- package/dist/mcp/index.js +1242 -1177
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/memory-bin.d.ts +1 -1
- package/dist/mcp/memory-bin.js +37 -16
- package/dist/mcp/memory-bin.js.map +1 -1
- package/dist/memory-server-DL6cE2Ag.js +234 -0
- package/dist/memory-server-DL6cE2Ag.js.map +1 -0
- package/dist/model-resolution-Btd9iIKV.js +98 -0
- package/dist/model-resolution-Btd9iIKV.js.map +1 -0
- package/dist/openai-tools-B68JaOCx.d.ts +24 -0
- package/dist/openai-tools-CKLy1C7M.js +44 -0
- package/dist/openai-tools-CKLy1C7M.js.map +1 -0
- package/dist/otel-export-D34IQW1C.js +843 -0
- package/dist/otel-export-D34IQW1C.js.map +1 -0
- package/dist/platform.d.ts +188 -185
- package/dist/platform.js +185 -218
- package/dist/platform.js.map +1 -1
- package/dist/prepare-B53sPV7M.js +4292 -0
- package/dist/prepare-B53sPV7M.js.map +1 -0
- package/dist/primeintellect/index.d.ts +150 -147
- package/dist/primeintellect/index.js +626 -1004
- package/dist/primeintellect/index.js.map +1 -1
- package/dist/profiles.d.ts +296 -490
- package/dist/profiles.js +722 -749
- package/dist/profiles.js.map +1 -1
- package/dist/protected-model-port-BLPH5VVv.js +722 -0
- package/dist/protected-model-port-BLPH5VVv.js.map +1 -0
- package/dist/protected-model-port-BP6Z4eau.d.ts +283 -0
- package/dist/protected-redaction--F3v1oo8.js +200 -0
- package/dist/protected-redaction--F3v1oo8.js.map +1 -0
- package/dist/redact-D-u-rrcn.js +82 -0
- package/dist/redact-D-u-rrcn.js.map +1 -0
- package/dist/redact-kbIOjfjM.d.ts +452 -0
- package/dist/researcher-CoVqNhfI.js +433 -0
- package/dist/researcher-CoVqNhfI.js.map +1 -0
- package/dist/runtime-BG0BziaP.js +5408 -0
- package/dist/runtime-BG0BziaP.js.map +1 -0
- package/dist/sandbox-events-DeI5xX8P.js +316 -0
- package/dist/sandbox-events-DeI5xX8P.js.map +1 -0
- package/dist/spawn-journal-CwPvKUTa.js +456 -0
- package/dist/spawn-journal-CwPvKUTa.js.map +1 -0
- package/dist/structural-rollout-IXUEplky.js +1445 -0
- package/dist/structural-rollout-IXUEplky.js.map +1 -0
- package/dist/substrate-B0TYNrXn.js +27 -0
- package/dist/substrate-B0TYNrXn.js.map +1 -0
- package/dist/{substrate-Dyiqmd3o.d.ts → substrate-BcnuSHXm.d.ts} +57 -54
- package/dist/supervise-BiRutHS9.js +7321 -0
- package/dist/supervise-BiRutHS9.js.map +1 -0
- package/dist/supervisor-DTKhF-RV.js +3826 -0
- package/dist/supervisor-DTKhF-RV.js.map +1 -0
- package/dist/testing.d.ts +4 -3
- package/dist/testing.js +2478 -2637
- package/dist/testing.js.map +1 -1
- package/dist/tool-server-RcWgLIsL.js +141 -0
- package/dist/tool-server-RcWgLIsL.js.map +1 -0
- package/dist/trace-propagation-CJJC7SVB.js +57 -0
- package/dist/trace-propagation-CJJC7SVB.js.map +1 -0
- package/dist/types-BaIpb0XT.d.ts +492 -0
- package/dist/types-BevOjfTY.d.ts +765 -0
- package/dist/types-C9j4qg6l.d.ts +500 -0
- package/dist/types-Cja5OTGh.d.ts +190 -0
- package/dist/util-MVgdwuIS.js +145 -0
- package/dist/util-MVgdwuIS.js.map +1 -0
- package/dist/workspace-archive-BosQiotA.js +575 -0
- package/dist/workspace-archive-BosQiotA.js.map +1 -0
- package/package.json +43 -55
- package/skills/build-with-agent-runtime/SKILL.md +16 -9
- package/skills/loop-writer/SKILL.md +1 -1
- package/dist/activation-Cm-9MMX1.d.ts +0 -69
- package/dist/analyst-loop.js.map +0 -1
- package/dist/backends-BO4Jqoe2.d.ts +0 -134
- package/dist/candidate-execution/index.js.map +0 -1
- package/dist/chunk-2KGAN2HM.js +0 -1256
- package/dist/chunk-2KGAN2HM.js.map +0 -1
- package/dist/chunk-2QJSNOGK.js +0 -48
- package/dist/chunk-2QJSNOGK.js.map +0 -1
- package/dist/chunk-2UABGGUB.js +0 -474
- package/dist/chunk-2UABGGUB.js.map +0 -1
- package/dist/chunk-2VZYRB55.js +0 -834
- package/dist/chunk-2VZYRB55.js.map +0 -1
- package/dist/chunk-2X65HYMI.js +0 -3526
- package/dist/chunk-2X65HYMI.js.map +0 -1
- package/dist/chunk-3MDZX7YU.js +0 -125
- package/dist/chunk-3MDZX7YU.js.map +0 -1
- package/dist/chunk-3PPVCS75.js +0 -199
- package/dist/chunk-3PPVCS75.js.map +0 -1
- package/dist/chunk-5HNDYWUT.js +0 -6424
- package/dist/chunk-5HNDYWUT.js.map +0 -1
- package/dist/chunk-5IBQAPVB.js +0 -29
- package/dist/chunk-5IBQAPVB.js.map +0 -1
- package/dist/chunk-5QOB7H74.js +0 -387
- package/dist/chunk-5QOB7H74.js.map +0 -1
- package/dist/chunk-ATDFUJYI.js +0 -4728
- package/dist/chunk-ATDFUJYI.js.map +0 -1
- package/dist/chunk-B7K7V22Y.js +0 -699
- package/dist/chunk-B7K7V22Y.js.map +0 -1
- package/dist/chunk-DPEUKJRO.js +0 -27
- package/dist/chunk-DPEUKJRO.js.map +0 -1
- package/dist/chunk-FD2MBMOH.js +0 -494
- package/dist/chunk-FD2MBMOH.js.map +0 -1
- package/dist/chunk-FJI3EXU3.js +0 -2402
- package/dist/chunk-FJI3EXU3.js.map +0 -1
- package/dist/chunk-HAO4CXHP.js +0 -200
- package/dist/chunk-HAO4CXHP.js.map +0 -1
- package/dist/chunk-ISPWRSEC.js +0 -408
- package/dist/chunk-ISPWRSEC.js.map +0 -1
- package/dist/chunk-KRBFHMV6.js +0 -1228
- package/dist/chunk-KRBFHMV6.js.map +0 -1
- package/dist/chunk-L5DST3QC.js +0 -351
- package/dist/chunk-L5DST3QC.js.map +0 -1
- package/dist/chunk-M22PL4GH.js +0 -171
- package/dist/chunk-M22PL4GH.js.map +0 -1
- package/dist/chunk-NURIQBJQ.js +0 -72
- package/dist/chunk-NURIQBJQ.js.map +0 -1
- package/dist/chunk-PZZKQVQV.js +0 -52
- package/dist/chunk-PZZKQVQV.js.map +0 -1
- package/dist/chunk-Q2JSAVQ3.js +0 -671
- package/dist/chunk-Q2JSAVQ3.js.map +0 -1
- package/dist/chunk-SBTWKPVR.js +0 -5576
- package/dist/chunk-SBTWKPVR.js.map +0 -1
- package/dist/chunk-SGKPNBXE.js +0 -60
- package/dist/chunk-SGKPNBXE.js.map +0 -1
- package/dist/chunk-SNSMRT6H.js +0 -680
- package/dist/chunk-SNSMRT6H.js.map +0 -1
- package/dist/chunk-STJEHM5F.js +0 -1338
- package/dist/chunk-STJEHM5F.js.map +0 -1
- package/dist/chunk-UPWGXKXB.js +0 -254
- package/dist/chunk-UPWGXKXB.js.map +0 -1
- package/dist/chunk-YEJR7IXO.js +0 -68
- package/dist/chunk-YEJR7IXO.js.map +0 -1
- package/dist/chunk-ZXICDSAK.js +0 -907
- package/dist/chunk-ZXICDSAK.js.map +0 -1
- package/dist/completion-gate-Bv47cOnn.d.ts +0 -256
- package/dist/conversation.js.map +0 -1
- package/dist/coordination-CxtzMxSM.d.ts +0 -2570
- package/dist/environment-provider.js.map +0 -1
- package/dist/improve--2A0gK1-.d.ts +0 -440
- package/dist/improvement-adapter-HAZz-7vK.d.ts +0 -203
- package/dist/knowledge.js.map +0 -1
- package/dist/local-harness-CtK9dSny.d.ts +0 -175
- package/dist/loop-runner-bin-DUOGtvOl.d.ts +0 -207
- package/dist/loop-runner-bin.js.map +0 -1
- package/dist/loops.js.map +0 -1
- package/dist/openai-tools-fnj6SRVg.d.ts +0 -46
- package/dist/protected-model-port-DzkUY3HQ.d.ts +0 -271
- package/dist/sanitize-DEbPNtyI.d.ts +0 -328
- package/dist/structural-rollout-C4mabf87.d.ts +0 -446
- package/dist/supervise-7q9F5brx.d.ts +0 -379
- package/dist/types-BCoemcXU.d.ts +0 -802
- package/dist/types-BwoZWq-i.d.ts +0 -510
- package/dist/types-Cbp72qyu.d.ts +0 -491
- package/dist/types-CmYCMbFT.d.ts +0 -203
- package/dist/types-CvNwMzQt.d.ts +0 -957
- package/dist/worktree-fanout-WsOzt8qu.d.ts +0 -999
|
@@ -0,0 +1,4292 @@
|
|
|
1
|
+
import { a as contentAddress } from "./spawn-journal-CwPvKUTa.js";
|
|
2
|
+
import { i as redactProtectedValue, n as createProtectedRecordRedactor, r as redactProtectedReason, t as assertNoProtectedBytes } from "./protected-redaction--F3v1oo8.js";
|
|
3
|
+
import { REDACTION_VERSION, canonicalJson, isLlmSpan } from "@tangle-network/agent-eval";
|
|
4
|
+
import { agentCandidateArtifactRefSchema, agentCandidateBenchmarkResultEvidenceSchema, agentCandidateBenchmarkSuiteSchema, agentCandidateBenchmarkTaskSchema, agentCandidateBundleSchema, agentCandidateContainerSchema, agentCandidateExecutionLimitsSchema, agentCandidateExecutionPlanEvidenceSchema, agentCandidateExecutionPlanMaterialSchema, agentCandidateMaterializationReceiptSchema, agentCandidateModelAccessNetworkSchema, agentCandidateModelSettlementEvidenceSchema, agentCandidateProfileActivationSchema, agentCandidateProfilePlanEvidenceSchema, agentCandidateProfileSchema, agentCandidateResolvedModelSchema, agentCandidateRunCellSchema, agentCandidateRunReceiptSchema, agentCandidateTaskOutcomeEvidenceSchema, agentCandidateTaskOutcomeSpecSchema, agentCandidateTerminationSchema, agentCandidateWorkspaceManifestMaterialSchema, agentCandidateWorkspaceSnapshotEvidenceSchema, agentProfileDiffSchema, agentProfileSchema, applyAgentProfileDiff, sha256DigestSchema } from "@tangle-network/agent-interface";
|
|
5
|
+
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
6
|
+
import { applyAgentCandidateWorkspacePlan, isMaterializerHarness, materializeCandidateProfile } from "@tangle-network/agent-profile-materialize";
|
|
7
|
+
import { lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rm, stat } from "node:fs/promises";
|
|
8
|
+
import { constants } from "node:fs";
|
|
9
|
+
import { isAbsolute, join, posix, relative, resolve, sep } from "node:path";
|
|
10
|
+
import { spawn } from "node:child_process";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { hashKnowledgeBase } from "@tangle-network/agent-knowledge";
|
|
13
|
+
//#region src/candidate-execution/digest.ts
|
|
14
|
+
function sha256Bytes(bytes) {
|
|
15
|
+
return `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
|
|
16
|
+
}
|
|
17
|
+
function canonicalCandidateBytes(value) {
|
|
18
|
+
return Buffer.from(canonicalJson(value), "utf8");
|
|
19
|
+
}
|
|
20
|
+
function canonicalCandidateDigest$1(value) {
|
|
21
|
+
return contentAddress(value);
|
|
22
|
+
}
|
|
23
|
+
/** Returns a detached, deeply frozen JSON value with canonical number normalization. */
|
|
24
|
+
function immutableCandidateValue(value) {
|
|
25
|
+
return deepFreezeCandidate(JSON.parse(Buffer.from(canonicalCandidateBytes(value)).toString("utf8")));
|
|
26
|
+
}
|
|
27
|
+
function canonicalCandidateDocument(valueWithoutDigest) {
|
|
28
|
+
const bytes = canonicalCandidateBytes(valueWithoutDigest);
|
|
29
|
+
const digest = canonicalCandidateDigest$1(valueWithoutDigest);
|
|
30
|
+
if (sha256Bytes(bytes) !== digest) throw new Error("canonical candidate serializers disagree on document digest");
|
|
31
|
+
const storedBytes = Uint8Array.from(bytes);
|
|
32
|
+
const value = immutableCandidateValue({
|
|
33
|
+
...valueWithoutDigest,
|
|
34
|
+
digest
|
|
35
|
+
});
|
|
36
|
+
return Object.freeze({
|
|
37
|
+
value,
|
|
38
|
+
get bytes() {
|
|
39
|
+
return Uint8Array.from(storedBytes);
|
|
40
|
+
},
|
|
41
|
+
digest
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function verifyCanonicalCandidateDocument(value, label) {
|
|
45
|
+
if (canonicalCandidateDigest$1(omitTopLevelDigest(value)) !== value.digest) throw new Error(`${label} digest does not match`);
|
|
46
|
+
return immutableCandidateValue(value);
|
|
47
|
+
}
|
|
48
|
+
function embeddedCandidateArtifact(bytes) {
|
|
49
|
+
return {
|
|
50
|
+
encoding: "base64",
|
|
51
|
+
content: Buffer.from(bytes).toString("base64"),
|
|
52
|
+
sha256: sha256Bytes(bytes),
|
|
53
|
+
byteLength: bytes.byteLength
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function omitTopLevelDigest(value) {
|
|
57
|
+
const { digest: _digest, ...rest } = value;
|
|
58
|
+
return rest;
|
|
59
|
+
}
|
|
60
|
+
function deepFreezeCandidate(value, seen = /* @__PURE__ */ new Set()) {
|
|
61
|
+
if (value === null || typeof value !== "object" || ArrayBuffer.isView(value) || seen.has(value)) return value;
|
|
62
|
+
seen.add(value);
|
|
63
|
+
for (const child of Object.values(value)) deepFreezeCandidate(child, seen);
|
|
64
|
+
return Object.freeze(value);
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/candidate-execution/profile.ts
|
|
68
|
+
function candidateMaterializerHarness(harness) {
|
|
69
|
+
if (!isMaterializerHarness(harness)) throw new Error(`sealed candidate profile materialization is unsupported for harness ${harness}`);
|
|
70
|
+
return harness;
|
|
71
|
+
}
|
|
72
|
+
/** Bind exact native profile text to the canonical plan captured during preparation. */
|
|
73
|
+
function createAgentCandidateProfileActivation(plan, profilePlan) {
|
|
74
|
+
const sourceByPath = new Map(plan.files.map((file) => [file.relPath, file]));
|
|
75
|
+
if (sourceByPath.size !== plan.files.length) throw new Error("candidate profile activation contains duplicate native file paths");
|
|
76
|
+
const files = profilePlan.material.files.map((expected) => {
|
|
77
|
+
const source = sourceByPath.get(expected.relPath);
|
|
78
|
+
if (!source) throw new Error(`candidate profile activation is missing ${expected.relPath}`);
|
|
79
|
+
const mode = source.mode ?? 420;
|
|
80
|
+
if (!Number.isSafeInteger(mode) || mode < 0 || mode > 511) throw new Error(`candidate profile activation has invalid mode for ${expected.relPath}`);
|
|
81
|
+
return {
|
|
82
|
+
path: expected.relPath,
|
|
83
|
+
mode,
|
|
84
|
+
content: source.content
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
if (files.length !== plan.files.length) throw new Error("candidate profile activation contains unplanned native files");
|
|
88
|
+
return parseAgentCandidateProfileActivation(canonicalCandidateDocument({
|
|
89
|
+
kind: "agent-candidate-profile-activation",
|
|
90
|
+
profilePlan,
|
|
91
|
+
files
|
|
92
|
+
}).value, profilePlan.digest);
|
|
93
|
+
}
|
|
94
|
+
/** Parse and check every native file hash plus both canonical document digests. */
|
|
95
|
+
function parseAgentCandidateProfileActivation(input, expectedProfilePlanDigest) {
|
|
96
|
+
const activation = agentCandidateProfileActivationSchema.parse(input);
|
|
97
|
+
const planBytes = canonicalCandidateBytes(activation.profilePlan.material);
|
|
98
|
+
const profilePlanArtifact = activation.profilePlan.artifact;
|
|
99
|
+
if (sha256Bytes(planBytes) !== activation.profilePlan.digest || profilePlanArtifact.sha256 !== activation.profilePlan.digest || profilePlanArtifact.byteLength !== planBytes.byteLength || "content" in profilePlanArtifact && !Buffer.from(profilePlanArtifact.content, "base64").equals(Buffer.from(planBytes)) || expectedProfilePlanDigest !== void 0 && activation.profilePlan.digest !== expectedProfilePlanDigest) throw new Error("candidate profile activation has an invalid canonical profile plan");
|
|
100
|
+
if (canonicalCandidateDigest$1(omitTopLevelDigest(activation)) !== activation.digest) throw new Error("candidate profile activation digest does not match");
|
|
101
|
+
return immutableCandidateValue(activation);
|
|
102
|
+
}
|
|
103
|
+
const CANDIDATE_PROFILE_DIRECT_FIELDS = [
|
|
104
|
+
"name",
|
|
105
|
+
"description",
|
|
106
|
+
"version",
|
|
107
|
+
"tags",
|
|
108
|
+
"prompt",
|
|
109
|
+
"harness",
|
|
110
|
+
"permissions",
|
|
111
|
+
"tools",
|
|
112
|
+
"confidential"
|
|
113
|
+
];
|
|
114
|
+
/** Convert only behavior-preserving generic profile fields into the closed candidate contract. */
|
|
115
|
+
function freezeGenericAgentCandidateProfile(input) {
|
|
116
|
+
return candidateProfileFromGenericProfile(parseExactAgentProfile(input, "profile"));
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Keep every generic-profile-to-candidate conversion in one place.
|
|
120
|
+
* Callers use this both when sealing a new bundle and when proving a measured
|
|
121
|
+
* profile is exactly the profile in an existing candidate.
|
|
122
|
+
*/
|
|
123
|
+
function candidateProfileFromGenericProfile(profile) {
|
|
124
|
+
if (profile.connections !== void 0) unsupportedProfileField("connections");
|
|
125
|
+
if (profile.metadata !== void 0) unsupportedProfileField("metadata");
|
|
126
|
+
if (profile.extensions !== void 0) unsupportedProfileField("extensions");
|
|
127
|
+
if (profile.model?.metadata !== void 0) unsupportedProfileField("model.metadata");
|
|
128
|
+
const candidate = {};
|
|
129
|
+
copyDirectProfileFields(candidate, profile);
|
|
130
|
+
if (profile.model) {
|
|
131
|
+
const { metadata: _metadata, ...model } = profile.model;
|
|
132
|
+
candidate.model = model;
|
|
133
|
+
}
|
|
134
|
+
if (profile.mcp) candidate.mcp = freezeMcpServers(profile.mcp);
|
|
135
|
+
if (profile.subagents) candidate.subagents = Object.fromEntries(Object.entries(profile.subagents).map(([name, subagent]) => {
|
|
136
|
+
if (subagent.metadata !== void 0) unsupportedProfileField(`subagents.${name}.metadata`);
|
|
137
|
+
const { metadata: _metadata, ...value } = subagent;
|
|
138
|
+
return [name, value];
|
|
139
|
+
}));
|
|
140
|
+
if (profile.resources) candidate.resources = freezeResources(profile.resources);
|
|
141
|
+
if (profile.hooks && Object.values(profile.hooks).some((commands) => commands.length > 0)) throw new Error("generic AgentProfile hooks cannot be safely tokenized; use a candidate-profile source with executable/args");
|
|
142
|
+
if (profile.hooks) candidate.hooks = profile.hooks;
|
|
143
|
+
if (profile.modes) candidate.modes = Object.fromEntries(Object.entries(profile.modes).map(([name, mode]) => {
|
|
144
|
+
if (mode.metadata !== void 0) unsupportedProfileField(`modes.${name}.metadata`);
|
|
145
|
+
const { metadata: _metadata, ...value } = mode;
|
|
146
|
+
return [name, value];
|
|
147
|
+
}));
|
|
148
|
+
return parseExactCandidateProfile(candidate);
|
|
149
|
+
}
|
|
150
|
+
/** Prove the measured generic profile and sealed candidate profile describe the same behavior. */
|
|
151
|
+
function assertCandidateProfileBinding(measuredInput, bundled) {
|
|
152
|
+
if (canonicalCandidateDigest$1(candidateProfileFromGenericProfile(parseExactAgentProfile(measuredInput, "measured agent profile"))) !== canonicalCandidateDigest$1(bundled)) throw new Error("measured agent profile does not match sealed candidate profile");
|
|
153
|
+
}
|
|
154
|
+
/** Parse a complete profile without silently discarding unsupported fields. */
|
|
155
|
+
function parseExactAgentProfile(input, label) {
|
|
156
|
+
let parsed;
|
|
157
|
+
try {
|
|
158
|
+
parsed = agentProfileSchema.parse(input);
|
|
159
|
+
} catch (cause) {
|
|
160
|
+
throw new Error(`${label} contains unsupported or non-canonical fields`, { cause });
|
|
161
|
+
}
|
|
162
|
+
assertCanonicalParse(input, parsed, label);
|
|
163
|
+
return parsed;
|
|
164
|
+
}
|
|
165
|
+
/** Parse a profile diff without silently discarding unsupported fields. */
|
|
166
|
+
function parseExactAgentProfileDiff(input, label) {
|
|
167
|
+
let parsed;
|
|
168
|
+
try {
|
|
169
|
+
parsed = agentProfileDiffSchema.parse(input);
|
|
170
|
+
} catch (cause) {
|
|
171
|
+
throw new Error(`${label} contains unsupported or non-canonical fields`, { cause });
|
|
172
|
+
}
|
|
173
|
+
assertCanonicalParse(input, parsed, label);
|
|
174
|
+
return parsed;
|
|
175
|
+
}
|
|
176
|
+
/** Apply one exact diff and reject any value that cannot be preserved canonically. */
|
|
177
|
+
function applyExactAgentProfileDiff(baseInput, diffInput, label) {
|
|
178
|
+
return parseExactAgentProfile(omitUndefinedObjectFields(applyAgentProfileDiff(parseExactAgentProfile(baseInput, `${label} base profile`), parseExactAgentProfileDiff(diffInput, `${label} diff`)), label), `${label} result`);
|
|
179
|
+
}
|
|
180
|
+
function parseExactCandidateProfile(input) {
|
|
181
|
+
const parsed = agentCandidateProfileSchema.parse(input);
|
|
182
|
+
assertCanonicalParse(input, parsed, "candidate profile");
|
|
183
|
+
return parsed;
|
|
184
|
+
}
|
|
185
|
+
/** Reject profile fields whose behavioral effect is not yet proven by candidate executors. */
|
|
186
|
+
function assertCandidateProfileExecutionSupport(profile) {
|
|
187
|
+
const unsupported = [
|
|
188
|
+
"tools",
|
|
189
|
+
"permissions",
|
|
190
|
+
"modes",
|
|
191
|
+
"confidential"
|
|
192
|
+
].filter((field) => hasEntries(profile[field]));
|
|
193
|
+
if (unsupported.length > 0) throw new Error(`candidate execution cannot prove in-session effects for non-empty AgentProfile fields: ${unsupported.join(", ")}`);
|
|
194
|
+
}
|
|
195
|
+
function agentCandidateProfileAsAgentProfile(candidate) {
|
|
196
|
+
const output = {};
|
|
197
|
+
copyDirectProfileFields(output, candidate);
|
|
198
|
+
if (candidate.model) output.model = { ...candidate.model };
|
|
199
|
+
if (candidate.mcp) output.mcp = Object.fromEntries(Object.entries(candidate.mcp).map(([name, server]) => [name, {
|
|
200
|
+
...server,
|
|
201
|
+
...server.args ? { args: server.args.map(publicValue) } : {},
|
|
202
|
+
...server.env ? { env: mapPublicValues(server.env) } : {}
|
|
203
|
+
}]));
|
|
204
|
+
if (candidate.subagents) output.subagents = cloneRecord(candidate.subagents);
|
|
205
|
+
if (candidate.modes) output.modes = cloneRecord(candidate.modes);
|
|
206
|
+
if (candidate.hooks) output.hooks = Object.fromEntries(Object.entries(candidate.hooks).map(([event, hooks]) => [event, hooks.map(({ executable, args, env, ...hook }) => ({
|
|
207
|
+
...hook,
|
|
208
|
+
command: [executable, ...(args ?? []).map(publicValue)].map(shellQuote).join(" "),
|
|
209
|
+
...env ? { env: mapPublicValues(env) } : {}
|
|
210
|
+
}))]));
|
|
211
|
+
if (candidate.resources) {
|
|
212
|
+
if (candidate.resources.failOnError !== true) throw new Error("sealed candidate profile contains fields unsupported by generic profiles");
|
|
213
|
+
output.resources = {
|
|
214
|
+
failOnError: true,
|
|
215
|
+
...candidate.resources.files ? { files: candidate.resources.files.map((file) => ({
|
|
216
|
+
...file,
|
|
217
|
+
resource: publicResource(file.resource)
|
|
218
|
+
})) } : {},
|
|
219
|
+
...candidate.resources.tools ? { tools: candidate.resources.tools.map(publicResource) } : {},
|
|
220
|
+
...candidate.resources.skills ? { skills: candidate.resources.skills.map(publicResource) } : {},
|
|
221
|
+
...candidate.resources.agents ? { agents: candidate.resources.agents.map(publicResource) } : {},
|
|
222
|
+
...candidate.resources.commands ? { commands: candidate.resources.commands.map(publicResource) } : {},
|
|
223
|
+
...candidate.resources.instructions !== void 0 ? { instructions: typeof candidate.resources.instructions === "string" ? candidate.resources.instructions : publicResource(candidate.resources.instructions) } : {}
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
return output;
|
|
227
|
+
}
|
|
228
|
+
function omitUndefinedObjectFields(value, path) {
|
|
229
|
+
if (Array.isArray(value)) return value.map((entry, index) => {
|
|
230
|
+
if (entry === void 0) throw new Error(`${path} produced an undefined array entry at ${index}`);
|
|
231
|
+
return omitUndefinedObjectFields(entry, `${path}[${index}]`);
|
|
232
|
+
});
|
|
233
|
+
if (value === null || typeof value !== "object") return value;
|
|
234
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0).map(([key, entry]) => [key, omitUndefinedObjectFields(entry, `${path}.${key}`)]));
|
|
235
|
+
}
|
|
236
|
+
function freezeMcpServers(servers) {
|
|
237
|
+
return Object.fromEntries(Object.entries(servers).map(([name, server]) => {
|
|
238
|
+
if (server.transport !== void 0 && server.transport !== "stdio") unsupportedProfileField(`mcp.${name}.transport=${server.transport}`);
|
|
239
|
+
if (server.url !== void 0) unsupportedProfileField(`mcp.${name}.url`);
|
|
240
|
+
if (server.headers !== void 0) unsupportedProfileField(`mcp.${name}.headers`);
|
|
241
|
+
if (server.metadata !== void 0) unsupportedProfileField(`mcp.${name}.metadata`);
|
|
242
|
+
return [name, {
|
|
243
|
+
...server.transport ? { transport: server.transport } : {},
|
|
244
|
+
...server.command ? { command: server.command } : {},
|
|
245
|
+
...server.args ? { args: server.args.map(candidatePublicValue) } : {},
|
|
246
|
+
...server.env ? { env: mapCandidatePublicValues(server.env) } : {},
|
|
247
|
+
...server.cwd ? { cwd: server.cwd } : {},
|
|
248
|
+
...server.enabled === void 0 ? {} : { enabled: server.enabled }
|
|
249
|
+
}];
|
|
250
|
+
}));
|
|
251
|
+
}
|
|
252
|
+
function freezeResources(resources) {
|
|
253
|
+
if (resources.failOnError !== true) throw new Error("candidate profile resources require failOnError: true");
|
|
254
|
+
return {
|
|
255
|
+
failOnError: true,
|
|
256
|
+
...resources.files ? { files: resources.files.map((file) => ({
|
|
257
|
+
...file,
|
|
258
|
+
resource: freezeResource(file.resource)
|
|
259
|
+
})) } : {},
|
|
260
|
+
...resources.tools ? { tools: resources.tools.map(freezeResource) } : {},
|
|
261
|
+
...resources.skills ? { skills: resources.skills.map(freezeResource) } : {},
|
|
262
|
+
...resources.agents ? { agents: resources.agents.map(freezeResource) } : {},
|
|
263
|
+
...resources.commands ? { commands: resources.commands.map(freezeResource) } : {},
|
|
264
|
+
...resources.instructions === void 0 ? {} : { instructions: typeof resources.instructions === "string" ? resources.instructions : freezeResource(resources.instructions) }
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function freezeResource(resource) {
|
|
268
|
+
if (resource.kind === "github") throw new Error("generic GitHub profile resources do not carry byte identity; use a candidate-profile source with a pinned commit, digest, and byte length");
|
|
269
|
+
const bytes = Buffer.from(resource.content, "utf8");
|
|
270
|
+
return {
|
|
271
|
+
...resource,
|
|
272
|
+
sha256: embeddedCandidateArtifact(bytes).sha256,
|
|
273
|
+
byteLength: bytes.byteLength
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function copyDirectProfileFields(target, source) {
|
|
277
|
+
for (const key of CANDIDATE_PROFILE_DIRECT_FIELDS) if (source[key] !== void 0) target[key] = source[key];
|
|
278
|
+
}
|
|
279
|
+
function assertCanonicalParse(input, parsed, label) {
|
|
280
|
+
if (!Buffer.from(canonicalCandidateBytes(input)).equals(canonicalCandidateBytes(parsed))) throw new Error(`${label} contains unsupported or non-canonical fields`);
|
|
281
|
+
}
|
|
282
|
+
function publicResource(resource) {
|
|
283
|
+
if (resource.kind === "inline") return {
|
|
284
|
+
kind: "inline",
|
|
285
|
+
name: resource.name,
|
|
286
|
+
content: resource.content
|
|
287
|
+
};
|
|
288
|
+
return {
|
|
289
|
+
kind: "github",
|
|
290
|
+
repository: `${resource.repository.owner}/${resource.repository.repo}`,
|
|
291
|
+
path: resource.path,
|
|
292
|
+
ref: resource.commit,
|
|
293
|
+
...resource.name ? { name: resource.name } : {}
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
function candidatePublicValue(value) {
|
|
297
|
+
return {
|
|
298
|
+
kind: "public",
|
|
299
|
+
value
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
function mapCandidatePublicValues(values) {
|
|
303
|
+
return Object.fromEntries(Object.entries(values).map(([name, value]) => [name, candidatePublicValue(value)]));
|
|
304
|
+
}
|
|
305
|
+
function publicValue(value) {
|
|
306
|
+
return value.value;
|
|
307
|
+
}
|
|
308
|
+
function mapPublicValues(values) {
|
|
309
|
+
return Object.fromEntries(Object.entries(values).map(([key, value]) => [key, publicValue(value)]));
|
|
310
|
+
}
|
|
311
|
+
function cloneRecord(values) {
|
|
312
|
+
return Object.fromEntries(Object.entries(values).map(([key, value]) => [key, { ...value }]));
|
|
313
|
+
}
|
|
314
|
+
function hasEntries(value) {
|
|
315
|
+
return value !== void 0 && value !== null && typeof value === "object" ? Object.keys(value).length > 0 : false;
|
|
316
|
+
}
|
|
317
|
+
function shellQuote(value) {
|
|
318
|
+
return /^[A-Za-z0-9_./:@%+=,-]+$/.test(value) ? value : `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
319
|
+
}
|
|
320
|
+
function unsupportedProfileField(path) {
|
|
321
|
+
throw new Error(`generic AgentProfile field ${path} is not representable in a sealed candidate profile`);
|
|
322
|
+
}
|
|
323
|
+
//#endregion
|
|
324
|
+
//#region src/candidate-execution/exact-object.ts
|
|
325
|
+
/** Reject unknown fields while requiring every declared non-optional field. */
|
|
326
|
+
function assertExactObjectKeys(value, required, label, optional = []) {
|
|
327
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
|
|
328
|
+
const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
|
|
329
|
+
if (allowed.size !== required.length + optional.length) throw new Error(`${label} exact-key contract contains duplicate fields`);
|
|
330
|
+
for (const key of Object.keys(value)) if (!allowed.has(key)) throw new Error(`${label} contains unknown field ${key}`);
|
|
331
|
+
for (const key of required) if (!(key in value)) throw new Error(`${label} is missing field ${key}`);
|
|
332
|
+
}
|
|
333
|
+
//#endregion
|
|
334
|
+
//#region src/candidate-execution/claim-file-formats.ts
|
|
335
|
+
function sealCandidatePreparationEvidence(value, executionPlanDigest) {
|
|
336
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("candidate execution preparation evidence must be an object");
|
|
337
|
+
const record = value;
|
|
338
|
+
assertExactObjectKeys(record, ["executionPlan", "materializationReceipt"], "candidate execution preparation evidence");
|
|
339
|
+
const executionPlan = immutableCandidateValue(agentCandidateArtifactRefSchema.parse(record.executionPlan));
|
|
340
|
+
const materializationReceipt = immutableCandidateValue(agentCandidateArtifactRefSchema.parse(record.materializationReceipt));
|
|
341
|
+
if (executionPlan.sha256 !== executionPlanDigest) throw new Error("candidate execution plan artifact digest does not match its claim");
|
|
342
|
+
return Object.freeze({
|
|
343
|
+
executionPlan,
|
|
344
|
+
materializationReceipt
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
//#endregion
|
|
348
|
+
//#region src/candidate-execution/claim-terminal.ts
|
|
349
|
+
const SHA256_PATTERN$1 = /^sha256:[a-f0-9]{64}$/;
|
|
350
|
+
function terminalRecord(claim, result) {
|
|
351
|
+
const terminal = sealTerminalResult(result);
|
|
352
|
+
const value = {
|
|
353
|
+
executionId: claim.executionId,
|
|
354
|
+
attempt: claim.attempt,
|
|
355
|
+
bundleDigest: claim.bundleDigest,
|
|
356
|
+
executionPlanDigest: claim.executionPlanDigest,
|
|
357
|
+
preparationEvidence: claim.preparationEvidence,
|
|
358
|
+
...terminal
|
|
359
|
+
};
|
|
360
|
+
return immutableCandidateValue({
|
|
361
|
+
...value,
|
|
362
|
+
terminalDigest: canonicalCandidateDigest$1(value)
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
function recoveredTerminalRecord(claim, phase, evidence) {
|
|
366
|
+
const recovered = sealRecoveryEvidence(evidence, claim);
|
|
367
|
+
return terminalRecord(claim, {
|
|
368
|
+
status: "failed",
|
|
369
|
+
failureClass: recovered.failureClass === "pre-model-infrastructure" && phase !== "claimed" ? "unknown" : recovered.failureClass,
|
|
370
|
+
usage: recovered.usage,
|
|
371
|
+
modelSettlement: recovered.modelSettlement,
|
|
372
|
+
...recovered.failureEvidence ? { failureEvidence: recovered.failureEvidence } : {}
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
function assertTerminalAllowedInPhase(phase, terminal) {
|
|
376
|
+
if (phase === "candidate-may-run" && terminal.status === "failed" && terminal.failureClass === "pre-model-infrastructure") throw new Error("candidate execution crossed candidate-may-run before pre-model failure");
|
|
377
|
+
if (phase === "claimed" && (terminal.status === "succeeded" || terminal.status === "failed" && (terminal.failureClass === "execution" || terminal.failureClass === "post-model-infrastructure"))) throw new Error("candidate execution terminal requires candidate-may-run phase");
|
|
378
|
+
}
|
|
379
|
+
function rejectedFinish(existing, requestedTerminalDigest) {
|
|
380
|
+
return Object.freeze({
|
|
381
|
+
finished: false,
|
|
382
|
+
terminal: existing,
|
|
383
|
+
exactReplay: existing.terminalDigest === requestedTerminalDigest
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
function rejectedStage(existing, requested) {
|
|
387
|
+
return Object.freeze({
|
|
388
|
+
staged: false,
|
|
389
|
+
terminal: existing,
|
|
390
|
+
exactReplay: existing.terminalDigest === requested.terminalDigest
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
function requireStagedTerminal(staged, terminalDigest) {
|
|
394
|
+
if (!staged) throw new Error("candidate execution terminal has not been staged");
|
|
395
|
+
if (staged.terminalDigest !== terminalDigest) throw new Error("candidate execution terminal digest does not match staged outbox");
|
|
396
|
+
return staged;
|
|
397
|
+
}
|
|
398
|
+
function sealTerminalDigest(value) {
|
|
399
|
+
assertSha256Digest$1(value, "terminalDigest");
|
|
400
|
+
return value;
|
|
401
|
+
}
|
|
402
|
+
function assertRecoveryMatchesStaged(staged, recovered) {
|
|
403
|
+
if (canonicalCandidateDigest$1(staged.usage) !== canonicalCandidateDigest$1(recovered.usage) || canonicalCandidateDigest$1(staged.modelSettlement) !== canonicalCandidateDigest$1(recovered.modelSettlement)) throw new Error("candidate execution recovery evidence does not match staged model evidence");
|
|
404
|
+
}
|
|
405
|
+
function sealTerminalRecordValue(value, label) {
|
|
406
|
+
const status = requireTerminalStatus(value.status, label);
|
|
407
|
+
assertExactObjectKeys(value, status === "succeeded" ? [
|
|
408
|
+
"executionId",
|
|
409
|
+
"attempt",
|
|
410
|
+
"bundleDigest",
|
|
411
|
+
"executionPlanDigest",
|
|
412
|
+
"preparationEvidence",
|
|
413
|
+
"terminalDigest",
|
|
414
|
+
"status",
|
|
415
|
+
"usage",
|
|
416
|
+
"modelSettlement",
|
|
417
|
+
"taskOutcome",
|
|
418
|
+
"benchmarkResult",
|
|
419
|
+
"runReceipt"
|
|
420
|
+
] : [
|
|
421
|
+
"executionId",
|
|
422
|
+
"attempt",
|
|
423
|
+
"bundleDigest",
|
|
424
|
+
"executionPlanDigest",
|
|
425
|
+
"preparationEvidence",
|
|
426
|
+
"terminalDigest",
|
|
427
|
+
"status",
|
|
428
|
+
"failureClass",
|
|
429
|
+
"usage",
|
|
430
|
+
"modelSettlement",
|
|
431
|
+
...value.failureEvidence ? ["failureEvidence"] : []
|
|
432
|
+
], label);
|
|
433
|
+
const executionPlanDigest = requireString$1(value.executionPlanDigest, label, "executionPlanDigest");
|
|
434
|
+
const identity = {
|
|
435
|
+
executionId: requireString$1(value.executionId, label, "executionId"),
|
|
436
|
+
attempt: requireNumber$1(value.attempt, label, "attempt"),
|
|
437
|
+
bundleDigest: requireString$1(value.bundleDigest, label, "bundleDigest"),
|
|
438
|
+
executionPlanDigest,
|
|
439
|
+
preparationEvidence: sealCandidatePreparationEvidence(value.preparationEvidence, executionPlanDigest)
|
|
440
|
+
};
|
|
441
|
+
assertExecutionId$1(identity.executionId);
|
|
442
|
+
if (!Number.isSafeInteger(identity.attempt) || identity.attempt < 1) throw new Error(`${label} has invalid attempt`);
|
|
443
|
+
assertSha256Digest$1(identity.bundleDigest, "bundleDigest");
|
|
444
|
+
assertSha256Digest$1(identity.executionPlanDigest, "executionPlanDigest");
|
|
445
|
+
if (identity.preparationEvidence.executionPlan.sha256 !== identity.executionPlanDigest) throw new Error(`${label} execution plan artifact does not match executionPlanDigest`);
|
|
446
|
+
const result = sealTerminalResult(status === "succeeded" ? {
|
|
447
|
+
status,
|
|
448
|
+
usage: requireObject$1(value.usage, label, "usage"),
|
|
449
|
+
modelSettlement: requireArtifactRef(value.modelSettlement, label, "modelSettlement"),
|
|
450
|
+
taskOutcome: requireArtifactRef(value.taskOutcome, label, "taskOutcome"),
|
|
451
|
+
benchmarkResult: requireArtifactRef(value.benchmarkResult, label, "benchmarkResult"),
|
|
452
|
+
runReceipt: requireArtifactRef(value.runReceipt, label, "runReceipt")
|
|
453
|
+
} : {
|
|
454
|
+
status,
|
|
455
|
+
failureClass: requireFailureClass(value.failureClass, label),
|
|
456
|
+
usage: requireObject$1(value.usage, label, "usage"),
|
|
457
|
+
modelSettlement: requireArtifactRef(value.modelSettlement, label, "modelSettlement"),
|
|
458
|
+
...value.failureEvidence ? { failureEvidence: requireArtifactRef(value.failureEvidence, label, "failureEvidence") } : {}
|
|
459
|
+
});
|
|
460
|
+
const material = {
|
|
461
|
+
...identity,
|
|
462
|
+
...result
|
|
463
|
+
};
|
|
464
|
+
const terminalDigest = requireString$1(value.terminalDigest, label, "terminalDigest");
|
|
465
|
+
assertSha256Digest$1(terminalDigest, "terminalDigest");
|
|
466
|
+
if (terminalDigest !== canonicalCandidateDigest$1(material)) throw new Error(`${label} has invalid terminalDigest`);
|
|
467
|
+
return immutableCandidateValue({
|
|
468
|
+
...material,
|
|
469
|
+
terminalDigest
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
function assertTerminalMatchesClaim(terminal, claim, path) {
|
|
473
|
+
if (terminal.executionId !== claim.executionId || terminal.attempt !== claim.attempt || terminal.bundleDigest !== claim.bundleDigest || terminal.executionPlanDigest !== claim.executionPlanDigest || canonicalCandidateDigest$1(terminal.preparationEvidence) !== canonicalCandidateDigest$1(claim.preparationEvidence)) throw new Error(`candidate execution terminal record at ${path} does not match its claim`);
|
|
474
|
+
}
|
|
475
|
+
function assertTerminalMatchesStaged(terminal, staged, path) {
|
|
476
|
+
if (terminal.terminalDigest !== staged.terminalDigest || canonicalCandidateDigest$1(terminal) !== canonicalCandidateDigest$1(staged)) throw new Error(`candidate execution terminal record at ${path} differs from staged outbox`);
|
|
477
|
+
}
|
|
478
|
+
function sealRecoveryEvidence(evidence, claim) {
|
|
479
|
+
assertExactObjectKeys(evidence, [
|
|
480
|
+
"failureClass",
|
|
481
|
+
"usage",
|
|
482
|
+
"modelSettlement",
|
|
483
|
+
"process",
|
|
484
|
+
"model",
|
|
485
|
+
...evidence.failureEvidence ? ["failureEvidence"] : [],
|
|
486
|
+
...evidence.memory ? ["memory"] : []
|
|
487
|
+
], "candidate execution recovery evidence");
|
|
488
|
+
assertFailureClass(evidence.failureClass);
|
|
489
|
+
const usage = sealUsage(evidence.usage);
|
|
490
|
+
if (evidence.failureClass === "pre-model-infrastructure" && usage.modelCalls !== 0) throw new Error("pre-model infrastructure failure cannot contain model calls");
|
|
491
|
+
const modelSettlement = sealArtifactRef(evidence.modelSettlement, "modelSettlement");
|
|
492
|
+
const failureEvidence = evidence.failureEvidence ? sealArtifactRef(evidence.failureEvidence, "failureEvidence") : void 0;
|
|
493
|
+
assertExactObjectKeys(evidence.process, ["stopped", "executionPlanDigest"], "candidate execution process closure evidence");
|
|
494
|
+
if (evidence.process.stopped !== true || evidence.process.executionPlanDigest !== claim.executionPlanDigest) throw new Error("candidate execution recovery does not prove the claimed process stopped");
|
|
495
|
+
assertExactObjectKeys(evidence.model, [
|
|
496
|
+
"closed",
|
|
497
|
+
"preparationId",
|
|
498
|
+
"grantDigest"
|
|
499
|
+
], "candidate execution model closure evidence");
|
|
500
|
+
if (evidence.model.closed !== true || evidence.model.preparationId !== claim.cleanup.preparationId || evidence.model.grantDigest !== claim.cleanup.modelGrantDigest) throw new Error("candidate execution recovery does not prove the claimed model grant closed");
|
|
501
|
+
if (claim.cleanup.memory) {
|
|
502
|
+
if (!evidence.memory) throw new Error("candidate execution recovery is missing memory closure evidence");
|
|
503
|
+
assertExactObjectKeys(evidence.memory, [
|
|
504
|
+
"closed",
|
|
505
|
+
"preparationId",
|
|
506
|
+
"accessDigest",
|
|
507
|
+
"effectiveNamespace"
|
|
508
|
+
], "candidate execution memory closure evidence");
|
|
509
|
+
if (evidence.memory.closed !== true || evidence.memory.preparationId !== claim.cleanup.preparationId || evidence.memory.accessDigest !== claim.cleanup.memory.accessDigest || evidence.memory.effectiveNamespace !== claim.cleanup.memory.effectiveNamespace) throw new Error("candidate execution recovery does not prove the claimed memory access closed");
|
|
510
|
+
} else if (evidence.memory !== void 0) throw new Error("candidate execution recovery has unexpected memory closure evidence");
|
|
511
|
+
return Object.freeze({
|
|
512
|
+
failureClass: evidence.failureClass,
|
|
513
|
+
usage,
|
|
514
|
+
modelSettlement,
|
|
515
|
+
...failureEvidence ? { failureEvidence } : {},
|
|
516
|
+
process: Object.freeze({ ...evidence.process }),
|
|
517
|
+
model: Object.freeze({ ...evidence.model }),
|
|
518
|
+
...evidence.memory ? { memory: Object.freeze({ ...evidence.memory }) } : {}
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
function sealTerminalResult(result) {
|
|
522
|
+
if (result.status !== "succeeded" && result.status !== "failed") throw new Error("candidate execution terminal status is invalid");
|
|
523
|
+
assertExactObjectKeys(result, result.status === "succeeded" ? [
|
|
524
|
+
"status",
|
|
525
|
+
"usage",
|
|
526
|
+
"modelSettlement",
|
|
527
|
+
"taskOutcome",
|
|
528
|
+
"benchmarkResult",
|
|
529
|
+
"runReceipt"
|
|
530
|
+
] : [
|
|
531
|
+
"status",
|
|
532
|
+
"failureClass",
|
|
533
|
+
"usage",
|
|
534
|
+
"modelSettlement",
|
|
535
|
+
...result.failureEvidence ? ["failureEvidence"] : []
|
|
536
|
+
], "candidate execution terminal result");
|
|
537
|
+
const usage = sealUsage(result.usage);
|
|
538
|
+
const modelSettlement = sealArtifactRef(result.modelSettlement, "modelSettlement");
|
|
539
|
+
if (result.status === "succeeded") return Object.freeze({
|
|
540
|
+
status: "succeeded",
|
|
541
|
+
usage,
|
|
542
|
+
modelSettlement,
|
|
543
|
+
taskOutcome: sealArtifactRef(result.taskOutcome, "taskOutcome"),
|
|
544
|
+
benchmarkResult: sealArtifactRef(result.benchmarkResult, "benchmarkResult"),
|
|
545
|
+
runReceipt: sealArtifactRef(result.runReceipt, "runReceipt")
|
|
546
|
+
});
|
|
547
|
+
assertFailureClass(result.failureClass);
|
|
548
|
+
if (result.failureClass === "pre-model-infrastructure" && usage.modelCalls !== 0) throw new Error("pre-model infrastructure failure cannot contain model calls");
|
|
549
|
+
return Object.freeze({
|
|
550
|
+
status: "failed",
|
|
551
|
+
failureClass: result.failureClass,
|
|
552
|
+
usage,
|
|
553
|
+
modelSettlement,
|
|
554
|
+
...result.failureEvidence ? { failureEvidence: sealArtifactRef(result.failureEvidence, "failureEvidence") } : {}
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
function sealUsage(usage) {
|
|
558
|
+
assertExactObjectKeys(usage, [
|
|
559
|
+
"costUsdNanos",
|
|
560
|
+
"inputTokens",
|
|
561
|
+
"outputTokens",
|
|
562
|
+
"cachedInputTokens",
|
|
563
|
+
"reasoningTokens",
|
|
564
|
+
"modelCalls"
|
|
565
|
+
], "candidate execution terminal usage");
|
|
566
|
+
for (const [field, value] of Object.entries(usage)) assertCount$1(value, `terminal usage ${field}`);
|
|
567
|
+
return Object.freeze({
|
|
568
|
+
costUsdNanos: usage.costUsdNanos,
|
|
569
|
+
inputTokens: usage.inputTokens,
|
|
570
|
+
outputTokens: usage.outputTokens,
|
|
571
|
+
cachedInputTokens: usage.cachedInputTokens,
|
|
572
|
+
reasoningTokens: usage.reasoningTokens,
|
|
573
|
+
modelCalls: usage.modelCalls
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
function sealArtifactRef(ref, label) {
|
|
577
|
+
const parsed = agentCandidateArtifactRefSchema.parse(ref);
|
|
578
|
+
if (!Number.isSafeInteger(parsed.byteLength)) throw new Error(`candidate execution terminal ${label} byteLength exceeds safe integer range`);
|
|
579
|
+
return immutableCandidateValue(parsed);
|
|
580
|
+
}
|
|
581
|
+
function requireArtifactRef(value, path, field) {
|
|
582
|
+
return requireObject$1(value, path, field);
|
|
583
|
+
}
|
|
584
|
+
function requireString$1(value, path, field) {
|
|
585
|
+
if (typeof value !== "string") throw new Error(`candidate execution record at ${path} has invalid ${field}`);
|
|
586
|
+
return value;
|
|
587
|
+
}
|
|
588
|
+
function requireNumber$1(value, path, field) {
|
|
589
|
+
if (typeof value !== "number") throw new Error(`candidate execution record at ${path} has invalid ${field}`);
|
|
590
|
+
return value;
|
|
591
|
+
}
|
|
592
|
+
function requireObject$1(value, path, field) {
|
|
593
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error(`candidate execution record at ${path} has invalid ${field}`);
|
|
594
|
+
return value;
|
|
595
|
+
}
|
|
596
|
+
function requireTerminalStatus(value, path) {
|
|
597
|
+
if (value !== "succeeded" && value !== "failed") throw new Error(`candidate execution terminal record at ${path} has invalid status`);
|
|
598
|
+
return value;
|
|
599
|
+
}
|
|
600
|
+
function requireFailureClass(value, path) {
|
|
601
|
+
try {
|
|
602
|
+
assertFailureClass(value);
|
|
603
|
+
return value;
|
|
604
|
+
} catch (error) {
|
|
605
|
+
throw new Error(`candidate execution terminal record at ${path} has invalid failureClass`, { cause: error });
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
function assertFailureClass(value) {
|
|
609
|
+
if (value !== "pre-model-infrastructure" && value !== "execution" && value !== "post-model-infrastructure" && value !== "unknown") throw new Error("candidate execution failureClass is invalid");
|
|
610
|
+
}
|
|
611
|
+
function assertExecutionId$1(value) {
|
|
612
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9._:-]{1,200}$/.test(value)) throw new Error("candidate execution claim executionId is invalid");
|
|
613
|
+
}
|
|
614
|
+
function assertSha256Digest$1(value, field) {
|
|
615
|
+
if (!SHA256_PATTERN$1.test(value)) throw new Error(`candidate execution claim ${field} must be a lowercase sha256 digest`);
|
|
616
|
+
}
|
|
617
|
+
function assertCount$1(value, label) {
|
|
618
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new Error(`candidate execution ${label} must be a non-negative safe integer`);
|
|
619
|
+
}
|
|
620
|
+
function candidateCleanupTimeout(timeoutMs) {
|
|
621
|
+
const effective = timeoutMs ?? 3e4;
|
|
622
|
+
if (!Number.isSafeInteger(effective) || effective <= 0 || effective > 2147483647) throw new Error("candidate cleanup timeout is outside the supported timer range");
|
|
623
|
+
return effective;
|
|
624
|
+
}
|
|
625
|
+
function candidateCleanupDeadline(timeoutMs) {
|
|
626
|
+
return Date.now() + candidateCleanupTimeout(timeoutMs);
|
|
627
|
+
}
|
|
628
|
+
/** Freeze a separate result-construction budget; defaults to the task wall limit. */
|
|
629
|
+
function candidateResultTimeout(timeoutMs, taskTimeoutMs) {
|
|
630
|
+
const effective = timeoutMs ?? taskTimeoutMs;
|
|
631
|
+
if (!Number.isSafeInteger(effective) || effective <= 0 || effective > 2147483647) throw new Error("candidate result timeout is outside the supported timer range");
|
|
632
|
+
return effective;
|
|
633
|
+
}
|
|
634
|
+
/** Bound an evaluator cleanup call and cancel the underlying port at expiry. */
|
|
635
|
+
async function withinCandidateCleanupDeadline(operation, deadlineAtMs, label) {
|
|
636
|
+
return withinCandidateDeadline(operation, deadlineAtMs, new CandidateCleanupTimeoutError(label));
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Bound cancellable scoring/result work. Every side-effecting port called by
|
|
640
|
+
* `operation` must honor the supplied signal before durable publication.
|
|
641
|
+
*/
|
|
642
|
+
async function withinCandidateResultDeadline(operation, deadlineAtMs, label) {
|
|
643
|
+
return withinCandidateDeadline(operation, deadlineAtMs, new CandidateResultTimeoutError(label));
|
|
644
|
+
}
|
|
645
|
+
async function withinCandidateDeadline(operation, deadlineAtMs, timeoutError) {
|
|
646
|
+
const remainingMs = deadlineAtMs - Date.now();
|
|
647
|
+
if (remainingMs <= 0) throw timeoutError;
|
|
648
|
+
const controller = new AbortController();
|
|
649
|
+
const pending = Promise.resolve().then(() => operation(controller.signal));
|
|
650
|
+
pending.catch(() => void 0);
|
|
651
|
+
let timer;
|
|
652
|
+
try {
|
|
653
|
+
const result = await Promise.race([pending, new Promise((_resolve, reject) => {
|
|
654
|
+
timer = setTimeout(() => {
|
|
655
|
+
controller.abort(timeoutError);
|
|
656
|
+
reject(timeoutError);
|
|
657
|
+
}, remainingMs);
|
|
658
|
+
})]);
|
|
659
|
+
if (Date.now() >= deadlineAtMs) {
|
|
660
|
+
controller.abort(timeoutError);
|
|
661
|
+
throw timeoutError;
|
|
662
|
+
}
|
|
663
|
+
return result;
|
|
664
|
+
} finally {
|
|
665
|
+
if (timer) clearTimeout(timer);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
var CandidateCleanupTimeoutError = class extends Error {
|
|
669
|
+
constructor(label) {
|
|
670
|
+
super(`${label} did not complete before the evaluator cleanup deadline`);
|
|
671
|
+
this.name = "CandidateCleanupTimeoutError";
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
var CandidateResultTimeoutError = class extends Error {
|
|
675
|
+
constructor(label) {
|
|
676
|
+
super(`${label} did not complete before the evaluator result deadline`);
|
|
677
|
+
this.name = "CandidateResultTimeoutError";
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
//#endregion
|
|
681
|
+
//#region src/candidate-execution/execution-window.ts
|
|
682
|
+
const CANDIDATE_TERMINAL_PERSISTENCE_MARGIN_MS = 1e4;
|
|
683
|
+
const CANDIDATE_POST_RUN_CLEANUP_PHASES = 4;
|
|
684
|
+
/** Process stop, access closure, model evidence, and task/result evidence. */
|
|
685
|
+
function candidatePostRunWindowMs(cleanupTimeoutMs, resultTimeoutMs) {
|
|
686
|
+
const windowMs = cleanupTimeoutMs * CANDIDATE_POST_RUN_CLEANUP_PHASES + resultTimeoutMs + CANDIDATE_TERMINAL_PERSISTENCE_MARGIN_MS;
|
|
687
|
+
if (!Number.isSafeInteger(windowMs) || windowMs > 2147483647) throw new Error("candidate post-run window exceeds the supported timer range");
|
|
688
|
+
return windowMs;
|
|
689
|
+
}
|
|
690
|
+
/** Maximum owner lifetime from claim through process stop, access closure, and terminal write. */
|
|
691
|
+
function candidateExecutionOwnerWindowMs(timeoutMs, cleanupTimeoutMs, resultTimeoutMs) {
|
|
692
|
+
const windowMs = timeoutMs + candidatePostRunWindowMs(cleanupTimeoutMs, resultTimeoutMs);
|
|
693
|
+
if (!Number.isSafeInteger(windowMs) || windowMs > 2147483647) throw new Error("candidate execution and cleanup window exceeds the supported timer range");
|
|
694
|
+
return windowMs;
|
|
695
|
+
}
|
|
696
|
+
/** Time reserved after scoring for failure evidence plus terminal publication. */
|
|
697
|
+
function candidateTerminalWindowMs(cleanupTimeoutMs) {
|
|
698
|
+
const windowMs = cleanupTimeoutMs + CANDIDATE_TERMINAL_PERSISTENCE_MARGIN_MS;
|
|
699
|
+
if (!Number.isSafeInteger(windowMs) || windowMs > 2147483647) throw new Error("candidate terminal window exceeds the supported timer range");
|
|
700
|
+
return windowMs;
|
|
701
|
+
}
|
|
702
|
+
//#endregion
|
|
703
|
+
//#region src/candidate-execution/artifacts.ts
|
|
704
|
+
function artifactCacheKey(artifact) {
|
|
705
|
+
return `${artifact.sha256}:${artifact.byteLength}`;
|
|
706
|
+
}
|
|
707
|
+
async function readVerifiedArtifact(artifact, port) {
|
|
708
|
+
const bytes = "content" in artifact ? Buffer.from(artifact.content, "base64") : await port.read(artifact);
|
|
709
|
+
verifyBytes(bytes, artifact.sha256, artifact.byteLength, "candidate artifact");
|
|
710
|
+
return Uint8Array.from(bytes);
|
|
711
|
+
}
|
|
712
|
+
function verifyBytes(bytes, digest, byteLength, label) {
|
|
713
|
+
if (bytes.byteLength !== byteLength) throw new Error(`${label} byte length ${bytes.byteLength} does not match ${byteLength}`);
|
|
714
|
+
const actual = sha256Bytes(bytes);
|
|
715
|
+
if (actual !== digest) throw new Error(`${label} digest ${actual} does not match ${digest}`);
|
|
716
|
+
}
|
|
717
|
+
async function verifyWorkspaceSnapshotArtifacts(snapshot, port) {
|
|
718
|
+
const [manifest, archive] = await Promise.all([readVerifiedArtifact(snapshot.manifest, port), readVerifiedArtifact(snapshot.archive, port)]);
|
|
719
|
+
const canonicalManifest = canonicalCandidateBytes(snapshot.material);
|
|
720
|
+
if (!Buffer.from(manifest).equals(Buffer.from(canonicalManifest))) throw new Error("workspace manifest artifact is not the exact canonical manifest material");
|
|
721
|
+
if (sha256Bytes(canonicalManifest) !== snapshot.digest) throw new Error("workspace snapshot digest does not match its canonical manifest material");
|
|
722
|
+
return {
|
|
723
|
+
manifest,
|
|
724
|
+
archive
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
async function verifyMaterializedWorkspace(root, expected, options = {}) {
|
|
728
|
+
assertWorkspaceManifest((await scanWorkspace(root, new Set(options.ignoredProtectedRootEntries ?? []))).manifest, expected);
|
|
729
|
+
}
|
|
730
|
+
async function captureMaterializedWorkspace(root, options = {}) {
|
|
731
|
+
const observed = await scanWorkspace(root, new Set(options.ignoredProtectedRootEntries ?? []), options.limits);
|
|
732
|
+
return {
|
|
733
|
+
manifest: observed.manifest,
|
|
734
|
+
files: observed.files.map((file) => ({
|
|
735
|
+
path: file.path,
|
|
736
|
+
mode: file.mode,
|
|
737
|
+
bytes: Uint8Array.from(file.bytes)
|
|
738
|
+
}))
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
/** Capture exact verified regular-file bytes for fresh isolated materialization. */
|
|
742
|
+
async function readMaterializedWorkspaceFiles(root, expected, options = {}) {
|
|
743
|
+
const observed = await captureMaterializedWorkspace(root, options);
|
|
744
|
+
assertWorkspaceManifest(observed.manifest, expected);
|
|
745
|
+
return observed.files.map((file) => Object.freeze({
|
|
746
|
+
path: file.path,
|
|
747
|
+
mode: file.mode,
|
|
748
|
+
bytes: Uint8Array.from(file.bytes)
|
|
749
|
+
}));
|
|
750
|
+
}
|
|
751
|
+
function candidateWorkspaceManifest(files) {
|
|
752
|
+
return {
|
|
753
|
+
kind: "agent-candidate-workspace-manifest",
|
|
754
|
+
files: files.map((file) => ({
|
|
755
|
+
path: file.path,
|
|
756
|
+
mode: file.mode,
|
|
757
|
+
sha256: sha256Bytes(file.bytes),
|
|
758
|
+
byteLength: file.bytes.byteLength
|
|
759
|
+
})).sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0)
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
function assertWorkspaceManifest(observed, expected) {
|
|
763
|
+
if (!Buffer.from(canonicalCandidateBytes(observed)).equals(canonicalCandidateBytes(expected))) throw new Error("materialized workspace files, modes, or bytes do not match the signed manifest");
|
|
764
|
+
}
|
|
765
|
+
async function verifyMaterializedProfileWorkspace(root, expected) {
|
|
766
|
+
const observedProfile = (await scanWorkspace(root, /* @__PURE__ */ new Set())).manifest.files.map(({ path, mode, sha256 }) => ({
|
|
767
|
+
relPath: path,
|
|
768
|
+
mode,
|
|
769
|
+
contentSha256: sha256
|
|
770
|
+
}));
|
|
771
|
+
if (!Buffer.from(canonicalCandidateBytes(observedProfile)).equals(canonicalCandidateBytes(expected.files))) throw new Error("profile staging files, modes, or bytes do not match the signed profile plan");
|
|
772
|
+
}
|
|
773
|
+
async function scanWorkspace(root, ignoredProtectedRootEntries, limits) {
|
|
774
|
+
const absoluteRoot = resolve(root);
|
|
775
|
+
const rootStats = await lstat(absoluteRoot);
|
|
776
|
+
if (!rootStats.isDirectory() || rootStats.isSymbolicLink()) throw new Error("workspace root must be a real directory");
|
|
777
|
+
if (await realpath(absoluteRoot) !== absoluteRoot) throw new Error("workspace root has a symlinked path component");
|
|
778
|
+
const capturedFiles = [];
|
|
779
|
+
let totalBytes = 0;
|
|
780
|
+
async function visit(directory) {
|
|
781
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
782
|
+
entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
783
|
+
for (const entry of entries) {
|
|
784
|
+
if (directory === absoluteRoot && ignoredProtectedRootEntries.has(entry.name)) continue;
|
|
785
|
+
const absolute = resolve(directory, entry.name);
|
|
786
|
+
const relPath = relative(absoluteRoot, absolute).split(sep).join("/");
|
|
787
|
+
if (!relPath || relPath.startsWith("../") || relPath.includes("/../")) throw new Error(`workspace entry escapes root: ${relPath}`);
|
|
788
|
+
const stats = await lstat(absolute);
|
|
789
|
+
if (stats.isSymbolicLink()) throw new Error(`workspace contains a symlink: ${relPath}`);
|
|
790
|
+
if (stats.isDirectory()) {
|
|
791
|
+
await visit(absolute);
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
if (!stats.isFile()) throw new Error(`workspace contains a non-regular entry: ${relPath}`);
|
|
795
|
+
if (limits && capturedFiles.length >= limits.maxFiles) throw new Error("workspace exceeds maxFiles");
|
|
796
|
+
const descriptor = await open(absolute, constants.O_RDONLY | (typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0));
|
|
797
|
+
try {
|
|
798
|
+
const openedStats = await descriptor.stat();
|
|
799
|
+
if (!openedStats.isFile()) throw new Error(`workspace contains a non-regular entry: ${relPath}`);
|
|
800
|
+
if (openedStats.nlink !== 1) throw new Error(`workspace contains a hard-linked file: ${relPath}`);
|
|
801
|
+
const mode = openedStats.mode & 511;
|
|
802
|
+
if (limits && openedStats.size > limits.maxFileBytes) throw new Error(`workspace file exceeds maxFileBytes: ${relPath}`);
|
|
803
|
+
const remainingBytes = limits ? limits.maxTotalFileBytes - totalBytes : void 0;
|
|
804
|
+
if (remainingBytes !== void 0 && openedStats.size > remainingBytes) throw new Error("workspace exceeds maxTotalFileBytes");
|
|
805
|
+
const bytes = await readBoundedFile(descriptor, limits ? Math.min(limits.maxFileBytes, remainingBytes ?? limits.maxFileBytes) : void 0, relPath);
|
|
806
|
+
totalBytes += bytes.byteLength;
|
|
807
|
+
capturedFiles.push({
|
|
808
|
+
path: relPath,
|
|
809
|
+
mode,
|
|
810
|
+
bytes: Uint8Array.from(bytes)
|
|
811
|
+
});
|
|
812
|
+
} finally {
|
|
813
|
+
await descriptor.close();
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
await visit(absoluteRoot);
|
|
818
|
+
return {
|
|
819
|
+
manifest: candidateWorkspaceManifest(capturedFiles),
|
|
820
|
+
files: capturedFiles
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
async function readBoundedFile(descriptor, maxBytes, path) {
|
|
824
|
+
if (maxBytes === void 0) return await descriptor.readFile();
|
|
825
|
+
const chunks = [];
|
|
826
|
+
let total = 0;
|
|
827
|
+
while (true) {
|
|
828
|
+
const remaining = maxBytes - total;
|
|
829
|
+
const chunk = Buffer.allocUnsafe(Math.min(64 * 1024, remaining + 1));
|
|
830
|
+
const { bytesRead } = await descriptor.read(chunk, 0, chunk.byteLength, null);
|
|
831
|
+
if (bytesRead === 0) break;
|
|
832
|
+
total += bytesRead;
|
|
833
|
+
if (total > maxBytes) throw new Error(`workspace file exceeds its capture limit: ${path}`);
|
|
834
|
+
chunks.push(chunk.subarray(0, bytesRead));
|
|
835
|
+
}
|
|
836
|
+
return Buffer.concat(chunks, total);
|
|
837
|
+
}
|
|
838
|
+
//#endregion
|
|
839
|
+
//#region src/candidate-execution/git-materialize.ts
|
|
840
|
+
async function verifyCandidateCode(code, repositories, patchBytes) {
|
|
841
|
+
if (code.kind === "disabled") return void 0;
|
|
842
|
+
const repositoryRoot = await verifiedRepositoryRoot(code.repository, repositories);
|
|
843
|
+
await assertCommitAndBaseTree(repositoryRoot, code.baseCommit, code.baseTree);
|
|
844
|
+
if (code.kind === "no-op") {
|
|
845
|
+
await assertSafeTree(repositoryRoot, code.baseTree);
|
|
846
|
+
return code.baseTree;
|
|
847
|
+
}
|
|
848
|
+
if (!patchBytes) throw new Error("git-patch candidate is missing verified patch bytes");
|
|
849
|
+
const temporary = await mkdtemp(join(tmpdir(), "agent-candidate-git-"));
|
|
850
|
+
try {
|
|
851
|
+
const indexFile = join(temporary, "index");
|
|
852
|
+
await runCandidateGit(repositoryRoot, ["read-tree", code.baseTree], void 0, { GIT_INDEX_FILE: indexFile });
|
|
853
|
+
await runCandidateGit(repositoryRoot, [
|
|
854
|
+
"apply",
|
|
855
|
+
"--cached",
|
|
856
|
+
"--binary",
|
|
857
|
+
"--whitespace=nowarn",
|
|
858
|
+
"-"
|
|
859
|
+
], patchBytes, { GIT_INDEX_FILE: indexFile });
|
|
860
|
+
const candidateTree = (await runCandidateGit(repositoryRoot, ["write-tree"], void 0, { GIT_INDEX_FILE: indexFile })).stdout.toString("utf8").trim();
|
|
861
|
+
if (candidateTree !== code.candidateTree) throw new Error(`git patch materialized tree ${candidateTree} does not match ${code.candidateTree}`);
|
|
862
|
+
await assertSafeTree(repositoryRoot, candidateTree);
|
|
863
|
+
return candidateTree;
|
|
864
|
+
} finally {
|
|
865
|
+
await rm(temporary, {
|
|
866
|
+
recursive: true,
|
|
867
|
+
force: true
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
async function readCandidateGitHubResource(resource, repositories) {
|
|
872
|
+
const repositoryRoot = await verifiedRepositoryRoot(resource.repository, repositories);
|
|
873
|
+
if ((await runCandidateGit(repositoryRoot, [
|
|
874
|
+
"cat-file",
|
|
875
|
+
"-t",
|
|
876
|
+
resource.commit
|
|
877
|
+
])).stdout.toString("utf8").trim() !== "commit") throw new Error(`GitHub resource commit is not a commit object: ${resource.commit}`);
|
|
878
|
+
const listing = (await runCandidateGit(repositoryRoot, [
|
|
879
|
+
"ls-tree",
|
|
880
|
+
"-z",
|
|
881
|
+
resource.commit,
|
|
882
|
+
"--",
|
|
883
|
+
resource.path
|
|
884
|
+
])).stdout;
|
|
885
|
+
const entries = parseTreeEntries(listing);
|
|
886
|
+
if (entries.length !== 1 || entries[0]?.path !== resource.path) throw new Error(`GitHub resource path is not one exact file: ${resource.path}`);
|
|
887
|
+
const entry = entries[0];
|
|
888
|
+
if (entry?.type !== "blob" || entry.mode !== "100644" && entry.mode !== "100755") throw new Error(`GitHub resource path is not a regular Git blob: ${resource.path}`);
|
|
889
|
+
const bytes = (await runCandidateGit(repositoryRoot, [
|
|
890
|
+
"cat-file",
|
|
891
|
+
"blob",
|
|
892
|
+
entry.object
|
|
893
|
+
])).stdout;
|
|
894
|
+
verifyBytes(bytes, resource.sha256, resource.byteLength, `GitHub resource ${resource.path}`);
|
|
895
|
+
return Uint8Array.from(bytes);
|
|
896
|
+
}
|
|
897
|
+
async function verifyTaskCheckout(taskRoot, expected) {
|
|
898
|
+
const root = resolve(taskRoot);
|
|
899
|
+
const head = (await runCandidateGit(root, ["rev-parse", "HEAD"])).stdout.toString("utf8").trim();
|
|
900
|
+
if (head !== expected.baseCommit) throw new Error(`task checkout HEAD ${head} does not match ${expected.baseCommit}`);
|
|
901
|
+
const tree = (await runCandidateGit(root, ["rev-parse", "HEAD^{tree}"])).stdout.toString("utf8").trim();
|
|
902
|
+
if (tree !== expected.baseTree) throw new Error(`task checkout base tree ${tree} does not match ${expected.baseTree}`);
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Apply an evaluator-captured binary diff in a detached object database and
|
|
906
|
+
* prove its result tree exactly matches the captured after-state manifest.
|
|
907
|
+
*/
|
|
908
|
+
async function verifyTaskOutcomePatch(input) {
|
|
909
|
+
const repositoryRoot = resolve(input.repositoryRoot);
|
|
910
|
+
await verifyTaskCheckout(repositoryRoot, input);
|
|
911
|
+
const gitDir = resolve((await runCandidateGit(repositoryRoot, ["rev-parse", "--absolute-git-dir"])).stdout.toString("utf8").trim());
|
|
912
|
+
if (await realpath(gitDir) !== gitDir || gitDir.includes(":")) throw new Error("task Git object store has an unsupported path");
|
|
913
|
+
await assertNoGitIndirection(repositoryRoot, gitDir, "task repository");
|
|
914
|
+
const temporary = await mkdtemp(join(tmpdir(), "agent-candidate-task-outcome-"));
|
|
915
|
+
try {
|
|
916
|
+
const objectDirectory = join(temporary, "objects");
|
|
917
|
+
const indexFile = join(temporary, "index");
|
|
918
|
+
await mkdir(objectDirectory);
|
|
919
|
+
const gitEnvironment = {
|
|
920
|
+
GIT_INDEX_FILE: indexFile,
|
|
921
|
+
GIT_OBJECT_DIRECTORY: objectDirectory,
|
|
922
|
+
GIT_ALTERNATE_OBJECT_DIRECTORIES: join(gitDir, "objects")
|
|
923
|
+
};
|
|
924
|
+
await runCandidateGit(repositoryRoot, ["read-tree", input.baseTree], void 0, gitEnvironment);
|
|
925
|
+
if (input.patch.byteLength > 0) await runCandidateGit(repositoryRoot, [
|
|
926
|
+
"apply",
|
|
927
|
+
"--cached",
|
|
928
|
+
"--binary",
|
|
929
|
+
"--whitespace=nowarn",
|
|
930
|
+
"-"
|
|
931
|
+
], input.patch, gitEnvironment);
|
|
932
|
+
const resultTree = (await runCandidateGit(repositoryRoot, ["write-tree"], void 0, gitEnvironment)).stdout.toString("utf8").trim();
|
|
933
|
+
if (resultTree !== input.resultTree) throw new Error(`task outcome patch materialized tree ${resultTree} does not match ${input.resultTree}`);
|
|
934
|
+
await assertSafeTree(repositoryRoot, resultTree, gitEnvironment);
|
|
935
|
+
const observed = await workspaceManifestFromGitTree(repositoryRoot, resultTree, gitEnvironment);
|
|
936
|
+
if (!Buffer.from(canonicalCandidateBytes(observed)).equals(Buffer.from(canonicalCandidateBytes(input.afterState)))) throw new Error("task outcome after-state does not match the materialized result tree");
|
|
937
|
+
const resultCommit = (await runCandidateGit(repositoryRoot, [
|
|
938
|
+
"commit-tree",
|
|
939
|
+
resultTree,
|
|
940
|
+
"-p",
|
|
941
|
+
input.baseCommit
|
|
942
|
+
], Buffer.from("candidate task outcome\n", "utf8"), {
|
|
943
|
+
...gitEnvironment,
|
|
944
|
+
GIT_AUTHOR_NAME: "Tangle Evaluator",
|
|
945
|
+
GIT_AUTHOR_EMAIL: "evaluator@tangle.tools",
|
|
946
|
+
GIT_AUTHOR_DATE: "2000-01-01T00:00:00Z",
|
|
947
|
+
GIT_COMMITTER_NAME: "Tangle Evaluator",
|
|
948
|
+
GIT_COMMITTER_EMAIL: "evaluator@tangle.tools",
|
|
949
|
+
GIT_COMMITTER_DATE: "2000-01-01T00:00:00Z"
|
|
950
|
+
})).stdout.toString("utf8").trim();
|
|
951
|
+
if ((await runCandidateGit(repositoryRoot, ["rev-parse", `${resultCommit}^{tree}`], void 0, gitEnvironment)).stdout.toString("utf8").trim() !== resultTree) throw new Error("task outcome evaluator commit does not bind the verified result tree");
|
|
952
|
+
return {
|
|
953
|
+
resultTree,
|
|
954
|
+
resultCommit
|
|
955
|
+
};
|
|
956
|
+
} finally {
|
|
957
|
+
await rm(temporary, {
|
|
958
|
+
recursive: true,
|
|
959
|
+
force: true
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
async function verifiedRepositoryRoot(repository, repositories) {
|
|
964
|
+
const repositoryRoot = resolve(await repositories.resolve(repository));
|
|
965
|
+
if (!(await stat(repositoryRoot)).isDirectory()) throw new Error("candidate repository path is not a directory");
|
|
966
|
+
const origin = (await runCandidateGit(repositoryRoot, [
|
|
967
|
+
"remote",
|
|
968
|
+
"get-url",
|
|
969
|
+
"origin"
|
|
970
|
+
])).stdout.toString("utf8").trim();
|
|
971
|
+
const actual = parseGitHubRemote(origin);
|
|
972
|
+
if (!actual || actual.owner !== repository.owner || actual.repo !== repository.repo) throw new Error(`local repository origin ${origin || "<missing>"} does not match github.com/${repository.owner}/${repository.repo}`);
|
|
973
|
+
await assertNoGitIndirection(repositoryRoot, resolve((await runCandidateGit(repositoryRoot, ["rev-parse", "--absolute-git-dir"])).stdout.toString("utf8").trim()), "candidate repository");
|
|
974
|
+
return repositoryRoot;
|
|
975
|
+
}
|
|
976
|
+
async function assertNoGitIndirection(repositoryRoot, gitDir, label) {
|
|
977
|
+
if ((await runCandidateGit(repositoryRoot, [
|
|
978
|
+
"for-each-ref",
|
|
979
|
+
"--format=%(refname)",
|
|
980
|
+
"refs/replace"
|
|
981
|
+
])).stdout.toString("utf8").trim()) throw new Error(`${label} contains Git replace refs`);
|
|
982
|
+
const commonDir = resolve(repositoryRoot, (await runCandidateGit(repositoryRoot, ["rev-parse", "--git-common-dir"])).stdout.toString("utf8").trim());
|
|
983
|
+
if (await realpath(commonDir) !== commonDir || commonDir.includes(":")) throw new Error(`${label} Git common directory has an unsupported path`);
|
|
984
|
+
for (const objectRoot of /* @__PURE__ */ new Set([gitDir, commonDir])) for (const name of ["alternates", "http-alternates"]) {
|
|
985
|
+
const path = join(objectRoot, "objects", "info", name);
|
|
986
|
+
try {
|
|
987
|
+
if ((await readFile(path, "utf8")).trim()) throw new Error(`${label} uses forbidden Git ${name}`);
|
|
988
|
+
} catch (error) {
|
|
989
|
+
if (!isNoEntry(error)) throw error;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
async function assertCommitAndBaseTree(repositoryRoot, commit, expectedTree) {
|
|
994
|
+
if ((await runCandidateGit(repositoryRoot, [
|
|
995
|
+
"cat-file",
|
|
996
|
+
"-t",
|
|
997
|
+
commit
|
|
998
|
+
])).stdout.toString("utf8").trim() !== "commit") throw new Error(`candidate base object is not a commit: ${commit}`);
|
|
999
|
+
const actualTree = (await runCandidateGit(repositoryRoot, ["rev-parse", `${commit}^{tree}`])).stdout.toString("utf8").trim();
|
|
1000
|
+
if (actualTree !== expectedTree) throw new Error(`candidate base commit tree ${actualTree} does not match ${expectedTree}`);
|
|
1001
|
+
}
|
|
1002
|
+
async function assertSafeTree(repositoryRoot, tree, environment = {}) {
|
|
1003
|
+
const listing = (await runCandidateGit(repositoryRoot, [
|
|
1004
|
+
"ls-tree",
|
|
1005
|
+
"-rz",
|
|
1006
|
+
"--full-tree",
|
|
1007
|
+
tree
|
|
1008
|
+
], void 0, environment)).stdout;
|
|
1009
|
+
const entries = parseTreeEntries(listing);
|
|
1010
|
+
if (entries.length === 0) throw new Error("candidate Git tree cannot be empty");
|
|
1011
|
+
for (const entry of entries) {
|
|
1012
|
+
if (entry.type !== "blob" || entry.mode !== "100644" && entry.mode !== "100755") throw new Error(`candidate Git tree contains a symlink, submodule, or non-blob: ${entry.path}`);
|
|
1013
|
+
assertSafeGitPath(entry.path);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
async function workspaceManifestFromGitTree(repositoryRoot, tree, environment) {
|
|
1017
|
+
return candidateWorkspaceManifest(await readCandidateGitTreeFiles(repositoryRoot, tree, environment));
|
|
1018
|
+
}
|
|
1019
|
+
async function readCandidateGitTreeFiles(repositoryRoot, tree, environment = {}, limits) {
|
|
1020
|
+
const listing = (await runCandidateGit(repositoryRoot, [
|
|
1021
|
+
"ls-tree",
|
|
1022
|
+
"-rzl",
|
|
1023
|
+
"--full-tree",
|
|
1024
|
+
tree
|
|
1025
|
+
], void 0, environment)).stdout;
|
|
1026
|
+
const files = [];
|
|
1027
|
+
const entries = parseTreeEntries(listing);
|
|
1028
|
+
if (limits && entries.length > limits.maxFiles) throw new Error("candidate Git tree exceeds maxFiles");
|
|
1029
|
+
let totalBytes = 0;
|
|
1030
|
+
for (const entry of entries) {
|
|
1031
|
+
if (entry.type !== "blob" || entry.mode !== "100644" && entry.mode !== "100755") throw new Error(`candidate Git tree contains a non-regular file: ${entry.path}`);
|
|
1032
|
+
assertSafeGitPath(entry.path);
|
|
1033
|
+
if (entry.size === void 0) throw new Error(`candidate Git tree is missing blob size: ${entry.path}`);
|
|
1034
|
+
if (limits && entry.size > limits.maxFileBytes) throw new Error(`candidate Git tree file exceeds maxFileBytes: ${entry.path}`);
|
|
1035
|
+
totalBytes += entry.size;
|
|
1036
|
+
if (limits && totalBytes > limits.maxTotalFileBytes) throw new Error("candidate Git tree exceeds maxTotalFileBytes");
|
|
1037
|
+
const bytes = (await runCandidateGit(repositoryRoot, [
|
|
1038
|
+
"cat-file",
|
|
1039
|
+
"blob",
|
|
1040
|
+
entry.object
|
|
1041
|
+
], void 0, environment)).stdout;
|
|
1042
|
+
if (bytes.byteLength !== entry.size) throw new Error(`candidate Git blob size changed: ${entry.path}`);
|
|
1043
|
+
files.push({
|
|
1044
|
+
path: entry.path,
|
|
1045
|
+
mode: entry.mode === "100755" ? 493 : 420,
|
|
1046
|
+
bytes: Uint8Array.from(bytes)
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
return files.sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
|
|
1050
|
+
}
|
|
1051
|
+
function parseTreeEntries(bytes) {
|
|
1052
|
+
const raw = Buffer.from(bytes);
|
|
1053
|
+
const decoded = raw.toString("utf8");
|
|
1054
|
+
if (!Buffer.from(decoded, "utf8").equals(raw)) throw new Error("candidate Git tree contains a non-UTF-8 path");
|
|
1055
|
+
return decoded.split("\0").filter(Boolean).map((row) => {
|
|
1056
|
+
const tab = row.indexOf(" ");
|
|
1057
|
+
const header = row.slice(0, tab).split(" ").filter(Boolean);
|
|
1058
|
+
const path = row.slice(tab + 1);
|
|
1059
|
+
const [mode, type, object, sizeText] = header;
|
|
1060
|
+
if (tab < 1 || !mode || !type || !object || !path) throw new Error("malformed Git tree entry");
|
|
1061
|
+
const size = sizeText && /^\d+$/.test(sizeText) ? Number(sizeText) : void 0;
|
|
1062
|
+
if (size !== void 0 && !Number.isSafeInteger(size)) throw new Error("candidate Git tree entry size exceeds the safe integer range");
|
|
1063
|
+
return {
|
|
1064
|
+
mode,
|
|
1065
|
+
type,
|
|
1066
|
+
object,
|
|
1067
|
+
...size === void 0 ? {} : { size },
|
|
1068
|
+
path
|
|
1069
|
+
};
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
function assertSafeGitPath(path) {
|
|
1073
|
+
if (!path || path.startsWith("/") || path.includes("\\") || path.includes("\0") || hasControlCharacter$1(path) || path.split("/").some((part) => !part || part === "." || part === "..") || path.split("/")[0]?.toLowerCase() === ".git") throw new Error(`candidate Git tree contains an unsafe path: ${path}`);
|
|
1074
|
+
}
|
|
1075
|
+
function hasControlCharacter$1(value) {
|
|
1076
|
+
for (let index = 0; index < value.length; index++) {
|
|
1077
|
+
const code = value.charCodeAt(index);
|
|
1078
|
+
if (code < 32 || code === 127) return true;
|
|
1079
|
+
}
|
|
1080
|
+
return false;
|
|
1081
|
+
}
|
|
1082
|
+
function parseGitHubRemote(value) {
|
|
1083
|
+
const match = value.match(/^(?:https?:\/\/github\.com\/|ssh:\/\/git@github\.com\/|git@github\.com:)([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
1084
|
+
if (!match?.[1] || !match[2]) return void 0;
|
|
1085
|
+
return {
|
|
1086
|
+
owner: match[1],
|
|
1087
|
+
repo: match[2]
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
async function runCandidateGit(repositoryRoot, args, input, extraEnv = {}) {
|
|
1091
|
+
const env = Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith("GIT_")));
|
|
1092
|
+
Object.assign(env, {
|
|
1093
|
+
GIT_CONFIG_NOSYSTEM: "1",
|
|
1094
|
+
GIT_CONFIG_GLOBAL: "/dev/null",
|
|
1095
|
+
GIT_CONFIG_SYSTEM: "/dev/null",
|
|
1096
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
1097
|
+
GIT_NO_REPLACE_OBJECTS: "1",
|
|
1098
|
+
LC_ALL: "C",
|
|
1099
|
+
...extraEnv
|
|
1100
|
+
});
|
|
1101
|
+
const fullArgs = [
|
|
1102
|
+
"-c",
|
|
1103
|
+
"core.hooksPath=/dev/null",
|
|
1104
|
+
"-c",
|
|
1105
|
+
"core.fsmonitor=false",
|
|
1106
|
+
"-c",
|
|
1107
|
+
"core.untrackedCache=false",
|
|
1108
|
+
"-c",
|
|
1109
|
+
"protocol.file.allow=never",
|
|
1110
|
+
"-C",
|
|
1111
|
+
repositoryRoot,
|
|
1112
|
+
...args
|
|
1113
|
+
];
|
|
1114
|
+
return await new Promise((resolveResult, reject) => {
|
|
1115
|
+
const child = spawn("git", fullArgs, {
|
|
1116
|
+
env,
|
|
1117
|
+
stdio: [
|
|
1118
|
+
"pipe",
|
|
1119
|
+
"pipe",
|
|
1120
|
+
"pipe"
|
|
1121
|
+
]
|
|
1122
|
+
});
|
|
1123
|
+
const stdout = [];
|
|
1124
|
+
const stderr = [];
|
|
1125
|
+
child.stdout.on("data", (chunk) => stdout.push(chunk));
|
|
1126
|
+
child.stderr.on("data", (chunk) => stderr.push(chunk));
|
|
1127
|
+
child.on("error", reject);
|
|
1128
|
+
child.on("close", (code, signal) => {
|
|
1129
|
+
const out = Buffer.concat(stdout);
|
|
1130
|
+
const err = Buffer.concat(stderr);
|
|
1131
|
+
if (code !== 0) {
|
|
1132
|
+
reject(/* @__PURE__ */ new Error(`git ${args[0] ?? "<command>"} failed (${signal ?? code}): ${err.toString("utf8").trim()}`));
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
resolveResult({
|
|
1136
|
+
stdout: out,
|
|
1137
|
+
stderr: err
|
|
1138
|
+
});
|
|
1139
|
+
});
|
|
1140
|
+
if (input) child.stdin.end(input);
|
|
1141
|
+
else child.stdin.end();
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
function isNoEntry(error) {
|
|
1145
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
1146
|
+
}
|
|
1147
|
+
//#endregion
|
|
1148
|
+
//#region src/candidate-execution/types.ts
|
|
1149
|
+
const verifiedCandidateBrand = Symbol("verifiedAgentCandidate");
|
|
1150
|
+
const preparedCandidateBrand = Symbol("preparedAgentCandidate");
|
|
1151
|
+
const verifiedTaskOutcomeBrand = Symbol("verifiedTaskOutcome");
|
|
1152
|
+
/** Protected trace tags that bind a run to one prepared candidate execution. */
|
|
1153
|
+
const CANDIDATE_TRACE_TAGS = {
|
|
1154
|
+
executionId: "tangle.candidate.execution_id",
|
|
1155
|
+
bundleDigest: "tangle.candidate.bundle_digest",
|
|
1156
|
+
executionPlanDigest: "tangle.candidate.execution_plan_digest",
|
|
1157
|
+
materializationReceiptDigest: "tangle.candidate.materialization_receipt_digest"
|
|
1158
|
+
};
|
|
1159
|
+
/** Environment keys used to propagate immutable candidate trace identity. */
|
|
1160
|
+
const CANDIDATE_TRACE_ENV = {
|
|
1161
|
+
executionId: "TANGLE_CANDIDATE_EXECUTION_ID",
|
|
1162
|
+
bundleDigest: "TANGLE_CANDIDATE_BUNDLE_DIGEST",
|
|
1163
|
+
executionPlanDigest: "TANGLE_CANDIDATE_EXECUTION_PLAN_DIGEST",
|
|
1164
|
+
materializationReceiptDigest: "TANGLE_CANDIDATE_MATERIALIZATION_RECEIPT_DIGEST",
|
|
1165
|
+
traceRunId: "TANGLE_TRACE_RUN_ID"
|
|
1166
|
+
};
|
|
1167
|
+
//#endregion
|
|
1168
|
+
//#region src/candidate-execution/prepared-state.ts
|
|
1169
|
+
const stateByExecution = /* @__PURE__ */ new WeakMap();
|
|
1170
|
+
const lifecycleByExecution = /* @__PURE__ */ new WeakMap();
|
|
1171
|
+
function createPreparedCandidateExecution(input) {
|
|
1172
|
+
const state = detachPreparedCandidateState(input);
|
|
1173
|
+
assertPrivateCandidateIntegrity(state);
|
|
1174
|
+
const prepared = Object.freeze({
|
|
1175
|
+
bundle: state.bundle,
|
|
1176
|
+
benchmark: Object.freeze({
|
|
1177
|
+
suite: state.benchmarkSuite,
|
|
1178
|
+
task: state.benchmarkTask
|
|
1179
|
+
}),
|
|
1180
|
+
executionId: state.executionId,
|
|
1181
|
+
roots: state.roots,
|
|
1182
|
+
profilePlan: evidenceView(state.profilePlan),
|
|
1183
|
+
profileActivation: state.profileActivation,
|
|
1184
|
+
executionPlan: evidenceView(state.executionPlan),
|
|
1185
|
+
materializationReceipt: state.materializationReceipt,
|
|
1186
|
+
launch: state.launch,
|
|
1187
|
+
instruction: bytesView(state.instruction, "bytes"),
|
|
1188
|
+
resolvedModel: state.resolvedModel,
|
|
1189
|
+
...state.knowledge ? { knowledge: knowledgeView(state.knowledge) } : {},
|
|
1190
|
+
trace: state.trace,
|
|
1191
|
+
memory: state.memory,
|
|
1192
|
+
[preparedCandidateBrand]: true
|
|
1193
|
+
});
|
|
1194
|
+
stateByExecution.set(prepared, state);
|
|
1195
|
+
lifecycleByExecution.set(prepared, { status: "prepared" });
|
|
1196
|
+
return prepared;
|
|
1197
|
+
}
|
|
1198
|
+
function getPreparedCandidateState(prepared) {
|
|
1199
|
+
const state = stateByExecution.get(prepared);
|
|
1200
|
+
if (!state || prepared[preparedCandidateBrand] !== true) throw new Error("execution must come from prepareAgentCandidateExecution");
|
|
1201
|
+
return state;
|
|
1202
|
+
}
|
|
1203
|
+
/** Revalidates the exact private bytes immediately before execution or finalization. */
|
|
1204
|
+
function assertPreparedCandidateIntegrity(prepared) {
|
|
1205
|
+
const state = getPreparedCandidateState(prepared);
|
|
1206
|
+
assertPrivateCandidateIntegrity(state);
|
|
1207
|
+
return state;
|
|
1208
|
+
}
|
|
1209
|
+
/** Atomically reserves this in-memory prepared value before the first await. */
|
|
1210
|
+
function beginPreparedCandidateClaim(prepared) {
|
|
1211
|
+
const state = assertPreparedCandidateIntegrity(prepared);
|
|
1212
|
+
transitionLifecycle(prepared, ["prepared"], "claiming");
|
|
1213
|
+
return state;
|
|
1214
|
+
}
|
|
1215
|
+
/** Claim an unexecuted preparation for explicit resource disposal. */
|
|
1216
|
+
function beginPreparedCandidateDisposal(prepared) {
|
|
1217
|
+
const state = assertPreparedCandidateIntegrity(prepared);
|
|
1218
|
+
transitionLifecycle(prepared, [
|
|
1219
|
+
"prepared",
|
|
1220
|
+
"disposal-failed",
|
|
1221
|
+
"cleanup-failed"
|
|
1222
|
+
], "disposing");
|
|
1223
|
+
return state;
|
|
1224
|
+
}
|
|
1225
|
+
function markPreparedCandidateClaimed(prepared) {
|
|
1226
|
+
transitionLifecycle(prepared, ["claiming"], "claimed");
|
|
1227
|
+
}
|
|
1228
|
+
/** Reveal protected values only after the durable claim and workspace checks succeed. */
|
|
1229
|
+
function beginPreparedCandidateRun(prepared, modelAccess, memoryAccess) {
|
|
1230
|
+
const state = assertPreparedCandidateIntegrity(prepared);
|
|
1231
|
+
assertProtectedEnvironment(state, modelAccess.env, memoryAccess?.env);
|
|
1232
|
+
transitionLifecycle(prepared, ["claimed"], "running");
|
|
1233
|
+
const completeEnvironment = immutableCandidateValue({
|
|
1234
|
+
...state.launch.env,
|
|
1235
|
+
...modelAccess.env,
|
|
1236
|
+
...memoryAccess?.env ?? {},
|
|
1237
|
+
...state.trace.env
|
|
1238
|
+
});
|
|
1239
|
+
const material = state.executionPlan.value.material;
|
|
1240
|
+
return {
|
|
1241
|
+
state,
|
|
1242
|
+
request: Object.freeze({
|
|
1243
|
+
executionId: state.executionId,
|
|
1244
|
+
benchmark: preparedBenchmark(state),
|
|
1245
|
+
inputs: Object.freeze({
|
|
1246
|
+
task: workspaceInputView(state.benchmarkTask.workspace, state.executorInputs.taskFiles),
|
|
1247
|
+
...material.candidateWorkspace && state.executorInputs.candidateFiles ? { candidate: workspaceInputView(material.candidateWorkspace, state.executorInputs.candidateFiles) } : {},
|
|
1248
|
+
profile: Object.freeze({ files: Object.freeze(state.executorInputs.profileFiles.map((file) => profileFileView(file))) })
|
|
1249
|
+
}),
|
|
1250
|
+
roots: state.roots.execution,
|
|
1251
|
+
profilePlan: evidenceView(state.profilePlan),
|
|
1252
|
+
profileActivation: state.profileActivation,
|
|
1253
|
+
executionPlan: evidenceView(state.executionPlan),
|
|
1254
|
+
materializationReceipt: state.materializationReceipt,
|
|
1255
|
+
launch: immutableCandidateValue({
|
|
1256
|
+
...state.launch,
|
|
1257
|
+
env: completeEnvironment
|
|
1258
|
+
}),
|
|
1259
|
+
instruction: bytesView(state.instruction, "bytes"),
|
|
1260
|
+
resolvedModel: state.resolvedModel,
|
|
1261
|
+
hardLimits: Object.freeze({ timeoutMs: state.executionPlan.value.material.limits.timeoutMs }),
|
|
1262
|
+
observedLimits: Object.freeze({ maxSteps: state.executionPlan.value.material.limits.maxSteps }),
|
|
1263
|
+
...state.knowledge ? { knowledge: knowledgeView(state.knowledge) } : {},
|
|
1264
|
+
trace: state.trace,
|
|
1265
|
+
memory: state.memory
|
|
1266
|
+
})
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
function preparedBenchmark(state) {
|
|
1270
|
+
return Object.freeze({
|
|
1271
|
+
suite: state.benchmarkSuite,
|
|
1272
|
+
task: state.benchmarkTask
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
function consumePreparedCandidateExecution(prepared, outcome) {
|
|
1276
|
+
transitionLifecycle(prepared, outcome === "disposed" || outcome === "disposal-failed" ? ["disposing"] : ["settling"], outcome);
|
|
1277
|
+
}
|
|
1278
|
+
function beginPreparedCandidateSettlement(prepared) {
|
|
1279
|
+
transitionLifecycle(prepared, [
|
|
1280
|
+
"claiming",
|
|
1281
|
+
"claimed",
|
|
1282
|
+
"running"
|
|
1283
|
+
], "settling");
|
|
1284
|
+
}
|
|
1285
|
+
/** Rechecks every mutable staging byte immediately before handing control to an executor. */
|
|
1286
|
+
async function assertPreparedCandidateWorkspaces(state) {
|
|
1287
|
+
const plan = state.executionPlan.value.material;
|
|
1288
|
+
await verifyMaterializedWorkspace(state.roots.staging.taskRoot, state.benchmarkTask.workspace.material, { ignoredProtectedRootEntries: [".git", ".sidecar"] });
|
|
1289
|
+
if (state.benchmarkTask.repository) await verifyTaskCheckout(state.roots.staging.taskRoot, state.benchmarkTask.repository);
|
|
1290
|
+
await verifyMaterializedProfileWorkspace(state.roots.staging.profileRoot, state.profilePlan.value.material);
|
|
1291
|
+
if (plan.candidateWorkspace) {
|
|
1292
|
+
const candidateRoot = state.roots.staging.candidateRoot;
|
|
1293
|
+
if (!candidateRoot) throw new Error("prepared candidate staging root is missing");
|
|
1294
|
+
await verifyMaterializedWorkspace(candidateRoot, plan.candidateWorkspace.material);
|
|
1295
|
+
} else if (state.roots.staging.candidateRoot !== void 0) throw new Error("disabled candidate unexpectedly has a staging root");
|
|
1296
|
+
}
|
|
1297
|
+
function detachPreparedCandidateState(input) {
|
|
1298
|
+
const benchmarkSuite = immutableCandidateValue(agentCandidateBenchmarkSuiteSchema.parse(input.benchmarkSuite));
|
|
1299
|
+
const benchmarkTask = immutableCandidateValue(agentCandidateBenchmarkTaskSchema.parse(input.benchmarkTask));
|
|
1300
|
+
const profilePlan = immutableCandidateValue(agentCandidateProfilePlanEvidenceSchema.parse(input.profilePlan.value));
|
|
1301
|
+
const profileActivation = parseAgentCandidateProfileActivation(input.profileActivation, profilePlan.digest);
|
|
1302
|
+
const executionPlan = immutableCandidateValue(agentCandidateExecutionPlanEvidenceSchema.parse(input.executionPlan.value));
|
|
1303
|
+
const materializationReceipt = canonicalCandidateDocument(omitTopLevelDigest(immutableCandidateValue(agentCandidateMaterializationReceiptSchema.parse(input.materializationReceipt.value))));
|
|
1304
|
+
if (materializationReceipt.digest !== input.materializationReceipt.digest) throw new Error("materialization receipt digest changed while sealing prepared execution");
|
|
1305
|
+
return Object.freeze({
|
|
1306
|
+
ports: input.ports,
|
|
1307
|
+
bundle: input.bundle,
|
|
1308
|
+
benchmarkSuite,
|
|
1309
|
+
benchmarkTask,
|
|
1310
|
+
executionId: input.executionId,
|
|
1311
|
+
roots: immutableCandidateValue(input.roots),
|
|
1312
|
+
profilePlan: Object.freeze({
|
|
1313
|
+
value: profilePlan,
|
|
1314
|
+
bytes: Uint8Array.from(input.profilePlan.bytes),
|
|
1315
|
+
written: Object.freeze([...input.profilePlan.written])
|
|
1316
|
+
}),
|
|
1317
|
+
profileActivation,
|
|
1318
|
+
executionPlan: Object.freeze({
|
|
1319
|
+
value: executionPlan,
|
|
1320
|
+
bytes: Uint8Array.from(input.executionPlan.bytes)
|
|
1321
|
+
}),
|
|
1322
|
+
materializationReceipt,
|
|
1323
|
+
launch: immutableCandidateValue(input.launch),
|
|
1324
|
+
instruction: Object.freeze({
|
|
1325
|
+
bytes: Uint8Array.from(input.instruction.bytes),
|
|
1326
|
+
delivery: immutableCandidateValue(input.instruction.delivery)
|
|
1327
|
+
}),
|
|
1328
|
+
resolvedModel: immutableCandidateValue(input.resolvedModel),
|
|
1329
|
+
preparationId: input.preparationId,
|
|
1330
|
+
reservationExpiresAtMs: input.reservationExpiresAtMs,
|
|
1331
|
+
cleanupTimeoutMs: input.cleanupTimeoutMs,
|
|
1332
|
+
resultTimeoutMs: input.resultTimeoutMs,
|
|
1333
|
+
modelReservation: immutableCandidateValue(input.modelReservation),
|
|
1334
|
+
executorInputs: Object.freeze({
|
|
1335
|
+
taskFiles: immutableExecutorFiles(input.executorInputs.taskFiles),
|
|
1336
|
+
...input.executorInputs.candidateFiles ? { candidateFiles: immutableExecutorFiles(input.executorInputs.candidateFiles) } : {},
|
|
1337
|
+
profileFiles: immutableExecutorFiles(input.executorInputs.profileFiles)
|
|
1338
|
+
}),
|
|
1339
|
+
...input.memoryReservation ? { memoryReservation: immutableCandidateValue(input.memoryReservation) } : {},
|
|
1340
|
+
...input.knowledge ? { knowledge: Object.freeze({
|
|
1341
|
+
candidate: immutableCandidateValue(input.knowledge.candidate),
|
|
1342
|
+
snapshot: immutableCandidateValue(input.knowledge.snapshot),
|
|
1343
|
+
files: immutableExecutorFiles(input.knowledge.files),
|
|
1344
|
+
...input.knowledge.retrievalConfig ? { retrievalConfig: Uint8Array.from(input.knowledge.retrievalConfig) } : {}
|
|
1345
|
+
}) } : {},
|
|
1346
|
+
trace: immutableCandidateValue(input.trace),
|
|
1347
|
+
memory: immutableCandidateValue(input.memory)
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
function assertPrivateCandidateIntegrity(state) {
|
|
1351
|
+
if (canonicalCandidateDigest$1(omitTopLevelDigest(state.bundle)) !== state.bundle.digest) throw new Error("prepared candidate bundle no longer matches its digest");
|
|
1352
|
+
assertPlanEvidence(state.profilePlan.value, state.profilePlan.bytes, "profile plan");
|
|
1353
|
+
parseAgentCandidateProfileActivation(state.profileActivation, state.profilePlan.value.digest);
|
|
1354
|
+
assertPlanEvidence(state.executionPlan.value, state.executionPlan.bytes, "execution plan");
|
|
1355
|
+
agentCandidateExecutionPlanEvidenceSchema.parse(state.executionPlan.value);
|
|
1356
|
+
const receipt = agentCandidateMaterializationReceiptSchema.parse(state.materializationReceipt.value);
|
|
1357
|
+
const receiptBytes = canonicalCandidateBytes(omitTopLevelDigest(receipt));
|
|
1358
|
+
if (canonicalCandidateDigest$1(omitTopLevelDigest(receipt)) !== state.materializationReceipt.digest || !Buffer.from(receiptBytes).equals(Buffer.from(state.materializationReceipt.bytes))) throw new Error("prepared materialization receipt no longer matches its canonical bytes");
|
|
1359
|
+
assertSignedBenchmarkInput(state);
|
|
1360
|
+
const instruction = Buffer.from(state.benchmarkTask.instruction, "utf8");
|
|
1361
|
+
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");
|
|
1362
|
+
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$1(state.executionPlan.value.material.limits))) throw new Error("prepared model access no longer matches the signed execution plan");
|
|
1363
|
+
assertExecutorInputs(state);
|
|
1364
|
+
assertPreparedKnowledge(state);
|
|
1365
|
+
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");
|
|
1366
|
+
if (state.memory.mode === "isolated" && state.memoryReservation && (state.memoryReservation.preparationId !== state.preparationId || state.memoryReservation.expiresAtMs !== state.reservationExpiresAtMs || state.memoryReservation.effectiveNamespace !== state.memory.effectiveNamespace)) throw new Error("prepared memory reservation identity no longer matches the execution");
|
|
1367
|
+
if (JSON.stringify(state.memory) !== JSON.stringify(state.executionPlan.value.material.memory)) throw new Error("prepared memory no longer matches the signed execution plan");
|
|
1368
|
+
const expectedTags = {
|
|
1369
|
+
[CANDIDATE_TRACE_TAGS.executionId]: state.executionId,
|
|
1370
|
+
[CANDIDATE_TRACE_TAGS.bundleDigest]: state.bundle.digest,
|
|
1371
|
+
[CANDIDATE_TRACE_TAGS.executionPlanDigest]: state.executionPlan.value.digest,
|
|
1372
|
+
[CANDIDATE_TRACE_TAGS.materializationReceiptDigest]: state.materializationReceipt.digest
|
|
1373
|
+
};
|
|
1374
|
+
const expectedTraceEnvironment = {
|
|
1375
|
+
[CANDIDATE_TRACE_ENV.executionId]: state.executionId,
|
|
1376
|
+
[CANDIDATE_TRACE_ENV.bundleDigest]: state.bundle.digest,
|
|
1377
|
+
[CANDIDATE_TRACE_ENV.executionPlanDigest]: state.executionPlan.value.digest,
|
|
1378
|
+
[CANDIDATE_TRACE_ENV.materializationReceiptDigest]: state.materializationReceipt.digest,
|
|
1379
|
+
[CANDIDATE_TRACE_ENV.traceRunId]: state.trace.runId
|
|
1380
|
+
};
|
|
1381
|
+
if (!state.trace.runId.startsWith(`${state.executionId}:attempt-${state.executionPlan.value.material.runCell.attempt}:`) || canonicalCandidateDigest$1(state.trace.tags) !== canonicalCandidateDigest$1(expectedTags) || canonicalCandidateDigest$1(state.trace.env) !== canonicalCandidateDigest$1(expectedTraceEnvironment)) throw new Error("prepared trace identity no longer matches the signed execution");
|
|
1382
|
+
}
|
|
1383
|
+
function assertPlanEvidence(evidence, bytes, label) {
|
|
1384
|
+
const expected = canonicalCandidateBytes(evidence.material);
|
|
1385
|
+
if (sha256Bytes(expected) !== evidence.digest || !Buffer.from(expected).equals(Buffer.from(bytes)) || evidence.artifact.sha256 !== evidence.digest || evidence.artifact.byteLength !== bytes.byteLength || !("content" in evidence.artifact) || !Buffer.from(evidence.artifact.content, "base64").equals(Buffer.from(bytes))) throw new Error(`prepared ${label} no longer matches its canonical bytes`);
|
|
1386
|
+
}
|
|
1387
|
+
function evidenceView(evidence) {
|
|
1388
|
+
const bytes = Uint8Array.from(evidence.bytes);
|
|
1389
|
+
return Object.freeze({
|
|
1390
|
+
...evidence,
|
|
1391
|
+
get bytes() {
|
|
1392
|
+
return Uint8Array.from(bytes);
|
|
1393
|
+
}
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
function bytesView(value, key) {
|
|
1397
|
+
const bytes = Uint8Array.from(value.bytes);
|
|
1398
|
+
return Object.freeze({
|
|
1399
|
+
...value,
|
|
1400
|
+
get [key]() {
|
|
1401
|
+
return Uint8Array.from(bytes);
|
|
1402
|
+
}
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
function knowledgeView(knowledge) {
|
|
1406
|
+
const retrievalConfig = knowledge.retrievalConfig ? Uint8Array.from(knowledge.retrievalConfig) : void 0;
|
|
1407
|
+
return Object.freeze({
|
|
1408
|
+
candidate: knowledge.candidate,
|
|
1409
|
+
snapshot: knowledge.snapshot,
|
|
1410
|
+
files: Object.freeze(knowledge.files.map((file) => profileFileView(file))),
|
|
1411
|
+
...retrievalConfig ? { get retrievalConfig() {
|
|
1412
|
+
return Uint8Array.from(retrievalConfig);
|
|
1413
|
+
} } : {}
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
function workspaceInputView(snapshot, sourceFiles) {
|
|
1417
|
+
return Object.freeze({
|
|
1418
|
+
snapshot,
|
|
1419
|
+
files: Object.freeze(sourceFiles.map((file) => executorFileView(file)))
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
function profileFileView(source) {
|
|
1423
|
+
const bytes = Uint8Array.from(source.bytes);
|
|
1424
|
+
return Object.freeze({
|
|
1425
|
+
path: source.path,
|
|
1426
|
+
mode: source.mode,
|
|
1427
|
+
get bytes() {
|
|
1428
|
+
return Uint8Array.from(bytes);
|
|
1429
|
+
}
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1432
|
+
function executorFileView(source) {
|
|
1433
|
+
const bytes = Uint8Array.from(source.bytes);
|
|
1434
|
+
return Object.freeze({
|
|
1435
|
+
path: source.path,
|
|
1436
|
+
mode: source.mode,
|
|
1437
|
+
get bytes() {
|
|
1438
|
+
return Uint8Array.from(bytes);
|
|
1439
|
+
}
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
function assertExecutorInputs(state) {
|
|
1443
|
+
const material = state.executionPlan.value.material;
|
|
1444
|
+
assertWorkspaceExecutorFiles(state.executorInputs.taskFiles, state.benchmarkTask.workspace.material);
|
|
1445
|
+
if (material.candidateWorkspace) {
|
|
1446
|
+
if (!state.executorInputs.candidateFiles) throw new Error("prepared candidate executor files are missing");
|
|
1447
|
+
assertWorkspaceExecutorFiles(state.executorInputs.candidateFiles, material.candidateWorkspace.material);
|
|
1448
|
+
} else if (state.executorInputs.candidateFiles) throw new Error("disabled candidate has executor files");
|
|
1449
|
+
const expectedFiles = state.profilePlan.value.material.files;
|
|
1450
|
+
if (state.executorInputs.profileFiles.length !== expectedFiles.length) throw new Error("prepared profile executor files do not match the signed profile plan");
|
|
1451
|
+
for (let index = 0; index < expectedFiles.length; index++) {
|
|
1452
|
+
const expected = expectedFiles[index];
|
|
1453
|
+
const actual = state.executorInputs.profileFiles[index];
|
|
1454
|
+
if (!expected || !actual || actual.path !== expected.relPath || actual.mode !== expected.mode || sha256Bytes(actual.bytes) !== expected.contentSha256) throw new Error("prepared profile executor files do not match the signed profile plan");
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
function assertPreparedKnowledge(state) {
|
|
1458
|
+
const expected = state.bundle.knowledge;
|
|
1459
|
+
const actual = state.knowledge;
|
|
1460
|
+
if (!expected || !actual) {
|
|
1461
|
+
if (expected || actual) throw new Error("prepared knowledge does not match the candidate bundle");
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
if (canonicalCandidateDigest$1(actual.candidate) !== canonicalCandidateDigest$1(expected.candidate) || canonicalCandidateDigest$1(actual.snapshot) !== canonicalCandidateDigest$1(expected.snapshot) || state.executionPlan.value.material.knowledgeManifestDigest !== expected.snapshot.digest) throw new Error("prepared knowledge identity does not match the candidate bundle");
|
|
1465
|
+
assertWorkspaceExecutorFiles(actual.files, expected.snapshot.material);
|
|
1466
|
+
if (!expected.retrievalConfig || !actual.retrievalConfig) {
|
|
1467
|
+
if (expected.retrievalConfig || actual.retrievalConfig) throw new Error("prepared knowledge retrieval config does not match the candidate bundle");
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
if (actual.retrievalConfig.byteLength !== expected.retrievalConfig.byteLength || sha256Bytes(actual.retrievalConfig) !== expected.retrievalConfig.sha256) throw new Error("prepared knowledge retrieval config does not match the candidate bundle");
|
|
1471
|
+
}
|
|
1472
|
+
function assertWorkspaceExecutorFiles(actualFiles, expected) {
|
|
1473
|
+
if (actualFiles.length !== expected.files.length) throw new Error("prepared workspace executor files do not match the signed manifest");
|
|
1474
|
+
for (let index = 0; index < expected.files.length; index++) {
|
|
1475
|
+
const actual = actualFiles[index];
|
|
1476
|
+
const planned = expected.files[index];
|
|
1477
|
+
if (!actual || !planned || actual.path !== planned.path || actual.mode !== planned.mode || actual.bytes.byteLength !== planned.byteLength || sha256Bytes(actual.bytes) !== planned.sha256) throw new Error("prepared workspace executor files do not match the signed manifest");
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
function assertSignedBenchmarkInput(state) {
|
|
1481
|
+
const cell = state.executionPlan.value.material.runCell;
|
|
1482
|
+
const suite = state.benchmarkSuite;
|
|
1483
|
+
const task = state.benchmarkTask;
|
|
1484
|
+
const receipt = state.materializationReceipt.value;
|
|
1485
|
+
const cellIndex = cell.taskIndex * suite.reps + cell.repetition;
|
|
1486
|
+
if (canonicalCandidateDigest$1(omitTopLevelDigest(suite)) !== suite.digest || canonicalCandidateDigest$1(omitTopLevelDigest(task)) !== task.digest || canonicalCandidateDigest$1(omitTopLevelDigest(cell)) !== cell.digest || cell.bundleDigest !== state.bundle.digest || cell.suiteDigest !== suite.digest || cell.taskDigest !== task.digest || suite.taskDigests[cell.taskIndex] !== task.digest || suite.seeds[cellIndex] !== cell.seed || cell.repetition >= suite.reps || cell.attempt < 1 || cell.attempt > task.attempt.maxAttempts) throw new Error("prepared candidate no longer matches its signed experiment cell");
|
|
1487
|
+
const suiteBytes = canonicalCandidateBytes(omitTopLevelDigest(suite));
|
|
1488
|
+
const taskBytes = canonicalCandidateBytes(omitTopLevelDigest(task));
|
|
1489
|
+
if (receipt.benchmark.suite.digest !== suite.digest || receipt.benchmark.suite.material.sha256 !== sha256Bytes(suiteBytes) || receipt.benchmark.suite.material.byteLength !== suiteBytes.byteLength || receipt.benchmark.task.digest !== task.digest || receipt.benchmark.task.material.sha256 !== sha256Bytes(taskBytes) || receipt.benchmark.task.material.byteLength !== taskBytes.byteLength) throw new Error("materialization receipt no longer matches its signed benchmark input");
|
|
1490
|
+
}
|
|
1491
|
+
function immutableExecutorFiles(files) {
|
|
1492
|
+
return Object.freeze(files.map((file) => Object.freeze({
|
|
1493
|
+
...file,
|
|
1494
|
+
bytes: Uint8Array.from(file.bytes)
|
|
1495
|
+
})));
|
|
1496
|
+
}
|
|
1497
|
+
function modelLimits$1(limits) {
|
|
1498
|
+
return {
|
|
1499
|
+
maxModelCalls: limits.maxModelCalls,
|
|
1500
|
+
maxInputTokens: limits.maxInputTokens,
|
|
1501
|
+
maxOutputTokens: limits.maxOutputTokens,
|
|
1502
|
+
maxCostUsd: limits.maxCostUsd
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
function transitionLifecycle(prepared, expected, next) {
|
|
1506
|
+
const lifecycle = lifecycleByExecution.get(prepared);
|
|
1507
|
+
if (!lifecycle || !expected.includes(lifecycle.status)) throw new Error(`prepared candidate execution is already ${lifecycle?.status ?? "unknown"}`);
|
|
1508
|
+
lifecycle.status = next;
|
|
1509
|
+
}
|
|
1510
|
+
function assertProtectedEnvironment(state, modelEnvironment, memoryEnvironment) {
|
|
1511
|
+
const seen = /* @__PURE__ */ new Set([...Object.keys(state.launch.env), ...Object.keys(state.trace.env)]);
|
|
1512
|
+
for (const [name, value] of [...Object.entries(modelEnvironment), ...Object.entries(memoryEnvironment ?? {})]) {
|
|
1513
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || typeof value !== "string" || value.length < 8) throw new Error("protected model activation contains an invalid environment binding");
|
|
1514
|
+
if (seen.has(name)) throw new Error(`protected model activation collides with environment binding ${name}`);
|
|
1515
|
+
seen.add(name);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
//#endregion
|
|
1519
|
+
//#region src/candidate-execution/claim-plan.ts
|
|
1520
|
+
/** Extract the complete durable claim from a prepared execution. */
|
|
1521
|
+
function candidateExecutionClaim(prepared, preparationEvidence) {
|
|
1522
|
+
const state = assertPreparedCandidateIntegrity(prepared);
|
|
1523
|
+
const material = prepared.executionPlan.value.material;
|
|
1524
|
+
const attempt = {
|
|
1525
|
+
number: material.runCell.attempt,
|
|
1526
|
+
maxAttempts: state.benchmarkTask.attempt.maxAttempts,
|
|
1527
|
+
retryPolicy: state.benchmarkTask.attempt.retryPolicy
|
|
1528
|
+
};
|
|
1529
|
+
const nowMs = Date.now();
|
|
1530
|
+
if (!Number.isSafeInteger(nowMs) || nowMs < 0) throw new Error("candidate execution claim-store clock returned an invalid timestamp");
|
|
1531
|
+
const leaseExpiresAtMs = nowMs + candidateExecutionOwnerWindowMs(material.limits.timeoutMs, state.cleanupTimeoutMs, state.resultTimeoutMs);
|
|
1532
|
+
if (!Number.isSafeInteger(leaseExpiresAtMs) || leaseExpiresAtMs <= 0) throw new Error("candidate execution leaseExpiresAtMs must be a positive safe timestamp");
|
|
1533
|
+
if (leaseExpiresAtMs > state.reservationExpiresAtMs) throw new Error("candidate preparation expires before its full execution and cleanup owner window");
|
|
1534
|
+
if (preparationEvidence.executionPlan.sha256 !== prepared.executionPlan.value.digest || preparationEvidence.executionPlan.byteLength !== state.executionPlan.bytes.byteLength) throw new Error("persisted execution plan does not match its canonical preparation bytes");
|
|
1535
|
+
if (preparationEvidence.materializationReceipt.sha256 !== state.materializationReceipt.digest || preparationEvidence.materializationReceipt.byteLength !== state.materializationReceipt.bytes.byteLength) throw new Error("persisted materialization receipt does not match its canonical preparation bytes");
|
|
1536
|
+
return candidateClaimFileInternals.sealClaim({
|
|
1537
|
+
executionId: prepared.executionId,
|
|
1538
|
+
attempt: attempt.number,
|
|
1539
|
+
maxAttempts: attempt.maxAttempts,
|
|
1540
|
+
retryPolicy: attempt.retryPolicy,
|
|
1541
|
+
bundleDigest: prepared.bundle.digest,
|
|
1542
|
+
executionPlanDigest: prepared.executionPlan.value.digest,
|
|
1543
|
+
preparationEvidence,
|
|
1544
|
+
retryLineageDigest: retryLineageDigest(prepared, state.resultTimeoutMs),
|
|
1545
|
+
leaseExpiresAtMs,
|
|
1546
|
+
resultTimeoutMs: state.resultTimeoutMs,
|
|
1547
|
+
cleanup: {
|
|
1548
|
+
preparationId: state.preparationId,
|
|
1549
|
+
modelGrantDigest: state.modelReservation.digest,
|
|
1550
|
+
resolvedModel: state.resolvedModel,
|
|
1551
|
+
traceRunId: state.trace.runId,
|
|
1552
|
+
cleanupTimeoutMs: state.cleanupTimeoutMs,
|
|
1553
|
+
...state.memoryReservation ? { memory: {
|
|
1554
|
+
accessDigest: state.memoryReservation.accessDigest,
|
|
1555
|
+
effectiveNamespace: state.memoryReservation.effectiveNamespace
|
|
1556
|
+
} } : {}
|
|
1557
|
+
}
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
function retryLineageDigest(prepared, resultTimeoutMs) {
|
|
1561
|
+
const material = prepared.executionPlan.value.material;
|
|
1562
|
+
return canonicalCandidateDigest$1({
|
|
1563
|
+
resultTimeoutMs,
|
|
1564
|
+
executionPlan: {
|
|
1565
|
+
...material,
|
|
1566
|
+
runCell: {
|
|
1567
|
+
...material.runCell,
|
|
1568
|
+
attempt: 0,
|
|
1569
|
+
digest: `sha256:${"0".repeat(64)}`
|
|
1570
|
+
},
|
|
1571
|
+
model: {
|
|
1572
|
+
...material.model,
|
|
1573
|
+
access: {
|
|
1574
|
+
...material.model.access,
|
|
1575
|
+
grantDigest: `sha256:${"0".repeat(64)}`
|
|
1576
|
+
}
|
|
1577
|
+
},
|
|
1578
|
+
memory: material.memory.mode === "disabled" ? material.memory : {
|
|
1579
|
+
mode: "isolated",
|
|
1580
|
+
scope: "task",
|
|
1581
|
+
effectiveNamespace: "candidate/retry-lineage-normalized",
|
|
1582
|
+
reset: {
|
|
1583
|
+
kind: "fresh",
|
|
1584
|
+
emptyStateDigest: material.memory.reset.emptyStateDigest
|
|
1585
|
+
},
|
|
1586
|
+
beforeState: {
|
|
1587
|
+
digest: material.memory.beforeState.digest,
|
|
1588
|
+
material: material.memory.beforeState.material,
|
|
1589
|
+
manifest: {
|
|
1590
|
+
sha256: material.memory.beforeState.manifest.sha256,
|
|
1591
|
+
byteLength: material.memory.beforeState.manifest.byteLength
|
|
1592
|
+
},
|
|
1593
|
+
archive: {
|
|
1594
|
+
sha256: material.memory.beforeState.archive.sha256,
|
|
1595
|
+
byteLength: material.memory.beforeState.archive.byteLength
|
|
1596
|
+
}
|
|
1597
|
+
},
|
|
1598
|
+
...material.memory.seedDigest ? { seedDigest: material.memory.seedDigest } : {}
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1603
|
+
//#endregion
|
|
1604
|
+
//#region src/candidate-execution/claim.ts
|
|
1605
|
+
/** Durable one-shot lifecycle for candidate execution attempts. */
|
|
1606
|
+
function attemptRecord(claim, phase, staged, terminal) {
|
|
1607
|
+
return Object.freeze({
|
|
1608
|
+
claim,
|
|
1609
|
+
phase,
|
|
1610
|
+
...staged ? { staged } : {},
|
|
1611
|
+
...terminal ? { terminal } : {}
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
/** Single-process lifecycle implementation. */
|
|
1615
|
+
var InMemoryAgentCandidateExecutionClaimStore = class {
|
|
1616
|
+
claims = /* @__PURE__ */ new Map();
|
|
1617
|
+
now;
|
|
1618
|
+
constructor(options = {}) {
|
|
1619
|
+
this.now = options.now ?? Date.now;
|
|
1620
|
+
}
|
|
1621
|
+
async tryClaim(requested) {
|
|
1622
|
+
const claim = sealClaim(requested);
|
|
1623
|
+
const slot = claimSlot(claim);
|
|
1624
|
+
const existing = this.claims.get(slot);
|
|
1625
|
+
if (existing) return rejectedExistingClaim(existing.claim, claim);
|
|
1626
|
+
assertUnexpiredLease(claim.leaseExpiresAtMs, this.now());
|
|
1627
|
+
const retryRejection = retryRejectionFromMemory(this.claims, claim);
|
|
1628
|
+
if (retryRejection) return rejectedRetry(claim, retryRejection);
|
|
1629
|
+
const lease = newLease(claim);
|
|
1630
|
+
this.claims.set(slot, {
|
|
1631
|
+
claim,
|
|
1632
|
+
leaseDigest: leaseDigest(lease),
|
|
1633
|
+
phase: "claimed"
|
|
1634
|
+
});
|
|
1635
|
+
return Object.freeze({
|
|
1636
|
+
acquired: true,
|
|
1637
|
+
claim,
|
|
1638
|
+
lease
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1641
|
+
async getAttempt(requestedAttempt) {
|
|
1642
|
+
const attempt = sealAttemptRef(requestedAttempt);
|
|
1643
|
+
const stored = this.claims.get(claimSlot(attempt));
|
|
1644
|
+
return stored ? attemptRecord(stored.claim, stored.phase, stored.staged, stored.terminal) : void 0;
|
|
1645
|
+
}
|
|
1646
|
+
async markCandidateMayRun(requestedLease) {
|
|
1647
|
+
const lease = sealLease(requestedLease);
|
|
1648
|
+
const stored = this.requireClaim(lease);
|
|
1649
|
+
assertLease(stored.leaseDigest, stored.claim.leaseExpiresAtMs, lease);
|
|
1650
|
+
if (stored.phase === "candidate-may-run") return Object.freeze({
|
|
1651
|
+
marked: false,
|
|
1652
|
+
phase: "candidate-may-run"
|
|
1653
|
+
});
|
|
1654
|
+
if (stored.staged || stored.terminal) throw new Error("candidate execution terminal was staged before candidate-may-run phase");
|
|
1655
|
+
assertUnexpiredLease(stored.claim.leaseExpiresAtMs, this.now());
|
|
1656
|
+
stored.phase = "candidate-may-run";
|
|
1657
|
+
return Object.freeze({
|
|
1658
|
+
marked: true,
|
|
1659
|
+
phase: "candidate-may-run"
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
async stageTerminal(requestedLease, result) {
|
|
1663
|
+
const lease = sealLease(requestedLease);
|
|
1664
|
+
const stored = this.requireClaim(lease);
|
|
1665
|
+
assertLease(stored.leaseDigest, stored.claim.leaseExpiresAtMs, lease);
|
|
1666
|
+
const terminal = terminalRecord(stored.claim, result);
|
|
1667
|
+
if (stored.staged) return rejectedStage(stored.staged, terminal);
|
|
1668
|
+
assertTerminalAllowedInPhase(stored.phase, terminal);
|
|
1669
|
+
assertUnexpiredLease(stored.claim.leaseExpiresAtMs, this.now());
|
|
1670
|
+
stored.staged = terminal;
|
|
1671
|
+
return Object.freeze({
|
|
1672
|
+
staged: true,
|
|
1673
|
+
terminal
|
|
1674
|
+
});
|
|
1675
|
+
}
|
|
1676
|
+
async finish(requestedLease, requestedTerminalDigest) {
|
|
1677
|
+
const lease = sealLease(requestedLease);
|
|
1678
|
+
const terminalDigest = sealTerminalDigest(requestedTerminalDigest);
|
|
1679
|
+
const stored = this.requireClaim(lease);
|
|
1680
|
+
assertLease(stored.leaseDigest, stored.claim.leaseExpiresAtMs, lease);
|
|
1681
|
+
const staged = requireStagedTerminal(stored.staged, terminalDigest);
|
|
1682
|
+
if (stored.terminal) return rejectedFinish(stored.terminal, terminalDigest);
|
|
1683
|
+
assertUnexpiredLease(stored.claim.leaseExpiresAtMs, this.now());
|
|
1684
|
+
stored.terminal = staged;
|
|
1685
|
+
return Object.freeze({
|
|
1686
|
+
finished: true,
|
|
1687
|
+
terminal: staged
|
|
1688
|
+
});
|
|
1689
|
+
}
|
|
1690
|
+
async recoverExpired(requestedAttempt, evidence) {
|
|
1691
|
+
const attempt = sealAttemptRef(requestedAttempt);
|
|
1692
|
+
const stored = this.requireClaim(attempt, "candidate execution recovery");
|
|
1693
|
+
const recovered = recoveredTerminalRecord(stored.claim, stored.phase, evidence);
|
|
1694
|
+
if (stored.staged) assertRecoveryMatchesStaged(stored.staged, recovered);
|
|
1695
|
+
const requestedDigest = stored.staged?.terminalDigest ?? recovered.terminalDigest;
|
|
1696
|
+
if (stored.terminal) return rejectedFinish(stored.terminal, requestedDigest);
|
|
1697
|
+
assertExpiredLease(stored.claim.leaseExpiresAtMs, this.now());
|
|
1698
|
+
const terminal = stored.staged ?? recovered;
|
|
1699
|
+
assertRecoveryMatchesStaged(terminal, recovered);
|
|
1700
|
+
stored.staged ??= terminal;
|
|
1701
|
+
stored.terminal = terminal;
|
|
1702
|
+
return Object.freeze({
|
|
1703
|
+
finished: true,
|
|
1704
|
+
terminal
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1707
|
+
requireClaim(attempt, operation = "candidate execution lease") {
|
|
1708
|
+
const stored = this.claims.get(claimSlot(attempt));
|
|
1709
|
+
if (!stored) throw new Error(`${operation} does not name an acquired attempt`);
|
|
1710
|
+
return stored;
|
|
1711
|
+
}
|
|
1712
|
+
};
|
|
1713
|
+
const SHA256_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
1714
|
+
const LEASE_TOKEN_PATTERN = /^candidate-execution-lease\.[A-Za-z0-9_-]{43}$/;
|
|
1715
|
+
const PREPARATION_ID_PATTERN = /^candidate-preparation\.[A-Za-z0-9_-]{43}$/;
|
|
1716
|
+
function sealClaim(claim) {
|
|
1717
|
+
assertExactObjectKeys(claim, [
|
|
1718
|
+
"executionId",
|
|
1719
|
+
"attempt",
|
|
1720
|
+
"maxAttempts",
|
|
1721
|
+
"retryPolicy",
|
|
1722
|
+
"bundleDigest",
|
|
1723
|
+
"executionPlanDigest",
|
|
1724
|
+
"preparationEvidence",
|
|
1725
|
+
"retryLineageDigest",
|
|
1726
|
+
"leaseExpiresAtMs",
|
|
1727
|
+
"resultTimeoutMs",
|
|
1728
|
+
"cleanup"
|
|
1729
|
+
], "candidate execution claim");
|
|
1730
|
+
assertExecutionId(claim.executionId);
|
|
1731
|
+
if (!Number.isSafeInteger(claim.attempt) || claim.attempt < 1) throw new Error("candidate execution claim attempt must be a positive safe integer");
|
|
1732
|
+
if (!Number.isSafeInteger(claim.maxAttempts) || claim.maxAttempts < 1) throw new Error("candidate execution claim maxAttempts must be a positive safe integer");
|
|
1733
|
+
if (claim.attempt > claim.maxAttempts) throw new Error("candidate execution claim attempt exceeds maxAttempts");
|
|
1734
|
+
if (!["none", "pre-model-infrastructure-only"].includes(claim.retryPolicy)) throw new Error("candidate execution claim retryPolicy is invalid");
|
|
1735
|
+
if (claim.retryPolicy === "none" && claim.maxAttempts !== 1) throw new Error("candidate execution claim retryPolicy none requires maxAttempts 1");
|
|
1736
|
+
assertSha256Digest(claim.bundleDigest, "bundleDigest");
|
|
1737
|
+
assertSha256Digest(claim.executionPlanDigest, "executionPlanDigest");
|
|
1738
|
+
const preparationEvidence = sealCandidatePreparationEvidence(claim.preparationEvidence, claim.executionPlanDigest);
|
|
1739
|
+
assertSha256Digest(claim.retryLineageDigest, "retryLineageDigest");
|
|
1740
|
+
assertPositiveTimestamp(claim.leaseExpiresAtMs, "leaseExpiresAtMs");
|
|
1741
|
+
candidateResultTimeout(claim.resultTimeoutMs, claim.resultTimeoutMs);
|
|
1742
|
+
const cleanup = sealCleanupHandles(claim.cleanup);
|
|
1743
|
+
return Object.freeze({
|
|
1744
|
+
executionId: claim.executionId,
|
|
1745
|
+
attempt: claim.attempt,
|
|
1746
|
+
maxAttempts: claim.maxAttempts,
|
|
1747
|
+
retryPolicy: claim.retryPolicy,
|
|
1748
|
+
bundleDigest: claim.bundleDigest,
|
|
1749
|
+
executionPlanDigest: claim.executionPlanDigest,
|
|
1750
|
+
preparationEvidence,
|
|
1751
|
+
retryLineageDigest: claim.retryLineageDigest,
|
|
1752
|
+
leaseExpiresAtMs: claim.leaseExpiresAtMs,
|
|
1753
|
+
resultTimeoutMs: claim.resultTimeoutMs,
|
|
1754
|
+
cleanup
|
|
1755
|
+
});
|
|
1756
|
+
}
|
|
1757
|
+
function sealCleanupHandles(cleanup) {
|
|
1758
|
+
assertExactObjectKeys(cleanup, cleanup.memory ? [
|
|
1759
|
+
"preparationId",
|
|
1760
|
+
"modelGrantDigest",
|
|
1761
|
+
"resolvedModel",
|
|
1762
|
+
"traceRunId",
|
|
1763
|
+
"cleanupTimeoutMs",
|
|
1764
|
+
"memory"
|
|
1765
|
+
] : [
|
|
1766
|
+
"preparationId",
|
|
1767
|
+
"modelGrantDigest",
|
|
1768
|
+
"resolvedModel",
|
|
1769
|
+
"traceRunId",
|
|
1770
|
+
"cleanupTimeoutMs"
|
|
1771
|
+
], "candidate execution cleanup handles");
|
|
1772
|
+
if (!PREPARATION_ID_PATTERN.test(cleanup.preparationId)) throw new Error("candidate execution cleanup preparationId is invalid");
|
|
1773
|
+
assertSha256Digest(cleanup.modelGrantDigest, "cleanup modelGrantDigest");
|
|
1774
|
+
const resolvedModel = immutableCandidateValue(agentCandidateResolvedModelSchema.parse(cleanup.resolvedModel));
|
|
1775
|
+
assertBoundedIdentifier(cleanup.traceRunId, "cleanup traceRunId", 512);
|
|
1776
|
+
const cleanupTimeoutMs = candidateCleanupTimeout(cleanup.cleanupTimeoutMs);
|
|
1777
|
+
const memory = cleanup.memory ? sealMemoryCleanupHandle(cleanup.memory) : void 0;
|
|
1778
|
+
return Object.freeze({
|
|
1779
|
+
preparationId: cleanup.preparationId,
|
|
1780
|
+
modelGrantDigest: cleanup.modelGrantDigest,
|
|
1781
|
+
resolvedModel,
|
|
1782
|
+
traceRunId: cleanup.traceRunId,
|
|
1783
|
+
cleanupTimeoutMs,
|
|
1784
|
+
...memory ? { memory } : {}
|
|
1785
|
+
});
|
|
1786
|
+
}
|
|
1787
|
+
function sealMemoryCleanupHandle(memory) {
|
|
1788
|
+
assertExactObjectKeys(memory, ["accessDigest", "effectiveNamespace"], "candidate execution memory cleanup handle");
|
|
1789
|
+
assertSha256Digest(memory.accessDigest, "memory accessDigest");
|
|
1790
|
+
assertBoundedIdentifier(memory.effectiveNamespace, "memory effectiveNamespace", 1024);
|
|
1791
|
+
return Object.freeze({
|
|
1792
|
+
accessDigest: memory.accessDigest,
|
|
1793
|
+
effectiveNamespace: memory.effectiveNamespace
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
function sealAttemptRef(attempt) {
|
|
1797
|
+
assertExactObjectKeys(attempt, ["executionId", "attempt"], "candidate execution attempt reference");
|
|
1798
|
+
assertExecutionId(attempt.executionId);
|
|
1799
|
+
if (!Number.isSafeInteger(attempt.attempt) || attempt.attempt < 1) throw new Error("candidate execution attempt reference must have a positive safe attempt");
|
|
1800
|
+
return Object.freeze({
|
|
1801
|
+
executionId: attempt.executionId,
|
|
1802
|
+
attempt: attempt.attempt
|
|
1803
|
+
});
|
|
1804
|
+
}
|
|
1805
|
+
function sealLease(lease) {
|
|
1806
|
+
assertExactObjectKeys(lease, [
|
|
1807
|
+
"executionId",
|
|
1808
|
+
"attempt",
|
|
1809
|
+
"token",
|
|
1810
|
+
"expiresAtMs"
|
|
1811
|
+
], "candidate execution lease");
|
|
1812
|
+
if (lease.executionId.length === 0 || !Number.isSafeInteger(lease.attempt) || lease.attempt < 1) throw new Error("candidate execution lease identity is invalid");
|
|
1813
|
+
if (!LEASE_TOKEN_PATTERN.test(lease.token)) throw new Error("candidate execution lease token is invalid");
|
|
1814
|
+
assertPositiveTimestamp(lease.expiresAtMs, "lease expiresAtMs");
|
|
1815
|
+
return Object.freeze({
|
|
1816
|
+
executionId: lease.executionId,
|
|
1817
|
+
attempt: lease.attempt,
|
|
1818
|
+
token: lease.token,
|
|
1819
|
+
expiresAtMs: lease.expiresAtMs
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
function newLease(claim) {
|
|
1823
|
+
return Object.freeze({
|
|
1824
|
+
executionId: claim.executionId,
|
|
1825
|
+
attempt: claim.attempt,
|
|
1826
|
+
token: `candidate-execution-lease.${randomBytes(32).toString("base64url")}`,
|
|
1827
|
+
expiresAtMs: claim.leaseExpiresAtMs
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
function leaseDigest(lease) {
|
|
1831
|
+
return sha256(lease.token);
|
|
1832
|
+
}
|
|
1833
|
+
function assertLease(expectedDigest, expectedExpiresAtMs, lease) {
|
|
1834
|
+
const expected = Buffer.from(expectedDigest);
|
|
1835
|
+
const actual = Buffer.from(leaseDigest(lease));
|
|
1836
|
+
if (expected.length !== actual.length || !timingSafeEqual(expected, actual) || lease.expiresAtMs !== expectedExpiresAtMs) throw new Error("candidate execution lease is invalid");
|
|
1837
|
+
}
|
|
1838
|
+
function claimSlot(claim) {
|
|
1839
|
+
return createHash("sha256").update(JSON.stringify([claim.executionId, claim.attempt]), "utf8").digest("hex");
|
|
1840
|
+
}
|
|
1841
|
+
function retryRejectionFromMemory(claims, claim) {
|
|
1842
|
+
if (claim.attempt === 1) return void 0;
|
|
1843
|
+
return retryRejection(claim, claims.get(claimSlot({
|
|
1844
|
+
executionId: claim.executionId,
|
|
1845
|
+
attempt: claim.attempt - 1
|
|
1846
|
+
})));
|
|
1847
|
+
}
|
|
1848
|
+
function retryRejection(claim, prior) {
|
|
1849
|
+
if (!prior) return "prior-attempt-missing";
|
|
1850
|
+
if (claim.retryPolicy !== "pre-model-infrastructure-only" || prior.claim.retryPolicy !== claim.retryPolicy || prior.claim.maxAttempts !== claim.maxAttempts || prior.claim.bundleDigest !== claim.bundleDigest || prior.claim.retryLineageDigest !== claim.retryLineageDigest) return "retry-lineage-mismatch";
|
|
1851
|
+
if (!prior.terminal) return "prior-attempt-running";
|
|
1852
|
+
if (prior.terminal.status === "succeeded") return "prior-attempt-succeeded";
|
|
1853
|
+
if (prior.terminal.usage.modelCalls !== 0) return "prior-attempt-spent-model-calls";
|
|
1854
|
+
if (prior.terminal.failureClass !== "pre-model-infrastructure") return "prior-attempt-not-pre-model-infrastructure";
|
|
1855
|
+
}
|
|
1856
|
+
function rejectedExistingClaim(existing, requested) {
|
|
1857
|
+
return Object.freeze({
|
|
1858
|
+
acquired: false,
|
|
1859
|
+
reason: "already-claimed",
|
|
1860
|
+
claim: existing,
|
|
1861
|
+
exactReplay: canonicalCandidateDigest$1(existing) === canonicalCandidateDigest$1(requested)
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
function rejectedRetry(claim, detail) {
|
|
1865
|
+
return Object.freeze({
|
|
1866
|
+
acquired: false,
|
|
1867
|
+
reason: "retry-not-eligible",
|
|
1868
|
+
claim,
|
|
1869
|
+
detail
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1872
|
+
async function readClaim(path) {
|
|
1873
|
+
const parsed = await readJsonObject(path, "claim");
|
|
1874
|
+
const record = parsed;
|
|
1875
|
+
assertExactObjectKeys(parsed, [
|
|
1876
|
+
"executionId",
|
|
1877
|
+
"attempt",
|
|
1878
|
+
"maxAttempts",
|
|
1879
|
+
"retryPolicy",
|
|
1880
|
+
"bundleDigest",
|
|
1881
|
+
"executionPlanDigest",
|
|
1882
|
+
"preparationEvidence",
|
|
1883
|
+
"retryLineageDigest",
|
|
1884
|
+
"leaseExpiresAtMs",
|
|
1885
|
+
"resultTimeoutMs",
|
|
1886
|
+
"cleanup",
|
|
1887
|
+
"phase",
|
|
1888
|
+
"leaseDigest"
|
|
1889
|
+
], `candidate execution claim at ${path}`);
|
|
1890
|
+
const claim = sealClaim({
|
|
1891
|
+
executionId: requireString(record.executionId, path, "executionId"),
|
|
1892
|
+
attempt: requireNumber(record.attempt, path, "attempt"),
|
|
1893
|
+
maxAttempts: requireNumber(record.maxAttempts, path, "maxAttempts"),
|
|
1894
|
+
retryPolicy: requireRetryPolicy(record.retryPolicy, path),
|
|
1895
|
+
bundleDigest: requireString(record.bundleDigest, path, "bundleDigest"),
|
|
1896
|
+
executionPlanDigest: requireString(record.executionPlanDigest, path, "executionPlanDigest"),
|
|
1897
|
+
preparationEvidence: requireObject(record.preparationEvidence, path, "preparationEvidence"),
|
|
1898
|
+
retryLineageDigest: requireString(record.retryLineageDigest, path, "retryLineageDigest"),
|
|
1899
|
+
leaseExpiresAtMs: requireNumber(record.leaseExpiresAtMs, path, "leaseExpiresAtMs"),
|
|
1900
|
+
resultTimeoutMs: requireNumber(record.resultTimeoutMs, path, "resultTimeoutMs"),
|
|
1901
|
+
cleanup: requireObject(record.cleanup, path, "cleanup")
|
|
1902
|
+
});
|
|
1903
|
+
const persistedLeaseDigest = requireString(record.leaseDigest, path, "leaseDigest");
|
|
1904
|
+
assertSha256Digest(persistedLeaseDigest, "leaseDigest");
|
|
1905
|
+
if (record.phase !== "claimed") throw new Error(`candidate execution claim at ${path} has invalid initial phase`);
|
|
1906
|
+
return {
|
|
1907
|
+
claim,
|
|
1908
|
+
leaseDigest: persistedLeaseDigest,
|
|
1909
|
+
phase: "claimed"
|
|
1910
|
+
};
|
|
1911
|
+
}
|
|
1912
|
+
async function readClaimIfPresent(path) {
|
|
1913
|
+
try {
|
|
1914
|
+
return await readClaim(path);
|
|
1915
|
+
} catch (error) {
|
|
1916
|
+
if (isMissingError(error)) return void 0;
|
|
1917
|
+
throw error;
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
async function readTerminal(path) {
|
|
1921
|
+
const parsed = await readJsonObject(path, "terminal record");
|
|
1922
|
+
const record = parsed;
|
|
1923
|
+
assertExactObjectKeys(parsed, ["terminal"], `candidate execution terminal record at ${path}`);
|
|
1924
|
+
return sealTerminalRecordValue(requireObject(record.terminal, path, "terminal"), `candidate execution terminal record at ${path}`);
|
|
1925
|
+
}
|
|
1926
|
+
async function readTerminalIfPresent(path) {
|
|
1927
|
+
try {
|
|
1928
|
+
return await readTerminal(path);
|
|
1929
|
+
} catch (error) {
|
|
1930
|
+
if (isMissingError(error)) return void 0;
|
|
1931
|
+
throw error;
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
async function readTransitionIfPresent(path, claim) {
|
|
1935
|
+
let parsed;
|
|
1936
|
+
try {
|
|
1937
|
+
parsed = await readJsonObject(path, "transition record");
|
|
1938
|
+
} catch (error) {
|
|
1939
|
+
if (isMissingError(error)) return void 0;
|
|
1940
|
+
throw error;
|
|
1941
|
+
}
|
|
1942
|
+
if (parsed.kind === "candidate-execution-phase") {
|
|
1943
|
+
const record = parsed;
|
|
1944
|
+
assertExactObjectKeys(parsed, [
|
|
1945
|
+
"kind",
|
|
1946
|
+
"executionId",
|
|
1947
|
+
"attempt",
|
|
1948
|
+
"executionPlanDigest",
|
|
1949
|
+
"phase"
|
|
1950
|
+
], `candidate execution phase record at ${path}`);
|
|
1951
|
+
if (record.executionId !== claim.executionId || record.attempt !== claim.attempt || record.executionPlanDigest !== claim.executionPlanDigest || record.phase !== "candidate-may-run") throw new Error(`candidate execution phase record at ${path} does not match its claim`);
|
|
1952
|
+
return { kind: "phase" };
|
|
1953
|
+
}
|
|
1954
|
+
if (parsed.kind === "candidate-execution-pending-terminal") {
|
|
1955
|
+
const record = parsed;
|
|
1956
|
+
assertExactObjectKeys(parsed, ["kind", "terminal"], `candidate execution pending record at ${path}`);
|
|
1957
|
+
const terminal = sealTerminalRecordValue(requireObject(record.terminal, path, "terminal"), `candidate execution pending record at ${path}`);
|
|
1958
|
+
assertTerminalMatchesClaim(terminal, claim, path);
|
|
1959
|
+
return {
|
|
1960
|
+
kind: "pending",
|
|
1961
|
+
terminal
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
throw new Error(`candidate execution transition record at ${path} has invalid kind`);
|
|
1965
|
+
}
|
|
1966
|
+
async function readJsonObject(path, kind) {
|
|
1967
|
+
let parsed;
|
|
1968
|
+
try {
|
|
1969
|
+
parsed = JSON.parse(await readFile(path, "utf8"));
|
|
1970
|
+
} catch (error) {
|
|
1971
|
+
if (isMissingError(error)) throw error;
|
|
1972
|
+
throw new Error(`candidate execution ${kind} at ${path} is unreadable`, { cause: error });
|
|
1973
|
+
}
|
|
1974
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`candidate execution ${kind} at ${path} is not an object`);
|
|
1975
|
+
return parsed;
|
|
1976
|
+
}
|
|
1977
|
+
function assertSameSlot(existing, requested, path) {
|
|
1978
|
+
if (existing.executionId !== requested.executionId || existing.attempt !== requested.attempt) throw new Error(`candidate execution record at ${path} does not match its claim slot`);
|
|
1979
|
+
}
|
|
1980
|
+
function assertSha256Digest(value, field) {
|
|
1981
|
+
if (!SHA256_PATTERN.test(value)) throw new Error(`candidate execution claim ${field} must be a lowercase sha256 digest`);
|
|
1982
|
+
}
|
|
1983
|
+
function requireString(value, path, field) {
|
|
1984
|
+
if (typeof value !== "string") throw new Error(`candidate execution record at ${path} has invalid ${field}`);
|
|
1985
|
+
return value;
|
|
1986
|
+
}
|
|
1987
|
+
function requireNumber(value, path, field) {
|
|
1988
|
+
if (typeof value !== "number") throw new Error(`candidate execution record at ${path} has invalid ${field}`);
|
|
1989
|
+
return value;
|
|
1990
|
+
}
|
|
1991
|
+
function requireObject(value, path, field) {
|
|
1992
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error(`candidate execution record at ${path} has invalid ${field}`);
|
|
1993
|
+
return value;
|
|
1994
|
+
}
|
|
1995
|
+
function requireRetryPolicy(value, path) {
|
|
1996
|
+
if (value !== "none" && value !== "pre-model-infrastructure-only") throw new Error(`candidate execution record at ${path} has invalid retryPolicy`);
|
|
1997
|
+
return value;
|
|
1998
|
+
}
|
|
1999
|
+
function assertExecutionId(value) {
|
|
2000
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9._:-]{1,200}$/.test(value)) throw new Error("candidate execution claim executionId is invalid");
|
|
2001
|
+
}
|
|
2002
|
+
function assertBoundedIdentifier(value, label, maxLength) {
|
|
2003
|
+
if (typeof value !== "string" || value.length === 0 || value.length > maxLength || hasControlCharacter(value)) throw new Error(`candidate execution ${label} is invalid`);
|
|
2004
|
+
}
|
|
2005
|
+
function hasControlCharacter(value) {
|
|
2006
|
+
for (let index = 0; index < value.length; index++) {
|
|
2007
|
+
const code = value.charCodeAt(index);
|
|
2008
|
+
if (code < 32 || code === 127) return true;
|
|
2009
|
+
}
|
|
2010
|
+
return false;
|
|
2011
|
+
}
|
|
2012
|
+
function assertPositiveTimestamp(value, label) {
|
|
2013
|
+
if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`candidate execution ${label} must be a positive safe timestamp`);
|
|
2014
|
+
}
|
|
2015
|
+
function assertClock(value) {
|
|
2016
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new Error("candidate execution claim-store clock returned an invalid timestamp");
|
|
2017
|
+
}
|
|
2018
|
+
function assertUnexpiredLease(expiresAtMs, nowMs) {
|
|
2019
|
+
assertClock(nowMs);
|
|
2020
|
+
if (nowMs >= expiresAtMs) throw new Error("candidate execution lease has expired");
|
|
2021
|
+
}
|
|
2022
|
+
function assertExpiredLease(expiresAtMs, nowMs) {
|
|
2023
|
+
assertClock(nowMs);
|
|
2024
|
+
if (nowMs < expiresAtMs) throw new Error("candidate execution lease has not expired");
|
|
2025
|
+
}
|
|
2026
|
+
function sha256(value) {
|
|
2027
|
+
return `sha256:${createHash("sha256").update(value, "utf8").digest("hex")}`;
|
|
2028
|
+
}
|
|
2029
|
+
function isMissingError(error) {
|
|
2030
|
+
return isNodeError(error, "ENOENT");
|
|
2031
|
+
}
|
|
2032
|
+
function isNodeError(error, code) {
|
|
2033
|
+
return error !== null && typeof error === "object" && "code" in error && error.code === code;
|
|
2034
|
+
}
|
|
2035
|
+
const candidateClaimFileInternals = Object.freeze({
|
|
2036
|
+
assertExpiredLease,
|
|
2037
|
+
assertLease,
|
|
2038
|
+
assertSameSlot,
|
|
2039
|
+
assertUnexpiredLease,
|
|
2040
|
+
attemptRecord,
|
|
2041
|
+
claimSlot,
|
|
2042
|
+
leaseDigest,
|
|
2043
|
+
newLease,
|
|
2044
|
+
readClaim,
|
|
2045
|
+
readClaimIfPresent,
|
|
2046
|
+
readTerminal,
|
|
2047
|
+
readTerminalIfPresent,
|
|
2048
|
+
readTransitionIfPresent,
|
|
2049
|
+
rejectedExistingClaim,
|
|
2050
|
+
rejectedRetry,
|
|
2051
|
+
retryRejection,
|
|
2052
|
+
sealAttemptRef,
|
|
2053
|
+
sealClaim,
|
|
2054
|
+
sealLease
|
|
2055
|
+
});
|
|
2056
|
+
//#endregion
|
|
2057
|
+
//#region src/candidate-execution/model-settlement.ts
|
|
2058
|
+
const USD_NANOS = 1e9;
|
|
2059
|
+
/** Validate and detach the evaluator gateway's terminal, revoked call ledger. */
|
|
2060
|
+
function sealAgentCandidateModelSettlement(settlement, expected) {
|
|
2061
|
+
assertExactObjectKeys(settlement, [
|
|
2062
|
+
"preparationId",
|
|
2063
|
+
"grantDigest",
|
|
2064
|
+
"closed",
|
|
2065
|
+
"calls"
|
|
2066
|
+
], "model settlement");
|
|
2067
|
+
if (settlement.closed !== true) throw new Error("protected model grant is not closed");
|
|
2068
|
+
if (settlement.grantDigest !== expected.grantDigest) throw new Error("protected model settlement grant digest does not match the reservation");
|
|
2069
|
+
if (settlement.preparationId !== expected.preparationId) throw new Error("protected model settlement preparation does not match the reservation");
|
|
2070
|
+
if (!Array.isArray(settlement.calls)) throw new Error("protected model settlement calls must be an array");
|
|
2071
|
+
const callIds = /* @__PURE__ */ new Set();
|
|
2072
|
+
const spanIds = /* @__PURE__ */ new Set();
|
|
2073
|
+
let inputTokens = 0;
|
|
2074
|
+
let outputTokens = 0;
|
|
2075
|
+
let cachedInputTokens = 0;
|
|
2076
|
+
let reasoningTokens = 0;
|
|
2077
|
+
let costUsdNanos = 0;
|
|
2078
|
+
const calls = settlement.calls.map((source, index) => {
|
|
2079
|
+
assertExactObjectKeys(source, [
|
|
2080
|
+
"callId",
|
|
2081
|
+
"generationId",
|
|
2082
|
+
"traceSpanId",
|
|
2083
|
+
"status",
|
|
2084
|
+
"model",
|
|
2085
|
+
"startedAtMs",
|
|
2086
|
+
"endedAtMs",
|
|
2087
|
+
"inputTokens",
|
|
2088
|
+
"outputTokens",
|
|
2089
|
+
"cachedInputTokens",
|
|
2090
|
+
"reasoningTokens",
|
|
2091
|
+
"costUsdNanos"
|
|
2092
|
+
], `model settlement call ${index}`);
|
|
2093
|
+
assertIdentifier(source.callId, `model settlement call ${index} callId`);
|
|
2094
|
+
assertIdentifier(source.generationId, `model settlement call ${index} generationId`);
|
|
2095
|
+
assertIdentifier(source.traceSpanId, `model settlement call ${index} traceSpanId`);
|
|
2096
|
+
if (source.traceSpanId !== source.generationId) throw new Error(`model settlement call ${index} traceSpanId is not its router generationId`);
|
|
2097
|
+
if (source.status !== "succeeded" && source.status !== "failed") throw new Error(`model settlement call ${index} has an invalid status`);
|
|
2098
|
+
if (callIds.has(source.callId)) throw new Error("protected model settlement has duplicate call ids");
|
|
2099
|
+
if (spanIds.has(source.traceSpanId)) throw new Error("protected model settlement has duplicate trace span ids");
|
|
2100
|
+
callIds.add(source.callId);
|
|
2101
|
+
spanIds.add(source.traceSpanId);
|
|
2102
|
+
if (source.model !== expected.model) throw new Error(`protected model settlement call ${index} has an unexpected model`);
|
|
2103
|
+
assertTimestamp(source.startedAtMs, `model settlement call ${index} startedAtMs`);
|
|
2104
|
+
assertTimestamp(source.endedAtMs, `model settlement call ${index} endedAtMs`);
|
|
2105
|
+
if (source.endedAtMs < source.startedAtMs) throw new Error(`model settlement call ${index} ended before it started`);
|
|
2106
|
+
assertCount(source.inputTokens, `model settlement call ${index} inputTokens`);
|
|
2107
|
+
assertCount(source.outputTokens, `model settlement call ${index} outputTokens`);
|
|
2108
|
+
assertCount(source.cachedInputTokens, `model settlement call ${index} cachedInputTokens`);
|
|
2109
|
+
cachedInputTokens = safeAdd(cachedInputTokens, source.cachedInputTokens, "cached input token total");
|
|
2110
|
+
assertCount(source.reasoningTokens, `model settlement call ${index} reasoningTokens`);
|
|
2111
|
+
reasoningTokens = safeAdd(reasoningTokens, source.reasoningTokens, "reasoning token total");
|
|
2112
|
+
assertCount(source.costUsdNanos, `model settlement call ${index} costUsdNanos`);
|
|
2113
|
+
inputTokens = safeAdd(inputTokens, source.inputTokens, "input token total");
|
|
2114
|
+
outputTokens = safeAdd(outputTokens, source.outputTokens, "output token total");
|
|
2115
|
+
costUsdNanos = safeAdd(costUsdNanos, source.costUsdNanos, "cost total");
|
|
2116
|
+
return Object.freeze({ ...source });
|
|
2117
|
+
});
|
|
2118
|
+
const usage = Object.freeze({
|
|
2119
|
+
costUsdNanos,
|
|
2120
|
+
inputTokens,
|
|
2121
|
+
outputTokens,
|
|
2122
|
+
cachedInputTokens,
|
|
2123
|
+
reasoningTokens,
|
|
2124
|
+
modelCalls: calls.length
|
|
2125
|
+
});
|
|
2126
|
+
return Object.freeze({
|
|
2127
|
+
value: Object.freeze({
|
|
2128
|
+
preparationId: settlement.preparationId,
|
|
2129
|
+
grantDigest: settlement.grantDigest,
|
|
2130
|
+
closed: true,
|
|
2131
|
+
calls: Object.freeze(calls)
|
|
2132
|
+
}),
|
|
2133
|
+
usage
|
|
2134
|
+
});
|
|
2135
|
+
}
|
|
2136
|
+
/**
|
|
2137
|
+
* Append the only accepted LLM spans from the router's closed ledger.
|
|
2138
|
+
* Candidate and executor code may write tool/process spans, but never model usage.
|
|
2139
|
+
*/
|
|
2140
|
+
async function appendAuthoritativeModelSettlementSpans(traceStore, runId, settlement) {
|
|
2141
|
+
const run = await traceStore.getRun(runId);
|
|
2142
|
+
if (!run) throw new Error(`protected trace run is missing before model settlement: ${runId}`);
|
|
2143
|
+
if (run.status === "running" || run.endedAt === void 0) throw new Error("protected trace run must be terminal before model spans are appended");
|
|
2144
|
+
const existing = await traceStore.spans({ runId });
|
|
2145
|
+
if (existing.some(isLlmSpan)) throw new Error("protected trace contains a model span not authored from the closed router ledger");
|
|
2146
|
+
const occupiedIds = new Set(existing.map((span) => span.spanId));
|
|
2147
|
+
for (const call of settlement.value.calls) {
|
|
2148
|
+
if (occupiedIds.has(call.traceSpanId)) throw new Error(`protected trace span identity collides with router generation ${call.generationId}`);
|
|
2149
|
+
await traceStore.appendSpan({
|
|
2150
|
+
runId,
|
|
2151
|
+
spanId: call.traceSpanId,
|
|
2152
|
+
kind: "llm",
|
|
2153
|
+
name: "protected model call",
|
|
2154
|
+
model: call.model,
|
|
2155
|
+
messages: [],
|
|
2156
|
+
startedAt: call.startedAtMs,
|
|
2157
|
+
endedAt: call.endedAtMs,
|
|
2158
|
+
status: call.status === "succeeded" ? "ok" : "error",
|
|
2159
|
+
inputTokens: call.inputTokens,
|
|
2160
|
+
outputTokens: call.outputTokens,
|
|
2161
|
+
cachedTokens: call.cachedInputTokens,
|
|
2162
|
+
reasoningTokens: call.reasoningTokens,
|
|
2163
|
+
costUsd: call.costUsdNanos / USD_NANOS,
|
|
2164
|
+
attributes: {
|
|
2165
|
+
"tangle.protected_model.source": "router-settlement",
|
|
2166
|
+
"tangle.router.call_id": call.callId,
|
|
2167
|
+
"tangle.router.generation_id": call.generationId
|
|
2168
|
+
}
|
|
2169
|
+
});
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
/** Match every protected trace span one-for-one against gateway call evidence. */
|
|
2173
|
+
function assertTraceMatchesModelSettlement(spans, settlement) {
|
|
2174
|
+
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}`);
|
|
2175
|
+
const byId = new Map(spans.map((span) => [span.spanId, span]));
|
|
2176
|
+
if (byId.size !== spans.length) throw new Error("protected trace has duplicate model span ids");
|
|
2177
|
+
for (const call of settlement.value.calls) {
|
|
2178
|
+
const span = byId.get(call.traceSpanId);
|
|
2179
|
+
if (!span) throw new Error(`protected trace is missing model ledger span ${call.traceSpanId}`);
|
|
2180
|
+
assertTraceCall(span, call);
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
function usdToNanos(value, label) {
|
|
2184
|
+
if (!Number.isFinite(value) || value < 0) throw new Error(`${label} must be nonnegative`);
|
|
2185
|
+
const nanos = Math.round(value * USD_NANOS);
|
|
2186
|
+
if (!Number.isSafeInteger(nanos)) throw new Error(`${label} exceeds fixed-point range`);
|
|
2187
|
+
return nanos;
|
|
2188
|
+
}
|
|
2189
|
+
function assertTraceCall(span, call) {
|
|
2190
|
+
if (span.model !== call.model) throw new Error(`protected trace span ${span.spanId} model does not match model ledger`);
|
|
2191
|
+
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`);
|
|
2192
|
+
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`);
|
|
2193
|
+
for (const [name, traced, settled] of [
|
|
2194
|
+
[
|
|
2195
|
+
"inputTokens",
|
|
2196
|
+
span.inputTokens,
|
|
2197
|
+
call.inputTokens
|
|
2198
|
+
],
|
|
2199
|
+
[
|
|
2200
|
+
"outputTokens",
|
|
2201
|
+
span.outputTokens,
|
|
2202
|
+
call.outputTokens
|
|
2203
|
+
],
|
|
2204
|
+
[
|
|
2205
|
+
"cachedInputTokens",
|
|
2206
|
+
span.cachedTokens ?? 0,
|
|
2207
|
+
call.cachedInputTokens
|
|
2208
|
+
],
|
|
2209
|
+
[
|
|
2210
|
+
"reasoningTokens",
|
|
2211
|
+
span.reasoningTokens ?? 0,
|
|
2212
|
+
call.reasoningTokens
|
|
2213
|
+
]
|
|
2214
|
+
]) if (traced === void 0 || traced !== settled) throw new Error(`protected trace span ${span.spanId} ${name} ${traced} does not match model ledger ${settled}`);
|
|
2215
|
+
if (span.costUsd === void 0) throw new Error(`protected trace span ${span.spanId} is missing costUsd`);
|
|
2216
|
+
const tracedCost = usdToNanos(span.costUsd, `protected trace span ${span.spanId} costUsd`);
|
|
2217
|
+
if (tracedCost !== call.costUsdNanos) throw new Error(`protected trace span ${span.spanId} costUsdNanos ${tracedCost} does not match model ledger ${call.costUsdNanos}`);
|
|
2218
|
+
}
|
|
2219
|
+
function assertIdentifier(value, label) {
|
|
2220
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 256) throw new Error(`${label} must be a non-empty bounded string`);
|
|
2221
|
+
}
|
|
2222
|
+
function assertCount(value, label) {
|
|
2223
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new Error(`${label} must be a nonnegative safe integer`);
|
|
2224
|
+
}
|
|
2225
|
+
function assertTimestamp(value, label) {
|
|
2226
|
+
if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`${label} must be a positive safe integer`);
|
|
2227
|
+
}
|
|
2228
|
+
function safeAdd(left, right, label) {
|
|
2229
|
+
const total = left + right;
|
|
2230
|
+
if (!Number.isSafeInteger(total)) throw new Error(`${label} exceeds safe integer range`);
|
|
2231
|
+
return total;
|
|
2232
|
+
}
|
|
2233
|
+
//#endregion
|
|
2234
|
+
//#region src/candidate-execution/verify.ts
|
|
2235
|
+
const verifiedCandidateState = /* @__PURE__ */ new WeakMap();
|
|
2236
|
+
/** Surfaces admitted by Runtime's verifier before an environment adapter is selected. */
|
|
2237
|
+
const AGENT_CANDIDATE_EXECUTION_SUPPORT = Object.freeze({
|
|
2238
|
+
outcomes: Object.freeze(["workspace", "output"]),
|
|
2239
|
+
code: Object.freeze([
|
|
2240
|
+
"disabled",
|
|
2241
|
+
"no-op",
|
|
2242
|
+
"git-patch"
|
|
2243
|
+
]),
|
|
2244
|
+
memory: Object.freeze(["disabled", "isolated"]),
|
|
2245
|
+
knowledge: true,
|
|
2246
|
+
profile: Object.freeze({
|
|
2247
|
+
mcpTransports: Object.freeze(["stdio"]),
|
|
2248
|
+
remoteMcp: false,
|
|
2249
|
+
tools: false,
|
|
2250
|
+
permissions: false,
|
|
2251
|
+
modes: false,
|
|
2252
|
+
confidential: false
|
|
2253
|
+
})
|
|
2254
|
+
});
|
|
2255
|
+
/** Verifies every digest, resource, workspace, and Git object in a candidate bundle. */
|
|
2256
|
+
async function verifyAgentCandidateBundle(input, ports) {
|
|
2257
|
+
const parsed = agentCandidateBundleSchema.parse(input);
|
|
2258
|
+
const withoutDigest = omitTopLevelDigest(parsed);
|
|
2259
|
+
const actualDigest = canonicalCandidateDigest$1(withoutDigest);
|
|
2260
|
+
if (actualDigest !== parsed.digest) throw new Error(`candidate bundle digest ${parsed.digest} does not match ${actualDigest}`);
|
|
2261
|
+
const canonicalBytes = canonicalCandidateBytes(withoutDigest);
|
|
2262
|
+
verifyBytes(canonicalBytes, parsed.digest, canonicalBytes.byteLength, "candidate bundle");
|
|
2263
|
+
assertCandidateProfileExecutionSupport(parsed.profile);
|
|
2264
|
+
const artifactBytes = /* @__PURE__ */ new Map();
|
|
2265
|
+
const readArtifact = async (artifact) => {
|
|
2266
|
+
const key = artifactCacheKey(artifact);
|
|
2267
|
+
const existing = artifactBytes.get(key);
|
|
2268
|
+
if (existing) return Uint8Array.from(existing);
|
|
2269
|
+
const bytes = await readVerifiedArtifact(artifact, ports.artifacts);
|
|
2270
|
+
artifactBytes.set(key, Uint8Array.from(bytes));
|
|
2271
|
+
return bytes;
|
|
2272
|
+
};
|
|
2273
|
+
let patchBytes;
|
|
2274
|
+
if (parsed.code.kind === "git-patch") patchBytes = await readArtifact(parsed.code.patch.artifact);
|
|
2275
|
+
const materializedTree = await verifyCandidateCode(parsed.code, ports.repositories, patchBytes);
|
|
2276
|
+
const resourceBytes = /* @__PURE__ */ new Map();
|
|
2277
|
+
for (const resource of candidateResources(parsed)) {
|
|
2278
|
+
const bytes = resource.kind === "inline" ? Buffer.from(resource.content, "utf8") : await readCandidateGitHubResource(resource, ports.repositories);
|
|
2279
|
+
verifyBytes(bytes, resource.sha256, resource.byteLength, `candidate resource ${resource.name ?? (resource.kind === "github" ? resource.path : "<unnamed>")}`);
|
|
2280
|
+
resourceBytes.set(resourceKey(resource), Uint8Array.from(bytes));
|
|
2281
|
+
resourceBytes.set(resource.sha256, Uint8Array.from(bytes));
|
|
2282
|
+
}
|
|
2283
|
+
if (parsed.execution.workspace) {
|
|
2284
|
+
const workspace = await verifyWorkspaceSnapshotArtifacts(parsed.execution.workspace, ports.artifacts);
|
|
2285
|
+
artifactBytes.set(artifactCacheKey(parsed.execution.workspace.manifest), workspace.manifest);
|
|
2286
|
+
artifactBytes.set(artifactCacheKey(parsed.execution.workspace.archive), workspace.archive);
|
|
2287
|
+
}
|
|
2288
|
+
if (parsed.knowledge) {
|
|
2289
|
+
const knowledge = await verifyWorkspaceSnapshotArtifacts(parsed.knowledge.snapshot, ports.artifacts);
|
|
2290
|
+
artifactBytes.set(artifactCacheKey(parsed.knowledge.snapshot.manifest), knowledge.manifest);
|
|
2291
|
+
artifactBytes.set(artifactCacheKey(parsed.knowledge.snapshot.archive), knowledge.archive);
|
|
2292
|
+
if (parsed.knowledge.retrievalConfig) await readArtifact(parsed.knowledge.retrievalConfig);
|
|
2293
|
+
await readArtifact(parsed.knowledge.evaluation);
|
|
2294
|
+
}
|
|
2295
|
+
if (parsed.memory.mode === "isolated" && parsed.memory.seed) await readArtifact(parsed.memory.seed);
|
|
2296
|
+
deepFreezeCandidate(parsed);
|
|
2297
|
+
const verified = Object.freeze({
|
|
2298
|
+
bundle: parsed,
|
|
2299
|
+
...materializedTree === void 0 ? {} : { materializedTree },
|
|
2300
|
+
[verifiedCandidateBrand]: true
|
|
2301
|
+
});
|
|
2302
|
+
verifiedCandidateState.set(verified, {
|
|
2303
|
+
ports,
|
|
2304
|
+
artifactBytes,
|
|
2305
|
+
resourceBytes
|
|
2306
|
+
});
|
|
2307
|
+
return verified;
|
|
2308
|
+
}
|
|
2309
|
+
function getVerifiedCandidateState(candidate) {
|
|
2310
|
+
const state = verifiedCandidateState.get(candidate);
|
|
2311
|
+
if (!state || candidate[verifiedCandidateBrand] !== true) throw new Error("candidate must come from verifyAgentCandidateBundle");
|
|
2312
|
+
return state;
|
|
2313
|
+
}
|
|
2314
|
+
async function verifiedArtifactBytes(candidate, artifact) {
|
|
2315
|
+
const state = getVerifiedCandidateState(candidate);
|
|
2316
|
+
const key = artifactCacheKey(artifact);
|
|
2317
|
+
const existing = state.artifactBytes.get(key);
|
|
2318
|
+
if (existing) return Uint8Array.from(existing);
|
|
2319
|
+
const bytes = await readVerifiedArtifact(artifact, state.ports.artifacts);
|
|
2320
|
+
state.artifactBytes.set(key, Uint8Array.from(bytes));
|
|
2321
|
+
return bytes;
|
|
2322
|
+
}
|
|
2323
|
+
function verifiedResourceTextByDigest(candidate) {
|
|
2324
|
+
const output = /* @__PURE__ */ new Map();
|
|
2325
|
+
for (const resource of candidateResources(candidate.bundle)) {
|
|
2326
|
+
const bytes = getVerifiedCandidateState(candidate).resourceBytes.get(resource.sha256);
|
|
2327
|
+
if (!bytes) throw new Error(`candidate resource digest was not verified: ${resource.sha256}`);
|
|
2328
|
+
output.set(resource.sha256, new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
2329
|
+
}
|
|
2330
|
+
return output;
|
|
2331
|
+
}
|
|
2332
|
+
function candidateResources(bundle) {
|
|
2333
|
+
const resources = bundle.profile.resources;
|
|
2334
|
+
if (!resources) return [];
|
|
2335
|
+
const output = [];
|
|
2336
|
+
for (const mount of resources.files ?? []) output.push(mount.resource);
|
|
2337
|
+
output.push(...resources.tools ?? []);
|
|
2338
|
+
output.push(...resources.skills ?? []);
|
|
2339
|
+
output.push(...resources.agents ?? []);
|
|
2340
|
+
output.push(...resources.commands ?? []);
|
|
2341
|
+
if (typeof resources.instructions === "object") output.push(resources.instructions);
|
|
2342
|
+
return output;
|
|
2343
|
+
}
|
|
2344
|
+
function resourceKey(resource) {
|
|
2345
|
+
return canonicalCandidateDigest$1(resource);
|
|
2346
|
+
}
|
|
2347
|
+
//#endregion
|
|
2348
|
+
//#region src/candidate-execution/knowledge.ts
|
|
2349
|
+
/** Environment variable containing the materialized candidate knowledge root. */
|
|
2350
|
+
const CANDIDATE_KNOWLEDGE_ROOT_ENV = "TANGLE_CANDIDATE_KNOWLEDGE_ROOT";
|
|
2351
|
+
/** Environment variable containing the materialized retrieval configuration path. */
|
|
2352
|
+
const CANDIDATE_KNOWLEDGE_RETRIEVAL_CONFIG_ENV = "TANGLE_CANDIDATE_KNOWLEDGE_RETRIEVAL_CONFIG";
|
|
2353
|
+
/** Deterministic, signed locations used by every candidate executor. */
|
|
2354
|
+
function candidateKnowledgeExecutionPaths(taskRoot, hasRetrievalConfig) {
|
|
2355
|
+
if (!posix.isAbsolute(taskRoot) || posix.normalize(taskRoot) !== taskRoot) throw new Error("candidate knowledge requires a normalized absolute task root");
|
|
2356
|
+
const parent = posix.join(taskRoot, ".tangle");
|
|
2357
|
+
return {
|
|
2358
|
+
root: posix.join(parent, "knowledge"),
|
|
2359
|
+
...hasRetrievalConfig ? { retrievalConfig: posix.join(parent, "knowledge-retrieval-config.json") } : {}
|
|
2360
|
+
};
|
|
2361
|
+
}
|
|
2362
|
+
/** Verify and detach the exact file-backed knowledge admitted by the bundle. */
|
|
2363
|
+
async function prepareAgentCandidateKnowledge(candidate, ports, arm) {
|
|
2364
|
+
const knowledge = candidate.bundle.knowledge;
|
|
2365
|
+
if (!knowledge) return void 0;
|
|
2366
|
+
const archive = await verifiedArtifactBytes(candidate, knowledge.snapshot.archive);
|
|
2367
|
+
const retrievalConfig = knowledge.retrievalConfig ? await verifiedArtifactBytes(candidate, knowledge.retrievalConfig) : void 0;
|
|
2368
|
+
const root = await mkdtemp(join(tmpdir(), "agent-candidate-knowledge-"));
|
|
2369
|
+
try {
|
|
2370
|
+
await ports.workspaces.materialize({
|
|
2371
|
+
role: "knowledge",
|
|
2372
|
+
snapshot: knowledge.snapshot,
|
|
2373
|
+
archive,
|
|
2374
|
+
destination: root
|
|
2375
|
+
});
|
|
2376
|
+
const expectedHash = arm === "baseline" ? knowledge.candidate.baseHash : knowledge.candidate.candidateHash;
|
|
2377
|
+
const actualHash = `sha256:${await hashKnowledgeBase(root)}`;
|
|
2378
|
+
if (actualHash !== expectedHash) throw new Error(`materialized ${arm} knowledge does not match its measured content: expected ${expectedHash}, got ${actualHash}`);
|
|
2379
|
+
const files = await readMaterializedWorkspaceFiles(root, knowledge.snapshot.material);
|
|
2380
|
+
return Object.freeze({
|
|
2381
|
+
candidate: knowledge.candidate,
|
|
2382
|
+
snapshot: knowledge.snapshot,
|
|
2383
|
+
files: Object.freeze(files.map((file) => Object.freeze({
|
|
2384
|
+
path: file.path,
|
|
2385
|
+
mode: file.mode,
|
|
2386
|
+
bytes: Uint8Array.from(file.bytes)
|
|
2387
|
+
}))),
|
|
2388
|
+
...retrievalConfig ? { retrievalConfig: Uint8Array.from(retrievalConfig) } : {}
|
|
2389
|
+
});
|
|
2390
|
+
} finally {
|
|
2391
|
+
await rm(root, {
|
|
2392
|
+
recursive: true,
|
|
2393
|
+
force: true
|
|
2394
|
+
});
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
//#endregion
|
|
2398
|
+
//#region src/candidate-execution/executor-capture.ts
|
|
2399
|
+
const sealedFinalCaptureBrand = Symbol("sealedAgentCandidateExecutorFinalCapture");
|
|
2400
|
+
/** Validate and detach the only candidate-authored fields accepted from execution. */
|
|
2401
|
+
function sealAgentCandidateProtectedRunCapture(value) {
|
|
2402
|
+
const capture = requireRecord(value, "candidate execution capture");
|
|
2403
|
+
assertExactObjectKeys(capture, ["executionId", "termination"], "candidate execution capture");
|
|
2404
|
+
if (typeof capture.executionId !== "string" || capture.executionId.length === 0) throw new Error("candidate execution capture has an invalid executionId");
|
|
2405
|
+
return Object.freeze({
|
|
2406
|
+
executionId: capture.executionId,
|
|
2407
|
+
termination: Object.freeze(agentCandidateTerminationSchema.parse(capture.termination))
|
|
2408
|
+
});
|
|
2409
|
+
}
|
|
2410
|
+
/** Validate, detach, and freeze evaluator-owned evidence captured after process death. */
|
|
2411
|
+
function sealAgentCandidateExecutorFinalCapture(value, expectedOutcome) {
|
|
2412
|
+
const capture = requireRecord(value, "candidate final capture");
|
|
2413
|
+
assertExactObjectKeys(capture, [], "candidate final capture", [
|
|
2414
|
+
"taskOutcome",
|
|
2415
|
+
"memoryAfter",
|
|
2416
|
+
"evidence"
|
|
2417
|
+
]);
|
|
2418
|
+
const taskOutcome = capture.taskOutcome ? sealTaskOutcomeCapture(capture.taskOutcome, expectedOutcome) : void 0;
|
|
2419
|
+
const memoryAfter = capture.memoryAfter ? sealMemoryCapture(capture.memoryAfter) : void 0;
|
|
2420
|
+
if (capture.evidence !== void 0 && !(capture.evidence instanceof Uint8Array)) throw new Error("candidate executor evidence must be a byte array");
|
|
2421
|
+
const evidence = capture.evidence ? Uint8Array.from(capture.evidence) : void 0;
|
|
2422
|
+
return Object.freeze({
|
|
2423
|
+
...taskOutcome ? { taskOutcome } : {},
|
|
2424
|
+
...memoryAfter ? { memoryAfter: Object.freeze(memoryAfter) } : {},
|
|
2425
|
+
...evidence ? { evidence } : {},
|
|
2426
|
+
[sealedFinalCaptureBrand]: true
|
|
2427
|
+
});
|
|
2428
|
+
}
|
|
2429
|
+
/** Prove exact process death before any final evidence capture. */
|
|
2430
|
+
function sealAgentCandidateExecutorStopAcknowledgement(value) {
|
|
2431
|
+
const capture = requireRecord(value, "candidate stop acknowledgement");
|
|
2432
|
+
assertExactObjectKeys(capture, ["stopped"], "candidate stop acknowledgement");
|
|
2433
|
+
if (capture.stopped !== true) throw new Error("candidate stop acknowledgement does not prove process death");
|
|
2434
|
+
}
|
|
2435
|
+
function sealMemoryCapture(value) {
|
|
2436
|
+
const capture = requireRecord(value, "candidate memory capture");
|
|
2437
|
+
assertExactObjectKeys(capture, ["afterState", "archive"], "candidate memory capture");
|
|
2438
|
+
if (!(capture.archive instanceof Uint8Array)) throw new Error("candidate memory capture archive must be a byte array");
|
|
2439
|
+
return Object.freeze({
|
|
2440
|
+
afterState: Object.freeze(agentCandidateWorkspaceManifestMaterialSchema.parse(capture.afterState)),
|
|
2441
|
+
archive: Uint8Array.from(capture.archive)
|
|
2442
|
+
});
|
|
2443
|
+
}
|
|
2444
|
+
function sealTaskOutcomeCapture(value, expected) {
|
|
2445
|
+
const capture = requireRecord(value, "candidate task capture");
|
|
2446
|
+
if (capture.kind === "output") {
|
|
2447
|
+
assertExactObjectKeys(capture, ["kind", "bytes"], "candidate task output capture");
|
|
2448
|
+
if (expected.kind !== "output") throw new Error("candidate task output capture does not match the signed outcome kind");
|
|
2449
|
+
if (!(capture.bytes instanceof Uint8Array)) throw new Error("candidate task output capture must contain a byte array");
|
|
2450
|
+
if (capture.bytes.byteLength > expected.maxBytes) throw new Error(`candidate task output exceeds the frozen ${expected.maxBytes}-byte maximum`);
|
|
2451
|
+
return Object.freeze({
|
|
2452
|
+
kind: "output",
|
|
2453
|
+
bytes: Uint8Array.from(capture.bytes)
|
|
2454
|
+
});
|
|
2455
|
+
}
|
|
2456
|
+
assertExactObjectKeys(capture, [
|
|
2457
|
+
"kind",
|
|
2458
|
+
"resultTree",
|
|
2459
|
+
"afterState",
|
|
2460
|
+
"archive",
|
|
2461
|
+
"gitDiff"
|
|
2462
|
+
], "candidate task capture");
|
|
2463
|
+
if (capture.kind !== "workspace") throw new Error("candidate task capture has an invalid outcome kind");
|
|
2464
|
+
if (expected.kind !== "workspace") throw new Error("candidate workspace capture does not match the signed outcome kind");
|
|
2465
|
+
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");
|
|
2466
|
+
if (!(capture.archive instanceof Uint8Array) || !(capture.gitDiff instanceof Uint8Array)) throw new Error("candidate task capture archive and gitDiff must be byte arrays");
|
|
2467
|
+
const afterState = agentCandidateWorkspaceManifestMaterialSchema.parse(capture.afterState);
|
|
2468
|
+
return Object.freeze({
|
|
2469
|
+
kind: "workspace",
|
|
2470
|
+
resultTree: capture.resultTree,
|
|
2471
|
+
afterState: Object.freeze(afterState),
|
|
2472
|
+
archive: Uint8Array.from(capture.archive),
|
|
2473
|
+
gitDiff: Uint8Array.from(capture.gitDiff)
|
|
2474
|
+
});
|
|
2475
|
+
}
|
|
2476
|
+
function requireRecord(value, label) {
|
|
2477
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
|
|
2478
|
+
return value;
|
|
2479
|
+
}
|
|
2480
|
+
//#endregion
|
|
2481
|
+
//#region src/candidate-execution/output-artifacts.ts
|
|
2482
|
+
/** Persist evaluator evidence, read it back, and bind the returned locator to the exact bytes. */
|
|
2483
|
+
async function persistCandidateOutputArtifact(port, input) {
|
|
2484
|
+
input.signal?.throwIfAborted();
|
|
2485
|
+
const bytes = Uint8Array.from(input.bytes);
|
|
2486
|
+
const expectedDigest = sha256Bytes(bytes);
|
|
2487
|
+
const ref = agentCandidateArtifactRefSchema.parse(await port.put({
|
|
2488
|
+
executionId: input.executionId,
|
|
2489
|
+
purpose: input.purpose,
|
|
2490
|
+
bytes: Uint8Array.from(bytes),
|
|
2491
|
+
...input.signal ? { signal: input.signal } : {}
|
|
2492
|
+
}));
|
|
2493
|
+
input.signal?.throwIfAborted();
|
|
2494
|
+
if (ref.sha256 !== expectedDigest || ref.byteLength !== bytes.byteLength) throw new Error("candidate output locator does not identify the submitted bytes");
|
|
2495
|
+
const stored = await port.read(ref);
|
|
2496
|
+
input.signal?.throwIfAborted();
|
|
2497
|
+
verifyBytes(stored, expectedDigest, bytes.byteLength, "persisted candidate output");
|
|
2498
|
+
return immutableCandidateValue(ref);
|
|
2499
|
+
}
|
|
2500
|
+
//#endregion
|
|
2501
|
+
//#region src/candidate-execution/finalize.ts
|
|
2502
|
+
/** Builds a candidate run receipt exclusively from protected trace and memory evidence. */
|
|
2503
|
+
async function finalizeAgentCandidateRun(state, capture, traceStore, traceIdentity, settlement, evidence, protectedValues, writeRedactionReport, signal) {
|
|
2504
|
+
let termination;
|
|
2505
|
+
try {
|
|
2506
|
+
signal?.throwIfAborted();
|
|
2507
|
+
if (capture.executionId !== state.executionId) throw new Error("protected capture execution id does not match the prepared execution");
|
|
2508
|
+
termination = capture.termination;
|
|
2509
|
+
if (termination.kind === "timeout" && termination.timeoutMs !== state.executionPlan.value.material.limits.timeoutMs) throw new Error("timeout termination does not match the frozen execution limit");
|
|
2510
|
+
const run = await traceStore.getRun(traceIdentity.runId);
|
|
2511
|
+
if (!run) throw new Error(`protected trace run is missing: ${traceIdentity.runId}`);
|
|
2512
|
+
if (run.status === "running" || run.endedAt === void 0) throw new Error("protected trace run is not terminal");
|
|
2513
|
+
assertTraceBindings(run.tags, traceIdentity.tags);
|
|
2514
|
+
const [spans, events, budget, artifacts] = await Promise.all([
|
|
2515
|
+
traceStore.spans({ runId: run.runId }),
|
|
2516
|
+
traceStore.events({ runId: run.runId }),
|
|
2517
|
+
traceStore.budget(run.runId),
|
|
2518
|
+
traceStore.artifacts(run.runId)
|
|
2519
|
+
]);
|
|
2520
|
+
const orderedSpans = [...spans].sort((a, b) => a.startedAt - b.startedAt || compareStrings(a.spanId, b.spanId));
|
|
2521
|
+
const orderedEvents = [...events].sort((a, b) => a.timestamp - b.timestamp || compareStrings(a.eventId, b.eventId));
|
|
2522
|
+
const orderedBudget = [...budget].sort((a, b) => a.timestamp - b.timestamp || compareStrings(a.dimension, b.dimension));
|
|
2523
|
+
const orderedArtifacts = [...artifacts].sort((a, b) => compareStrings(a.artifactId, b.artifactId));
|
|
2524
|
+
const modelSpans = orderedSpans.filter(isLlmSpan);
|
|
2525
|
+
assertTraceMatchesModelSettlement(modelSpans, settlement);
|
|
2526
|
+
const steps = enforceLimits(state, run.startedAt, run.endedAt, orderedSpans, settlement);
|
|
2527
|
+
const memory = await memoryReceipt(state, evidence.finalCapture, evidence.persistedCapture.memoryAfter, signal);
|
|
2528
|
+
const redacted = redactProtectedValue({
|
|
2529
|
+
run: {
|
|
2530
|
+
...run,
|
|
2531
|
+
redactionVersion: REDACTION_VERSION
|
|
2532
|
+
},
|
|
2533
|
+
spans: orderedSpans,
|
|
2534
|
+
events: orderedEvents,
|
|
2535
|
+
budget: orderedBudget,
|
|
2536
|
+
artifacts: orderedArtifacts
|
|
2537
|
+
}, protectedValues);
|
|
2538
|
+
const combinedByRule = { ...writeRedactionReport?.byRule ?? {} };
|
|
2539
|
+
for (const [rule, count] of Object.entries(redacted.report.byRule)) combinedByRule[rule] = (combinedByRule[rule] ?? 0) + count;
|
|
2540
|
+
const traceBytes = canonicalCandidateBytes({
|
|
2541
|
+
...redacted.value,
|
|
2542
|
+
evaluatorLimits: { resultTimeoutMs: state.resultTimeoutMs },
|
|
2543
|
+
redaction: {
|
|
2544
|
+
version: REDACTION_VERSION,
|
|
2545
|
+
redactionCount: (writeRedactionReport?.redactionCount ?? 0) + redacted.report.redactionCount,
|
|
2546
|
+
byRule: combinedByRule
|
|
2547
|
+
}
|
|
2548
|
+
});
|
|
2549
|
+
assertNoProtectedBytes(traceBytes, protectedValues);
|
|
2550
|
+
const trace = {
|
|
2551
|
+
artifact: await persistCandidateOutputArtifact(evidence.outputArtifacts, {
|
|
2552
|
+
executionId: state.executionId,
|
|
2553
|
+
purpose: "trace",
|
|
2554
|
+
bytes: traceBytes,
|
|
2555
|
+
signal
|
|
2556
|
+
}),
|
|
2557
|
+
eventCount: 1 + orderedSpans.length + orderedEvents.length + orderedBudget.length + orderedArtifacts.length,
|
|
2558
|
+
modelCallCount: modelSpans.length
|
|
2559
|
+
};
|
|
2560
|
+
const document = canonicalCandidateDocument({
|
|
2561
|
+
kind: "agent-candidate-run",
|
|
2562
|
+
digestAlgorithm: "rfc8785-sha256",
|
|
2563
|
+
bundleDigest: state.bundle.digest,
|
|
2564
|
+
runCellDigest: state.executionPlan.value.material.runCell.digest,
|
|
2565
|
+
materializationReceiptDigest: state.materializationReceipt.digest,
|
|
2566
|
+
executionPlanDigest: state.executionPlan.value.digest,
|
|
2567
|
+
timing: {
|
|
2568
|
+
startedAtMs: run.startedAt,
|
|
2569
|
+
endedAtMs: run.endedAt,
|
|
2570
|
+
durationMs: run.endedAt - run.startedAt
|
|
2571
|
+
},
|
|
2572
|
+
steps,
|
|
2573
|
+
memory,
|
|
2574
|
+
trace,
|
|
2575
|
+
termination,
|
|
2576
|
+
executorCapture: evidence.persistedCapture.evidence,
|
|
2577
|
+
modelSettlement: evidence.modelSettlement,
|
|
2578
|
+
taskOutcome: evidence.taskOutcome.evidence,
|
|
2579
|
+
benchmarkResult: evidence.benchmarkResult
|
|
2580
|
+
});
|
|
2581
|
+
agentCandidateRunReceiptSchema.parse(document.value);
|
|
2582
|
+
const runReceipt = await persistCandidateOutputArtifact(evidence.outputArtifacts, {
|
|
2583
|
+
executionId: state.executionId,
|
|
2584
|
+
purpose: "run-receipt",
|
|
2585
|
+
bytes: document.bytes,
|
|
2586
|
+
signal
|
|
2587
|
+
});
|
|
2588
|
+
return {
|
|
2589
|
+
succeeded: true,
|
|
2590
|
+
receipt: document,
|
|
2591
|
+
artifacts: {
|
|
2592
|
+
modelSettlement: evidence.modelSettlement.artifact,
|
|
2593
|
+
executorCapture: evidence.persistedCapture.evidence,
|
|
2594
|
+
taskOutcome: evidence.taskOutcome.evidence.artifact,
|
|
2595
|
+
benchmarkResult: evidence.benchmarkResult.artifact,
|
|
2596
|
+
runReceipt
|
|
2597
|
+
}
|
|
2598
|
+
};
|
|
2599
|
+
} catch (error) {
|
|
2600
|
+
return { ...failedAgentCandidateRun(state, redactProtectedReason(error instanceof Error ? error.message : String(error), protectedValues), termination, settlement.usage) };
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
function assertTraceBindings(tags, expected) {
|
|
2604
|
+
for (const name of Object.values(CANDIDATE_TRACE_TAGS)) if (tags?.[name] !== expected[name]) throw new Error(`protected trace is not bound to prepared execution tag ${name}`);
|
|
2605
|
+
}
|
|
2606
|
+
function enforceLimits(state, startedAt, endedAt, spans, settlement) {
|
|
2607
|
+
const limits = state.executionPlan.value.material.limits;
|
|
2608
|
+
const usage = settlement.usage;
|
|
2609
|
+
const wallMs = endedAt - startedAt;
|
|
2610
|
+
if (!Number.isFinite(wallMs) || wallMs < 0 || wallMs > limits.timeoutMs) throw new Error(`protected trace wall time ${wallMs} exceeds ${limits.timeoutMs}`);
|
|
2611
|
+
const steps = spans.filter((span) => span.kind === "tool").length;
|
|
2612
|
+
const checks = [
|
|
2613
|
+
[
|
|
2614
|
+
steps,
|
|
2615
|
+
limits.maxSteps,
|
|
2616
|
+
"tool steps"
|
|
2617
|
+
],
|
|
2618
|
+
[
|
|
2619
|
+
usage.modelCalls,
|
|
2620
|
+
limits.maxModelCalls,
|
|
2621
|
+
"model calls"
|
|
2622
|
+
],
|
|
2623
|
+
[
|
|
2624
|
+
usage.inputTokens,
|
|
2625
|
+
limits.maxInputTokens,
|
|
2626
|
+
"input tokens"
|
|
2627
|
+
],
|
|
2628
|
+
[
|
|
2629
|
+
usage.outputTokens,
|
|
2630
|
+
limits.maxOutputTokens,
|
|
2631
|
+
"output tokens"
|
|
2632
|
+
]
|
|
2633
|
+
];
|
|
2634
|
+
for (const [actual, limit, label] of checks) if (actual > limit) throw new Error(`protected ${label} ${actual} exceeds ${limit}`);
|
|
2635
|
+
if (usage.costUsdNanos > usdToNanos(limits.maxCostUsd, "frozen maxCostUsd")) throw new Error(`protected cost USD ${usage.costUsdNanos / 1e9} exceeds ${limits.maxCostUsd}`);
|
|
2636
|
+
return steps;
|
|
2637
|
+
}
|
|
2638
|
+
async function memoryReceipt(state, capture, persisted, signal) {
|
|
2639
|
+
signal?.throwIfAborted();
|
|
2640
|
+
if (state.memory.mode === "disabled") {
|
|
2641
|
+
if (capture.memoryAfter !== void 0 || persisted !== void 0) throw new Error("disabled memory cannot return an after-state");
|
|
2642
|
+
return { mode: "disabled" };
|
|
2643
|
+
}
|
|
2644
|
+
if (!capture.memoryAfter) throw new Error("isolated memory is missing its protected after-state");
|
|
2645
|
+
if (!persisted) throw new Error("isolated memory is missing persisted capture evidence");
|
|
2646
|
+
const afterState = capture.memoryAfter.afterState;
|
|
2647
|
+
const manifestBytes = canonicalCandidateBytes(afterState);
|
|
2648
|
+
const snapshot = agentCandidateWorkspaceSnapshotEvidenceSchema.parse({
|
|
2649
|
+
kind: "agent-candidate-workspace-snapshot",
|
|
2650
|
+
digest: sha256Bytes(manifestBytes),
|
|
2651
|
+
material: afterState,
|
|
2652
|
+
manifest: persisted.manifest,
|
|
2653
|
+
archive: persisted.archive
|
|
2654
|
+
});
|
|
2655
|
+
return {
|
|
2656
|
+
mode: "isolated",
|
|
2657
|
+
scope: "task",
|
|
2658
|
+
effectiveNamespace: state.memory.effectiveNamespace,
|
|
2659
|
+
resetEvidenceDigest: state.memory.reset.evidence.sha256,
|
|
2660
|
+
beforeStateDigest: state.memory.beforeState.digest,
|
|
2661
|
+
afterState: snapshot
|
|
2662
|
+
};
|
|
2663
|
+
}
|
|
2664
|
+
function failedAgentCandidateRun(state, reason, termination, usage = null) {
|
|
2665
|
+
return {
|
|
2666
|
+
succeeded: false,
|
|
2667
|
+
reason,
|
|
2668
|
+
partial: {
|
|
2669
|
+
executionId: state.executionId,
|
|
2670
|
+
bundleDigest: state.bundle.digest,
|
|
2671
|
+
executionPlanDigest: state.executionPlan.value.digest,
|
|
2672
|
+
materializationReceiptDigest: state.materializationReceipt.digest,
|
|
2673
|
+
...termination ? { termination } : {}
|
|
2674
|
+
},
|
|
2675
|
+
usage
|
|
2676
|
+
};
|
|
2677
|
+
}
|
|
2678
|
+
function compareStrings(a, b) {
|
|
2679
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
2680
|
+
}
|
|
2681
|
+
//#endregion
|
|
2682
|
+
//#region src/candidate-execution/benchmark-grader.ts
|
|
2683
|
+
/**
|
|
2684
|
+
* Admit verified grader bytes to the evaluator runner and reject any result
|
|
2685
|
+
* that is not bound to those bytes, the exact task outcome, and its raw output.
|
|
2686
|
+
*/
|
|
2687
|
+
async function runBoundCandidateBenchmarkGrader(input) {
|
|
2688
|
+
input.signal?.throwIfAborted();
|
|
2689
|
+
const descriptor = snapshotGraderDescriptor(input.grader, input.expectedGrader);
|
|
2690
|
+
const implementationBytes = await readVerifiedArtifact(descriptor.artifact, input.artifacts);
|
|
2691
|
+
if (implementationBytes.byteLength === 0) throw new Error("candidate benchmark grader implementation cannot be empty");
|
|
2692
|
+
const expectedImplementationDigest = sha256Bytes(implementationBytes);
|
|
2693
|
+
const termination = immutableCandidateValue(input.termination);
|
|
2694
|
+
const run = input.grader.run;
|
|
2695
|
+
const startedAtMs = Date.now();
|
|
2696
|
+
const result = await run(Object.freeze({
|
|
2697
|
+
executionId: input.executionId,
|
|
2698
|
+
termination,
|
|
2699
|
+
outcome: input.outcome,
|
|
2700
|
+
implementation: detachedImplementation(implementationBytes),
|
|
2701
|
+
signal: input.signal ?? new AbortController().signal
|
|
2702
|
+
}));
|
|
2703
|
+
const endedAtMs = Math.max(startedAtMs, Date.now());
|
|
2704
|
+
input.signal?.throwIfAborted();
|
|
2705
|
+
assertExactRunnerResult(result);
|
|
2706
|
+
const evidence = Uint8Array.from(result.evidence);
|
|
2707
|
+
const outputDigest = sha256Bytes(evidence);
|
|
2708
|
+
if (result.binding.implementationDigest !== expectedImplementationDigest) throw new Error("candidate benchmark grader executed implementation digest does not match its verified artifact");
|
|
2709
|
+
if (result.binding.taskOutcomeDigest !== input.outcome.evidence.digest) throw new Error("candidate benchmark grader task outcome digest does not match verified outcome");
|
|
2710
|
+
if (result.binding.outputDigest !== outputDigest) throw new Error("candidate benchmark grader raw output digest does not match returned evidence");
|
|
2711
|
+
return Object.freeze({
|
|
2712
|
+
grader: descriptor,
|
|
2713
|
+
grading: immutableCandidateValue({
|
|
2714
|
+
usage: emptyFixedSpend(),
|
|
2715
|
+
timing: {
|
|
2716
|
+
startedAtMs,
|
|
2717
|
+
endedAtMs,
|
|
2718
|
+
durationMs: endedAtMs - startedAtMs
|
|
2719
|
+
}
|
|
2720
|
+
}),
|
|
2721
|
+
evaluation: result.evaluation,
|
|
2722
|
+
evidence
|
|
2723
|
+
});
|
|
2724
|
+
}
|
|
2725
|
+
function snapshotGraderDescriptor(grader, expected) {
|
|
2726
|
+
if (!grader.name || !grader.version) throw new Error("candidate benchmark grader name and version must be non-empty");
|
|
2727
|
+
if (canonicalCandidateDigest$1({
|
|
2728
|
+
name: grader.name,
|
|
2729
|
+
version: grader.version,
|
|
2730
|
+
format: "tangle-grader",
|
|
2731
|
+
artifact: grader.artifact
|
|
2732
|
+
}) !== canonicalCandidateDigest$1(expected)) throw new Error("candidate benchmark grader does not match the signed task grader");
|
|
2733
|
+
return immutableCandidateValue(expected);
|
|
2734
|
+
}
|
|
2735
|
+
function emptyFixedSpend() {
|
|
2736
|
+
return {
|
|
2737
|
+
inputTokens: 0,
|
|
2738
|
+
outputTokens: 0,
|
|
2739
|
+
cachedInputTokens: 0,
|
|
2740
|
+
reasoningTokens: 0,
|
|
2741
|
+
modelCalls: 0,
|
|
2742
|
+
costUsdNanos: 0
|
|
2743
|
+
};
|
|
2744
|
+
}
|
|
2745
|
+
function detachedImplementation(bytes) {
|
|
2746
|
+
const stored = Uint8Array.from(bytes);
|
|
2747
|
+
return Object.freeze({
|
|
2748
|
+
byteLength: stored.byteLength,
|
|
2749
|
+
get bytes() {
|
|
2750
|
+
return Uint8Array.from(stored);
|
|
2751
|
+
}
|
|
2752
|
+
});
|
|
2753
|
+
}
|
|
2754
|
+
function assertExactRunnerResult(value) {
|
|
2755
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("candidate benchmark grader result must be an object");
|
|
2756
|
+
const result = value;
|
|
2757
|
+
const keys = Object.keys(result).sort();
|
|
2758
|
+
if (keys.length !== 3 || keys[0] !== "binding" || keys[1] !== "evaluation" || keys[2] !== "evidence") throw new Error("candidate benchmark grader returned unknown or missing fields");
|
|
2759
|
+
if (!(result.evidence instanceof Uint8Array)) throw new Error("candidate benchmark grader evidence must be bytes");
|
|
2760
|
+
if (result.binding === null || typeof result.binding !== "object" || Array.isArray(result.binding)) throw new Error("candidate benchmark grader binding must be an object");
|
|
2761
|
+
const bindingKeys = Object.keys(result.binding).sort();
|
|
2762
|
+
if (bindingKeys.length !== 3 || bindingKeys[0] !== "implementationDigest" || bindingKeys[1] !== "outputDigest" || bindingKeys[2] !== "taskOutcomeDigest") throw new Error("candidate benchmark grader binding returned unknown or missing fields");
|
|
2763
|
+
}
|
|
2764
|
+
//#endregion
|
|
2765
|
+
//#region src/candidate-execution/executor-capture-evidence.ts
|
|
2766
|
+
/** Persist structurally valid post-stop bytes without claiming they passed outcome verification. */
|
|
2767
|
+
async function persistAgentCandidateExecutorCapture(identity, expected, capture, outputArtifacts, signal) {
|
|
2768
|
+
const executorEvidence = capture.evidence ? await persistCandidateOutputArtifact(outputArtifacts, {
|
|
2769
|
+
executionId: identity.executionId,
|
|
2770
|
+
purpose: "executor-native-evidence",
|
|
2771
|
+
bytes: capture.evidence,
|
|
2772
|
+
signal
|
|
2773
|
+
}) : void 0;
|
|
2774
|
+
const taskOutcome = capture.taskOutcome ? await persistTaskCapture(identity.executionId, expected, capture.taskOutcome, outputArtifacts, signal) : void 0;
|
|
2775
|
+
const memoryAfter = capture.memoryAfter ? await persistWorkspaceCapture(identity.executionId, "memory-after-manifest", "memory-after-archive", capture.memoryAfter.afterState, capture.memoryAfter.archive, outputArtifacts, signal) : void 0;
|
|
2776
|
+
const bytes = canonicalCandidateBytes({
|
|
2777
|
+
kind: "agent-candidate-executor-capture",
|
|
2778
|
+
executionPlanDigest: identity.executionPlanDigest,
|
|
2779
|
+
...executorEvidence ? { executorEvidence } : {},
|
|
2780
|
+
...taskOutcome ? { taskOutcome } : {},
|
|
2781
|
+
...memoryAfter ? { memoryAfter } : {}
|
|
2782
|
+
});
|
|
2783
|
+
const evidence = await persistCandidateOutputArtifact(outputArtifacts, {
|
|
2784
|
+
executionId: identity.executionId,
|
|
2785
|
+
purpose: "executor-capture",
|
|
2786
|
+
bytes,
|
|
2787
|
+
signal
|
|
2788
|
+
});
|
|
2789
|
+
return Object.freeze({
|
|
2790
|
+
evidence,
|
|
2791
|
+
...taskOutcome ? { taskOutcome: Object.freeze(taskOutcome) } : {},
|
|
2792
|
+
...memoryAfter ? { memoryAfter: Object.freeze(memoryAfter) } : {}
|
|
2793
|
+
});
|
|
2794
|
+
}
|
|
2795
|
+
async function persistTaskCapture(executionId, expected, capture, outputArtifacts, signal) {
|
|
2796
|
+
if (capture.kind === "output") {
|
|
2797
|
+
if (expected.kind !== "output") throw new Error("captured output does not match the signed task");
|
|
2798
|
+
const artifact = await persistCandidateOutputArtifact(outputArtifacts, {
|
|
2799
|
+
executionId,
|
|
2800
|
+
purpose: "task-output",
|
|
2801
|
+
bytes: capture.bytes,
|
|
2802
|
+
signal
|
|
2803
|
+
});
|
|
2804
|
+
return {
|
|
2805
|
+
kind: "output",
|
|
2806
|
+
spec: {
|
|
2807
|
+
mediaType: expected.mediaType,
|
|
2808
|
+
maxBytes: expected.maxBytes
|
|
2809
|
+
},
|
|
2810
|
+
artifact
|
|
2811
|
+
};
|
|
2812
|
+
}
|
|
2813
|
+
if (expected.kind !== "workspace") throw new Error("captured workspace does not match the signed task");
|
|
2814
|
+
const workspace = await persistWorkspaceCapture(executionId, "task-manifest", "task-archive", capture.afterState, capture.archive, outputArtifacts, signal);
|
|
2815
|
+
const gitDiff = await persistCandidateOutputArtifact(outputArtifacts, {
|
|
2816
|
+
executionId,
|
|
2817
|
+
purpose: "task-patch",
|
|
2818
|
+
bytes: capture.gitDiff,
|
|
2819
|
+
signal
|
|
2820
|
+
});
|
|
2821
|
+
return {
|
|
2822
|
+
kind: "workspace",
|
|
2823
|
+
resultTree: capture.resultTree,
|
|
2824
|
+
...workspace,
|
|
2825
|
+
gitDiff
|
|
2826
|
+
};
|
|
2827
|
+
}
|
|
2828
|
+
async function persistWorkspaceCapture(executionId, manifestPurpose, archivePurpose, afterState, archiveBytes, outputArtifacts, signal) {
|
|
2829
|
+
const manifestBytes = canonicalCandidateBytes(afterState);
|
|
2830
|
+
const [manifest, archive] = await Promise.all([persistCandidateOutputArtifact(outputArtifacts, {
|
|
2831
|
+
executionId,
|
|
2832
|
+
purpose: manifestPurpose,
|
|
2833
|
+
bytes: manifestBytes,
|
|
2834
|
+
signal
|
|
2835
|
+
}), persistCandidateOutputArtifact(outputArtifacts, {
|
|
2836
|
+
executionId,
|
|
2837
|
+
purpose: archivePurpose,
|
|
2838
|
+
bytes: archiveBytes,
|
|
2839
|
+
signal
|
|
2840
|
+
})]);
|
|
2841
|
+
return {
|
|
2842
|
+
afterState,
|
|
2843
|
+
manifest,
|
|
2844
|
+
archive
|
|
2845
|
+
};
|
|
2846
|
+
}
|
|
2847
|
+
//#endregion
|
|
2848
|
+
//#region src/candidate-execution/outcome-evidence.ts
|
|
2849
|
+
/** Persist the closed evaluator model ledger as canonical receipt evidence. */
|
|
2850
|
+
async function persistCandidateModelSettlement(state, settlement, outputArtifacts) {
|
|
2851
|
+
return await persistCandidateModelSettlementEvidence({
|
|
2852
|
+
executionId: state.executionId,
|
|
2853
|
+
executionPlanDigest: state.executionPlan.value.digest,
|
|
2854
|
+
resolvedModel: state.resolvedModel
|
|
2855
|
+
}, settlement, outputArtifacts);
|
|
2856
|
+
}
|
|
2857
|
+
/** Persist a closed model ledger when only durable recovery identity remains. */
|
|
2858
|
+
async function persistCandidateModelSettlementEvidence(identity, settlement, outputArtifacts) {
|
|
2859
|
+
const material = {
|
|
2860
|
+
kind: "agent-candidate-model-settlement-material",
|
|
2861
|
+
executionPlanDigest: identity.executionPlanDigest,
|
|
2862
|
+
preparationId: settlement.value.preparationId,
|
|
2863
|
+
grantDigest: settlement.value.grantDigest,
|
|
2864
|
+
closed: true,
|
|
2865
|
+
resolved: identity.resolvedModel,
|
|
2866
|
+
calls: settlement.value.calls.map((call) => ({
|
|
2867
|
+
callId: call.callId,
|
|
2868
|
+
generationId: call.generationId,
|
|
2869
|
+
traceSpanId: call.traceSpanId,
|
|
2870
|
+
status: call.status,
|
|
2871
|
+
model: call.model,
|
|
2872
|
+
startedAtMs: call.startedAtMs,
|
|
2873
|
+
endedAtMs: call.endedAtMs,
|
|
2874
|
+
inputTokens: call.inputTokens,
|
|
2875
|
+
outputTokens: call.outputTokens,
|
|
2876
|
+
cachedInputTokens: call.cachedInputTokens ?? 0,
|
|
2877
|
+
reasoningTokens: call.reasoningTokens ?? 0,
|
|
2878
|
+
costUsdNanos: call.costUsdNanos
|
|
2879
|
+
})),
|
|
2880
|
+
usage: settlement.usage
|
|
2881
|
+
};
|
|
2882
|
+
const bytes = canonicalCandidateBytes(material);
|
|
2883
|
+
const digest = sha256Bytes(bytes);
|
|
2884
|
+
const artifact = await persistCandidateOutputArtifact(outputArtifacts, {
|
|
2885
|
+
executionId: identity.executionId,
|
|
2886
|
+
purpose: "model-settlement",
|
|
2887
|
+
bytes
|
|
2888
|
+
});
|
|
2889
|
+
return immutableCandidateValue(agentCandidateModelSettlementEvidenceSchema.parse({
|
|
2890
|
+
kind: "agent-candidate-model-settlement",
|
|
2891
|
+
digest,
|
|
2892
|
+
material,
|
|
2893
|
+
artifact
|
|
2894
|
+
}));
|
|
2895
|
+
}
|
|
2896
|
+
/** Verify once, persist the sealed capture once, and bind the resulting task evidence. */
|
|
2897
|
+
async function persistVerifiedAgentCandidateExecutorCapture(state, capture, outputArtifacts, protectedValues, signal) {
|
|
2898
|
+
signal?.throwIfAborted();
|
|
2899
|
+
const taskCapture = capture.taskOutcome;
|
|
2900
|
+
if (!taskCapture) throw new Error("candidate final capture is missing the task outcome");
|
|
2901
|
+
if (capture.evidence) assertNoProtectedBytes(capture.evidence, protectedValues);
|
|
2902
|
+
const expected = state.benchmarkTask.outcome;
|
|
2903
|
+
if (taskCapture.kind !== expected.kind) throw new Error(`candidate captured ${taskCapture.kind} outcome does not match expected ${expected.kind} outcome`);
|
|
2904
|
+
const verified = taskCapture.kind === "output" && expected.kind === "output" ? verifyOutputTaskCapture(taskCapture.bytes, expected, protectedValues) : taskCapture.kind === "workspace" && expected.kind === "workspace" ? await verifyWorkspaceTaskCapture(state, taskCapture, protectedValues, signal) : void 0;
|
|
2905
|
+
if (!verified) throw new Error("candidate task outcome kind could not be narrowed");
|
|
2906
|
+
await verifyMemoryCapture(state, capture, protectedValues, signal);
|
|
2907
|
+
const persistedCapture = await persistAgentCandidateExecutorCapture({
|
|
2908
|
+
executionId: state.executionId,
|
|
2909
|
+
executionPlanDigest: state.executionPlan.value.digest
|
|
2910
|
+
}, expected, capture, outputArtifacts, signal);
|
|
2911
|
+
const persisted = persistedCapture.taskOutcome;
|
|
2912
|
+
if (!persisted || persisted.kind !== verified.kind) throw new Error("persisted task capture kind changed");
|
|
2913
|
+
const taskOutcome = verified.kind === "output" && persisted.kind === "output" ? await persistVerifiedOutputTaskOutcome(state, verified, persisted, outputArtifacts, signal) : verified.kind === "workspace" && persisted.kind === "workspace" ? await persistVerifiedWorkspaceTaskOutcome(state, verified, persisted, outputArtifacts, signal) : void 0;
|
|
2914
|
+
if (!taskOutcome) throw new Error("persisted task outcome kind could not be narrowed");
|
|
2915
|
+
return Object.freeze({
|
|
2916
|
+
persistedCapture,
|
|
2917
|
+
taskOutcome
|
|
2918
|
+
});
|
|
2919
|
+
}
|
|
2920
|
+
async function verifyWorkspaceTaskCapture(state, capture, protectedValues, signal) {
|
|
2921
|
+
const repository = state.benchmarkTask.repository;
|
|
2922
|
+
if (!repository) throw new Error("workspace task outcome is missing repository identity");
|
|
2923
|
+
const patch = Uint8Array.from(capture.gitDiff);
|
|
2924
|
+
const archive = Uint8Array.from(capture.archive);
|
|
2925
|
+
if (archive.byteLength === 0) throw new Error("candidate task archive cannot be empty");
|
|
2926
|
+
assertNoProtectedBytes(patch, protectedValues);
|
|
2927
|
+
assertNoProtectedBytes(archive, protectedValues);
|
|
2928
|
+
const afterState = immutableCandidateValue(capture.afterState);
|
|
2929
|
+
const verified = await verifyTaskOutcomePatch({
|
|
2930
|
+
repositoryRoot: state.roots.staging.taskRoot,
|
|
2931
|
+
baseCommit: repository.baseCommit,
|
|
2932
|
+
baseTree: repository.baseTree,
|
|
2933
|
+
resultTree: capture.resultTree,
|
|
2934
|
+
patch,
|
|
2935
|
+
afterState
|
|
2936
|
+
});
|
|
2937
|
+
signal?.throwIfAborted();
|
|
2938
|
+
const manifestBytes = canonicalCandidateBytes(afterState);
|
|
2939
|
+
assertNoProtectedBytes(manifestBytes, protectedValues);
|
|
2940
|
+
await verifyTaskOutcomeArchive(state, agentCandidateWorkspaceSnapshotEvidenceSchema.parse({
|
|
2941
|
+
kind: "agent-candidate-workspace-snapshot",
|
|
2942
|
+
digest: sha256Bytes(manifestBytes),
|
|
2943
|
+
material: afterState,
|
|
2944
|
+
manifest: embeddedCandidateArtifact(manifestBytes),
|
|
2945
|
+
archive: embeddedCandidateArtifact(archive)
|
|
2946
|
+
}), archive, protectedValues);
|
|
2947
|
+
signal?.throwIfAborted();
|
|
2948
|
+
return {
|
|
2949
|
+
kind: "workspace",
|
|
2950
|
+
patch,
|
|
2951
|
+
archive,
|
|
2952
|
+
afterState,
|
|
2953
|
+
manifestBytes,
|
|
2954
|
+
resultCommit: verified.resultCommit,
|
|
2955
|
+
resultTree: verified.resultTree,
|
|
2956
|
+
repository
|
|
2957
|
+
};
|
|
2958
|
+
}
|
|
2959
|
+
async function persistVerifiedWorkspaceTaskOutcome(state, verified, persisted, outputArtifacts, signal) {
|
|
2960
|
+
const { afterState, manifestBytes, patch, repository } = verified;
|
|
2961
|
+
const snapshot = agentCandidateWorkspaceSnapshotEvidenceSchema.parse({
|
|
2962
|
+
kind: "agent-candidate-workspace-snapshot",
|
|
2963
|
+
digest: sha256Bytes(manifestBytes),
|
|
2964
|
+
material: afterState,
|
|
2965
|
+
manifest: persisted.manifest,
|
|
2966
|
+
archive: persisted.archive
|
|
2967
|
+
});
|
|
2968
|
+
const evidence = await persistTaskOutcomeEvidence(state, {
|
|
2969
|
+
kind: "agent-candidate-task-outcome-material",
|
|
2970
|
+
executionPlanDigest: state.executionPlan.value.digest,
|
|
2971
|
+
outcome: {
|
|
2972
|
+
kind: "workspace",
|
|
2973
|
+
baseRepository: {
|
|
2974
|
+
identity: repository.identity,
|
|
2975
|
+
rootIdentity: repository.rootIdentity,
|
|
2976
|
+
commit: repository.baseCommit,
|
|
2977
|
+
tree: repository.baseTree
|
|
2978
|
+
},
|
|
2979
|
+
resultRepository: {
|
|
2980
|
+
identity: repository.identity,
|
|
2981
|
+
rootIdentity: repository.rootIdentity,
|
|
2982
|
+
commit: verified.resultCommit,
|
|
2983
|
+
tree: verified.resultTree
|
|
2984
|
+
},
|
|
2985
|
+
afterState: snapshot,
|
|
2986
|
+
gitDiff: {
|
|
2987
|
+
format: "git-diff-binary",
|
|
2988
|
+
artifact: persisted.gitDiff
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
}, outputArtifacts, signal);
|
|
2992
|
+
const storedPatch = Uint8Array.from(patch);
|
|
2993
|
+
return Object.freeze({
|
|
2994
|
+
kind: "workspace",
|
|
2995
|
+
evidence,
|
|
2996
|
+
get patch() {
|
|
2997
|
+
return Uint8Array.from(storedPatch);
|
|
2998
|
+
},
|
|
2999
|
+
[verifiedTaskOutcomeBrand]: true
|
|
3000
|
+
});
|
|
3001
|
+
}
|
|
3002
|
+
function verifyOutputTaskCapture(capturedBytes, expected, protectedValues) {
|
|
3003
|
+
if (capturedBytes.byteLength === 0) throw new Error("candidate task output cannot be empty");
|
|
3004
|
+
if (capturedBytes.byteLength > expected.maxBytes) throw new Error(`candidate task output exceeds the frozen ${expected.maxBytes}-byte maximum`);
|
|
3005
|
+
const output = Uint8Array.from(capturedBytes);
|
|
3006
|
+
assertNoProtectedBytes(output, protectedValues);
|
|
3007
|
+
return {
|
|
3008
|
+
kind: "output",
|
|
3009
|
+
bytes: output,
|
|
3010
|
+
spec: {
|
|
3011
|
+
mediaType: expected.mediaType,
|
|
3012
|
+
maxBytes: expected.maxBytes
|
|
3013
|
+
}
|
|
3014
|
+
};
|
|
3015
|
+
}
|
|
3016
|
+
async function persistVerifiedOutputTaskOutcome(state, verified, persisted, outputArtifacts, signal) {
|
|
3017
|
+
if (persisted.spec.mediaType !== verified.spec.mediaType || persisted.spec.maxBytes !== verified.spec.maxBytes) throw new Error("persisted task output changed the signed media constraints");
|
|
3018
|
+
const evidence = await persistTaskOutcomeEvidence(state, {
|
|
3019
|
+
kind: "agent-candidate-task-outcome-material",
|
|
3020
|
+
executionPlanDigest: state.executionPlan.value.digest,
|
|
3021
|
+
outcome: {
|
|
3022
|
+
kind: "output",
|
|
3023
|
+
spec: verified.spec,
|
|
3024
|
+
artifact: persisted.artifact
|
|
3025
|
+
}
|
|
3026
|
+
}, outputArtifacts, signal);
|
|
3027
|
+
const storedOutput = Uint8Array.from(verified.bytes);
|
|
3028
|
+
return Object.freeze({
|
|
3029
|
+
kind: "output",
|
|
3030
|
+
evidence,
|
|
3031
|
+
spec: immutableCandidateValue(verified.spec),
|
|
3032
|
+
get bytes() {
|
|
3033
|
+
return Uint8Array.from(storedOutput);
|
|
3034
|
+
},
|
|
3035
|
+
[verifiedTaskOutcomeBrand]: true
|
|
3036
|
+
});
|
|
3037
|
+
}
|
|
3038
|
+
async function persistTaskOutcomeEvidence(state, material, outputArtifacts, signal) {
|
|
3039
|
+
const bytes = canonicalCandidateBytes(material);
|
|
3040
|
+
const digest = sha256Bytes(bytes);
|
|
3041
|
+
const artifact = await persistCandidateOutputArtifact(outputArtifacts, {
|
|
3042
|
+
executionId: state.executionId,
|
|
3043
|
+
purpose: "task-outcome",
|
|
3044
|
+
bytes,
|
|
3045
|
+
signal
|
|
3046
|
+
});
|
|
3047
|
+
return immutableCandidateValue(agentCandidateTaskOutcomeEvidenceSchema.parse({
|
|
3048
|
+
kind: "agent-candidate-task-outcome",
|
|
3049
|
+
digest,
|
|
3050
|
+
material,
|
|
3051
|
+
artifact
|
|
3052
|
+
}));
|
|
3053
|
+
}
|
|
3054
|
+
/** Grade only a runtime-verified outcome and persist both raw and normalized evidence. */
|
|
3055
|
+
async function persistCandidateBenchmarkResult(state, termination, outcome, grader, outputArtifacts, protectedValues, signal) {
|
|
3056
|
+
signal?.throwIfAborted();
|
|
3057
|
+
const frozenTermination = immutableCandidateValue(termination);
|
|
3058
|
+
const graded = await runBoundCandidateBenchmarkGrader({
|
|
3059
|
+
executionId: state.executionId,
|
|
3060
|
+
termination: frozenTermination,
|
|
3061
|
+
outcome,
|
|
3062
|
+
expectedGrader: state.benchmarkTask.grader,
|
|
3063
|
+
grader,
|
|
3064
|
+
artifacts: outputArtifacts,
|
|
3065
|
+
signal
|
|
3066
|
+
});
|
|
3067
|
+
signal?.throwIfAborted();
|
|
3068
|
+
const evaluation = normalizeEvaluation(graded.evaluation, frozenTermination);
|
|
3069
|
+
const rawEvidence = Uint8Array.from(graded.evidence);
|
|
3070
|
+
if (rawEvidence.byteLength === 0) throw new Error("candidate benchmark evidence cannot be empty");
|
|
3071
|
+
assertNoProtectedBytes(rawEvidence, protectedValues);
|
|
3072
|
+
const evidenceRef = await persistCandidateOutputArtifact(outputArtifacts, {
|
|
3073
|
+
executionId: state.executionId,
|
|
3074
|
+
purpose: "grader-evidence",
|
|
3075
|
+
bytes: rawEvidence,
|
|
3076
|
+
signal
|
|
3077
|
+
});
|
|
3078
|
+
const material = {
|
|
3079
|
+
kind: "agent-candidate-benchmark-result-material",
|
|
3080
|
+
executionPlanDigest: state.executionPlan.value.digest,
|
|
3081
|
+
taskOutcomeDigest: outcome.evidence.digest,
|
|
3082
|
+
grader: graded.grader,
|
|
3083
|
+
evidence: evidenceRef,
|
|
3084
|
+
grading: graded.grading,
|
|
3085
|
+
score: evaluation.score,
|
|
3086
|
+
passed: evaluation.passed,
|
|
3087
|
+
dimensions: evaluation.dimensions
|
|
3088
|
+
};
|
|
3089
|
+
const bytes = canonicalCandidateBytes(material);
|
|
3090
|
+
const digest = sha256Bytes(bytes);
|
|
3091
|
+
const artifact = await persistCandidateOutputArtifact(outputArtifacts, {
|
|
3092
|
+
executionId: state.executionId,
|
|
3093
|
+
purpose: "benchmark-result",
|
|
3094
|
+
bytes,
|
|
3095
|
+
signal
|
|
3096
|
+
});
|
|
3097
|
+
return immutableCandidateValue(agentCandidateBenchmarkResultEvidenceSchema.parse({
|
|
3098
|
+
kind: "agent-candidate-benchmark-result",
|
|
3099
|
+
digest,
|
|
3100
|
+
material,
|
|
3101
|
+
artifact
|
|
3102
|
+
}));
|
|
3103
|
+
}
|
|
3104
|
+
async function verifyTaskOutcomeArchive(state, snapshot, archive, protectedValues) {
|
|
3105
|
+
const root = await mkdtemp(join(tmpdir(), "agent-candidate-task-archive-"));
|
|
3106
|
+
try {
|
|
3107
|
+
await state.ports.workspaces.materialize({
|
|
3108
|
+
role: "task",
|
|
3109
|
+
snapshot,
|
|
3110
|
+
archive: Uint8Array.from(archive),
|
|
3111
|
+
destination: root
|
|
3112
|
+
});
|
|
3113
|
+
const files = await readMaterializedWorkspaceFiles(root, snapshot.material);
|
|
3114
|
+
for (const file of files) assertNoProtectedBytes(file.bytes, protectedValues);
|
|
3115
|
+
} finally {
|
|
3116
|
+
await rm(root, {
|
|
3117
|
+
recursive: true,
|
|
3118
|
+
force: true
|
|
3119
|
+
});
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
async function verifyMemoryCapture(state, capture, protectedValues, signal) {
|
|
3123
|
+
if (state.memory.mode === "disabled") {
|
|
3124
|
+
if (capture.memoryAfter) throw new Error("disabled memory cannot return an after-state");
|
|
3125
|
+
return;
|
|
3126
|
+
}
|
|
3127
|
+
const memory = capture.memoryAfter;
|
|
3128
|
+
if (!memory) throw new Error("isolated memory is missing its protected after-state");
|
|
3129
|
+
if (memory.archive.byteLength === 0) throw new Error("isolated memory archive cannot be empty");
|
|
3130
|
+
const manifestBytes = canonicalCandidateBytes(memory.afterState);
|
|
3131
|
+
assertNoProtectedBytes(manifestBytes, protectedValues);
|
|
3132
|
+
assertNoProtectedBytes(memory.archive, protectedValues);
|
|
3133
|
+
const snapshot = agentCandidateWorkspaceSnapshotEvidenceSchema.parse({
|
|
3134
|
+
kind: "agent-candidate-workspace-snapshot",
|
|
3135
|
+
digest: sha256Bytes(manifestBytes),
|
|
3136
|
+
material: memory.afterState,
|
|
3137
|
+
manifest: embeddedCandidateArtifact(manifestBytes),
|
|
3138
|
+
archive: embeddedCandidateArtifact(memory.archive)
|
|
3139
|
+
});
|
|
3140
|
+
const root = await mkdtemp(join(tmpdir(), "agent-candidate-memory-after-"));
|
|
3141
|
+
try {
|
|
3142
|
+
await state.ports.workspaces.materialize({
|
|
3143
|
+
role: "memory",
|
|
3144
|
+
snapshot,
|
|
3145
|
+
archive: Uint8Array.from(memory.archive),
|
|
3146
|
+
destination: root
|
|
3147
|
+
});
|
|
3148
|
+
const files = await readMaterializedWorkspaceFiles(root, memory.afterState);
|
|
3149
|
+
for (const file of files) assertNoProtectedBytes(file.bytes, protectedValues);
|
|
3150
|
+
signal?.throwIfAborted();
|
|
3151
|
+
} finally {
|
|
3152
|
+
await rm(root, {
|
|
3153
|
+
recursive: true,
|
|
3154
|
+
force: true
|
|
3155
|
+
});
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
function normalizeEvaluation(evaluation, termination) {
|
|
3159
|
+
if (!evaluation || typeof evaluation !== "object" || Array.isArray(evaluation)) throw new Error("candidate benchmark evaluation must be an object");
|
|
3160
|
+
assertUnitScore(evaluation.score, "candidate benchmark score");
|
|
3161
|
+
if (evaluation.passed !== void 0 && typeof evaluation.passed !== "boolean") throw new Error("candidate benchmark passed must be boolean");
|
|
3162
|
+
const cleanExit = termination.kind === "exit" && termination.exitCode === 0;
|
|
3163
|
+
const dimensions = Object.entries(evaluation.dimensions ?? {}).map(([name, score]) => {
|
|
3164
|
+
if (!/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/.test(name)) throw new Error(`candidate benchmark dimension is not normalized: ${name}`);
|
|
3165
|
+
assertUnitScore(score, `candidate benchmark dimension ${name}`);
|
|
3166
|
+
return {
|
|
3167
|
+
name,
|
|
3168
|
+
score: cleanExit ? score : 0
|
|
3169
|
+
};
|
|
3170
|
+
}).sort((left, right) => left.name.localeCompare(right.name));
|
|
3171
|
+
return {
|
|
3172
|
+
score: cleanExit ? evaluation.score : 0,
|
|
3173
|
+
passed: cleanExit && (evaluation.passed ?? evaluation.score > 0),
|
|
3174
|
+
dimensions
|
|
3175
|
+
};
|
|
3176
|
+
}
|
|
3177
|
+
function assertUnitScore(value, label) {
|
|
3178
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1) throw new Error(`${label} must be finite and within [0, 1]`);
|
|
3179
|
+
}
|
|
3180
|
+
//#endregion
|
|
3181
|
+
//#region src/candidate-execution/protected-trace-store.ts
|
|
3182
|
+
/** Keep the identity key in evaluator-owned closures and expose only the redacting store to code. */
|
|
3183
|
+
function createProtectedAgentCandidateTraceAccess(inner, protectedValues) {
|
|
3184
|
+
const redactor = createProtectedRecordRedactor(protectedValues);
|
|
3185
|
+
const pendingWrites = /* @__PURE__ */ new Set();
|
|
3186
|
+
let acceptsWrites = true;
|
|
3187
|
+
const write = (operation) => {
|
|
3188
|
+
if (!acceptsWrites) return Promise.reject(/* @__PURE__ */ new Error("candidate trace writes are closed"));
|
|
3189
|
+
let started;
|
|
3190
|
+
try {
|
|
3191
|
+
started = operation();
|
|
3192
|
+
} catch (error) {
|
|
3193
|
+
return Promise.reject(error);
|
|
3194
|
+
}
|
|
3195
|
+
let tracked;
|
|
3196
|
+
tracked = started.finally(() => pendingWrites.delete(tracked));
|
|
3197
|
+
pendingWrites.add(tracked);
|
|
3198
|
+
return tracked;
|
|
3199
|
+
};
|
|
3200
|
+
const store = Object.freeze({
|
|
3201
|
+
appendRun: (run) => write(() => inner.appendRun(redactor.record(run))),
|
|
3202
|
+
updateRun: (runId, patch) => write(() => inner.updateRun(redactor.identifier(runId), redactor.record(patch))),
|
|
3203
|
+
appendSpan: (span) => write(async () => {
|
|
3204
|
+
if (span.kind === "llm") throw new Error("candidate executors cannot author protected model spans");
|
|
3205
|
+
await inner.appendSpan(redactor.record(span));
|
|
3206
|
+
}),
|
|
3207
|
+
updateSpan: (spanId, patch) => write(async () => {
|
|
3208
|
+
if (patch.kind === "llm") throw new Error("candidate executors cannot author protected model spans");
|
|
3209
|
+
await inner.updateSpan(redactor.identifier(spanId), redactor.record(patch));
|
|
3210
|
+
}),
|
|
3211
|
+
appendEvent: (event) => write(() => inner.appendEvent(redactor.record(event))),
|
|
3212
|
+
appendArtifact: (artifact) => write(() => inner.appendArtifact(redactor.record(artifact))),
|
|
3213
|
+
appendBudgetEntry: (entry) => write(() => inner.appendBudgetEntry(redactor.record(entry))),
|
|
3214
|
+
getRun: async (runId) => inner.getRun(redactor.identifier(runId)),
|
|
3215
|
+
listRuns: async (filter) => inner.listRuns(filter ? redactor.query(filter) : void 0),
|
|
3216
|
+
spans: async (filter) => inner.spans(filter ? redactor.query(filter) : void 0),
|
|
3217
|
+
events: async (filter) => inner.events(filter ? redactor.query(filter) : void 0),
|
|
3218
|
+
budget: async (runId) => inner.budget(redactor.identifier(runId)),
|
|
3219
|
+
artifacts: async (runId) => inner.artifacts(redactor.identifier(runId))
|
|
3220
|
+
});
|
|
3221
|
+
return Object.freeze({
|
|
3222
|
+
store,
|
|
3223
|
+
closeWrites: async () => {
|
|
3224
|
+
acceptsWrites = false;
|
|
3225
|
+
await Promise.all([...pendingWrites]);
|
|
3226
|
+
},
|
|
3227
|
+
identity: (trace) => redactor.traceIdentity(trace),
|
|
3228
|
+
report: () => redactor.report()
|
|
3229
|
+
});
|
|
3230
|
+
}
|
|
3231
|
+
/** Recovery can read existing trace state but must never accept new unredactable writes. */
|
|
3232
|
+
var RecoveryAgentCandidateTraceStore = class {
|
|
3233
|
+
inner;
|
|
3234
|
+
constructor(inner) {
|
|
3235
|
+
this.inner = inner;
|
|
3236
|
+
}
|
|
3237
|
+
appendRun() {
|
|
3238
|
+
return this.rejectWrite();
|
|
3239
|
+
}
|
|
3240
|
+
updateRun() {
|
|
3241
|
+
return this.rejectWrite();
|
|
3242
|
+
}
|
|
3243
|
+
appendSpan() {
|
|
3244
|
+
return this.rejectWrite();
|
|
3245
|
+
}
|
|
3246
|
+
updateSpan() {
|
|
3247
|
+
return this.rejectWrite();
|
|
3248
|
+
}
|
|
3249
|
+
appendEvent() {
|
|
3250
|
+
return this.rejectWrite();
|
|
3251
|
+
}
|
|
3252
|
+
appendArtifact() {
|
|
3253
|
+
return this.rejectWrite();
|
|
3254
|
+
}
|
|
3255
|
+
appendBudgetEntry() {
|
|
3256
|
+
return this.rejectWrite();
|
|
3257
|
+
}
|
|
3258
|
+
getRun(...args) {
|
|
3259
|
+
return this.inner.getRun(...args);
|
|
3260
|
+
}
|
|
3261
|
+
listRuns(...args) {
|
|
3262
|
+
return this.inner.listRuns(...args);
|
|
3263
|
+
}
|
|
3264
|
+
spans(...args) {
|
|
3265
|
+
return this.inner.spans(...args);
|
|
3266
|
+
}
|
|
3267
|
+
events(...args) {
|
|
3268
|
+
return this.inner.events(...args);
|
|
3269
|
+
}
|
|
3270
|
+
budget(...args) {
|
|
3271
|
+
return this.inner.budget(...args);
|
|
3272
|
+
}
|
|
3273
|
+
artifacts(...args) {
|
|
3274
|
+
return this.inner.artifacts(...args);
|
|
3275
|
+
}
|
|
3276
|
+
rejectWrite() {
|
|
3277
|
+
return Promise.reject(/* @__PURE__ */ new Error("expired candidate recovery cannot append trace evidence"));
|
|
3278
|
+
}
|
|
3279
|
+
};
|
|
3280
|
+
//#endregion
|
|
3281
|
+
//#region src/candidate-execution/execute.ts
|
|
3282
|
+
/** Executes and finalizes one durably claimed candidate without exposing an unproven result. */
|
|
3283
|
+
async function executePreparedAgentCandidate(prepared, options) {
|
|
3284
|
+
const initialState = assertPreparedCandidateIntegrity(prepared);
|
|
3285
|
+
const cleanupTimeoutMs = candidateCleanupTimeout(options.cleanupTimeoutMs ?? initialState.cleanupTimeoutMs);
|
|
3286
|
+
if (cleanupTimeoutMs > initialState.cleanupTimeoutMs) throw new Error("execution cleanup timeout exceeds the frozen preparation bound");
|
|
3287
|
+
const resultTimeoutMs = candidateResultTimeout(options.resultTimeoutMs ?? initialState.resultTimeoutMs, initialState.resultTimeoutMs);
|
|
3288
|
+
if (resultTimeoutMs > initialState.resultTimeoutMs) throw new Error("execution result timeout exceeds the frozen preparation bound");
|
|
3289
|
+
let state;
|
|
3290
|
+
try {
|
|
3291
|
+
state = beginPreparedCandidateClaim(prepared);
|
|
3292
|
+
} catch (error) {
|
|
3293
|
+
return failedAgentCandidateRun(initialState, errorMessage$1(error));
|
|
3294
|
+
}
|
|
3295
|
+
try {
|
|
3296
|
+
await assertPreparedCandidateWorkspaces(state);
|
|
3297
|
+
} catch (error) {
|
|
3298
|
+
return await failBeforeActivation(prepared, state, error, "failed", cleanupTimeoutMs);
|
|
3299
|
+
}
|
|
3300
|
+
let preparationEvidence;
|
|
3301
|
+
try {
|
|
3302
|
+
const [executionPlan, materializationReceipt] = await Promise.all([persistCandidateOutputArtifact(options.outputArtifacts, {
|
|
3303
|
+
executionId: state.executionId,
|
|
3304
|
+
purpose: "execution-plan",
|
|
3305
|
+
bytes: state.executionPlan.bytes
|
|
3306
|
+
}), persistCandidateOutputArtifact(options.outputArtifacts, {
|
|
3307
|
+
executionId: state.executionId,
|
|
3308
|
+
purpose: "materialization-receipt",
|
|
3309
|
+
bytes: state.materializationReceipt.bytes
|
|
3310
|
+
})]);
|
|
3311
|
+
preparationEvidence = Object.freeze({
|
|
3312
|
+
executionPlan,
|
|
3313
|
+
materializationReceipt
|
|
3314
|
+
});
|
|
3315
|
+
} catch (error) {
|
|
3316
|
+
return await failBeforeActivation(prepared, state, error, "failed", cleanupTimeoutMs);
|
|
3317
|
+
}
|
|
3318
|
+
let acquired;
|
|
3319
|
+
try {
|
|
3320
|
+
acquired = await options.claimStore.tryClaim(candidateExecutionClaim(prepared, preparationEvidence));
|
|
3321
|
+
} catch (error) {
|
|
3322
|
+
return await failBeforeActivation(prepared, state, error, "failed", cleanupTimeoutMs);
|
|
3323
|
+
}
|
|
3324
|
+
if (!acquired.acquired) return await failBeforeActivation(prepared, state, /* @__PURE__ */ new Error(acquired.reason === "retry-not-eligible" ? `candidate execution retry is not eligible: ${acquired.detail}` : "candidate execution attempt is already claimed"), "replayed", cleanupTimeoutMs);
|
|
3325
|
+
markPreparedCandidateClaimed(prepared);
|
|
3326
|
+
const postRunWindowMs = candidatePostRunWindowMs(cleanupTimeoutMs, resultTimeoutMs);
|
|
3327
|
+
const deadlineAtMs = acquired.claim.leaseExpiresAtMs - candidatePostRunWindowMs(state.cleanupTimeoutMs, state.resultTimeoutMs);
|
|
3328
|
+
const requiredLeaseExpiry = deadlineAtMs + postRunWindowMs;
|
|
3329
|
+
if (Date.now() >= deadlineAtMs || deadlineAtMs > state.reservationExpiresAtMs || requiredLeaseExpiry > acquired.lease.expiresAtMs) return await failClaimedExecution(prepared, state, acquired.lease, options.claimStore, options.outputArtifacts, /* @__PURE__ */ new Error("candidate claim no longer covers its full execution and cleanup window"), "failed", cleanupTimeoutMs, "pre-model-infrastructure");
|
|
3330
|
+
let activation;
|
|
3331
|
+
try {
|
|
3332
|
+
const activated = await withinCandidateCleanupDeadline(() => state.ports.models.activateGrant({
|
|
3333
|
+
executionId: state.executionId,
|
|
3334
|
+
preparationId: state.preparationId,
|
|
3335
|
+
grantDigest: state.modelReservation.digest,
|
|
3336
|
+
resolved: state.resolvedModel,
|
|
3337
|
+
deadlineAtMs
|
|
3338
|
+
}), Math.min(deadlineAtMs, candidateCleanupDeadline(cleanupTimeoutMs)), "protected model activation");
|
|
3339
|
+
activation = Object.freeze({ env: Object.freeze({ ...activated.env }) });
|
|
3340
|
+
} catch (error) {
|
|
3341
|
+
return await failClaimedExecution(prepared, state, acquired.lease, options.claimStore, options.outputArtifacts, new Error("protected model activation failed", { cause: error }), "failed", cleanupTimeoutMs, "pre-model-infrastructure");
|
|
3342
|
+
}
|
|
3343
|
+
let memoryActivation;
|
|
3344
|
+
if (state.memory.mode === "isolated") try {
|
|
3345
|
+
const reservation = state.memoryReservation;
|
|
3346
|
+
if (!reservation) throw new Error("isolated memory reservation is missing");
|
|
3347
|
+
const activated = await withinCandidateCleanupDeadline(() => state.ports.memory.activate({
|
|
3348
|
+
executionId: state.executionId,
|
|
3349
|
+
preparationId: reservation.preparationId,
|
|
3350
|
+
accessDigest: reservation.accessDigest,
|
|
3351
|
+
effectiveNamespace: reservation.effectiveNamespace,
|
|
3352
|
+
deadlineAtMs
|
|
3353
|
+
}), Math.min(deadlineAtMs, candidateCleanupDeadline(cleanupTimeoutMs)), "isolated memory activation");
|
|
3354
|
+
memoryActivation = Object.freeze({ env: Object.freeze({ ...activated.env }) });
|
|
3355
|
+
} catch (error) {
|
|
3356
|
+
return await failClaimedExecution(prepared, state, acquired.lease, options.claimStore, options.outputArtifacts, new Error("isolated memory activation failed", { cause: error }), "failed", cleanupTimeoutMs, "pre-model-infrastructure", activation);
|
|
3357
|
+
}
|
|
3358
|
+
let request;
|
|
3359
|
+
try {
|
|
3360
|
+
request = beginPreparedCandidateRun(prepared, activation, memoryActivation).request;
|
|
3361
|
+
} catch (error) {
|
|
3362
|
+
return await failClaimedExecution(prepared, state, acquired.lease, options.claimStore, options.outputArtifacts, error, "failed", cleanupTimeoutMs, "pre-model-infrastructure", activation, memoryActivation);
|
|
3363
|
+
}
|
|
3364
|
+
try {
|
|
3365
|
+
if ((await options.claimStore.markCandidateMayRun(acquired.lease)).phase !== "candidate-may-run") throw new Error("candidate claim did not persist the candidate-may-run phase");
|
|
3366
|
+
} catch (error) {
|
|
3367
|
+
return await failClaimedExecution(prepared, state, acquired.lease, options.claimStore, options.outputArtifacts, new Error("candidate execution phase persistence failed", { cause: error }), "failed", cleanupTimeoutMs, "pre-model-infrastructure", activation, memoryActivation);
|
|
3368
|
+
}
|
|
3369
|
+
if (Date.now() >= deadlineAtMs) return await failClaimedExecution(prepared, state, acquired.lease, options.claimStore, options.outputArtifacts, /* @__PURE__ */ new Error("candidate execution deadline elapsed while persisting its launch phase"), "failed", cleanupTimeoutMs, "unknown", activation, memoryActivation);
|
|
3370
|
+
const protectedValues = protectedEnvironmentValues(activation, memoryActivation);
|
|
3371
|
+
const protectedTrace = createProtectedAgentCandidateTraceAccess(options.traceStore, protectedValues);
|
|
3372
|
+
const persistedTrace = protectedTrace.identity(state.trace);
|
|
3373
|
+
const execution = await runAndStopExecutor(options.executor, request, state.benchmarkTask.outcome, protectedTrace.store, deadlineAtMs, cleanupTimeoutMs);
|
|
3374
|
+
beginPreparedCandidateSettlement(prepared);
|
|
3375
|
+
const cleanupDeadlineAtMs = candidateCleanupDeadline(cleanupTimeoutMs);
|
|
3376
|
+
const accessReason = execution.kind === "timeout" || execution.termination?.kind === "timeout" ? "timeout" : execution.kind === "capture" ? "completed" : "failed";
|
|
3377
|
+
const traceClosure = withinCandidateCleanupDeadline(() => protectedTrace.closeWrites(), cleanupDeadlineAtMs, "candidate trace write closure").then(() => ({
|
|
3378
|
+
closed: true,
|
|
3379
|
+
error: void 0
|
|
3380
|
+
}), (error) => ({
|
|
3381
|
+
closed: false,
|
|
3382
|
+
error
|
|
3383
|
+
}));
|
|
3384
|
+
const [traceClose, memoryClose, settlementResult] = await Promise.all([
|
|
3385
|
+
traceClosure,
|
|
3386
|
+
closeMemoryAccess(state, accessReason, cleanupDeadlineAtMs),
|
|
3387
|
+
settleModelGrant(state, accessReason, cleanupDeadlineAtMs)
|
|
3388
|
+
]);
|
|
3389
|
+
if (!execution.processStopped || !traceClose.closed || !settlementResult.settlement || !memoryClose.closed) {
|
|
3390
|
+
consumePreparedCandidateExecution(prepared, "failed");
|
|
3391
|
+
return failedAgentCandidateRun(state, redactProtectedReason(joinErrors(execution.error, !execution.processStopped ? /* @__PURE__ */ new Error("candidate process termination is not proven") : void 0, traceClose.error, memoryClose.error, settlementResult.error ?? (!settlementResult.settlement ? /* @__PURE__ */ new Error("model settlement failed") : void 0), /* @__PURE__ */ new Error("candidate claim remains recoverable until protected cleanup is proven")), protectedValues), execution.termination, settlementResult.settlement?.usage ?? null);
|
|
3392
|
+
}
|
|
3393
|
+
let modelSettlement;
|
|
3394
|
+
try {
|
|
3395
|
+
modelSettlement = await withinCandidateCleanupDeadline(() => persistCandidateModelSettlement(state, settlementResult.settlement, options.outputArtifacts), candidateCleanupDeadline(cleanupTimeoutMs), "candidate model settlement persistence");
|
|
3396
|
+
} catch (error) {
|
|
3397
|
+
consumePreparedCandidateExecution(prepared, "failed");
|
|
3398
|
+
return failedAgentCandidateRun(state, redactProtectedReason(joinErrors(error, /* @__PURE__ */ new Error("candidate claim remains recoverable until settlement evidence is durable")), protectedValues), execution.termination, settlementResult.settlement.usage);
|
|
3399
|
+
}
|
|
3400
|
+
let result;
|
|
3401
|
+
const failureClass = execution.kind === "error" ? "execution" : "post-model-infrastructure";
|
|
3402
|
+
if (execution.kind === "error") result = failedAgentCandidateRun(state, redactProtectedReason(errorMessage$1(execution.error), protectedValues), execution.termination, settlementResult.settlement.usage);
|
|
3403
|
+
else if (!execution.finalCapture.taskOutcome) result = failedAgentCandidateRun(state, "candidate executor stopped without a captured task outcome", execution.termination, settlementResult.settlement.usage);
|
|
3404
|
+
else {
|
|
3405
|
+
const capture = execution.kind === "capture" ? execution.capture : {
|
|
3406
|
+
executionId: state.executionId,
|
|
3407
|
+
termination: execution.termination
|
|
3408
|
+
};
|
|
3409
|
+
try {
|
|
3410
|
+
result = await withinCandidateResultDeadline(async (signal) => {
|
|
3411
|
+
await appendAuthoritativeModelSettlementSpans(options.traceStore, persistedTrace.runId, settlementResult.settlement);
|
|
3412
|
+
const { persistedCapture, taskOutcome } = await persistVerifiedAgentCandidateExecutorCapture(state, execution.finalCapture, options.outputArtifacts, protectedValues, signal);
|
|
3413
|
+
const benchmarkResult = await persistCandidateBenchmarkResult(state, capture.termination, taskOutcome, options.grader, options.outputArtifacts, protectedValues, signal);
|
|
3414
|
+
return await finalizeAgentCandidateRun(state, capture, options.traceStore, persistedTrace, settlementResult.settlement, {
|
|
3415
|
+
finalCapture: execution.finalCapture,
|
|
3416
|
+
persistedCapture,
|
|
3417
|
+
modelSettlement,
|
|
3418
|
+
taskOutcome,
|
|
3419
|
+
benchmarkResult,
|
|
3420
|
+
outputArtifacts: options.outputArtifacts
|
|
3421
|
+
}, protectedValues, protectedTrace.report(), signal);
|
|
3422
|
+
}, Math.min(Date.now() + resultTimeoutMs, acquired.lease.expiresAtMs - candidateTerminalWindowMs(cleanupTimeoutMs)), "candidate evidence finalization");
|
|
3423
|
+
} catch (error) {
|
|
3424
|
+
result = failedAgentCandidateRun(state, redactProtectedReason(errorMessage$1(error), protectedValues), execution.termination, settlementResult.settlement.usage);
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
let terminal;
|
|
3428
|
+
try {
|
|
3429
|
+
terminal = result.succeeded ? {
|
|
3430
|
+
status: "succeeded",
|
|
3431
|
+
usage: settlementResult.settlement.usage,
|
|
3432
|
+
modelSettlement: result.artifacts.modelSettlement,
|
|
3433
|
+
taskOutcome: result.artifacts.taskOutcome,
|
|
3434
|
+
benchmarkResult: result.artifacts.benchmarkResult,
|
|
3435
|
+
runReceipt: result.artifacts.runReceipt
|
|
3436
|
+
} : {
|
|
3437
|
+
status: "failed",
|
|
3438
|
+
failureClass,
|
|
3439
|
+
usage: settlementResult.settlement.usage,
|
|
3440
|
+
modelSettlement: modelSettlement.artifact,
|
|
3441
|
+
failureEvidence: await withinCandidateCleanupDeadline(() => persistFailureEvidence(state, result.reason, failureClass, execution.termination, options.outputArtifacts), acquired.lease.expiresAtMs, "candidate failure-evidence persistence")
|
|
3442
|
+
};
|
|
3443
|
+
} catch (error) {
|
|
3444
|
+
consumePreparedCandidateExecution(prepared, "failed");
|
|
3445
|
+
return failedAgentCandidateRun(state, redactProtectedReason(joinErrors(error, /* @__PURE__ */ new Error("candidate claim remains recoverable until terminal evidence is durable")), protectedValues), execution.termination, settlementResult.settlement.usage);
|
|
3446
|
+
}
|
|
3447
|
+
if (await finishClaim(options.claimStore, acquired.lease, terminal, acquired.lease.expiresAtMs)) {
|
|
3448
|
+
consumePreparedCandidateExecution(prepared, result.succeeded ? "succeeded" : "failed");
|
|
3449
|
+
return result;
|
|
3450
|
+
}
|
|
3451
|
+
consumePreparedCandidateExecution(prepared, "failed");
|
|
3452
|
+
return failedAgentCandidateRun(state, "candidate execution terminal record could not be persisted", execution.termination, settlementResult.settlement.usage);
|
|
3453
|
+
}
|
|
3454
|
+
async function runAndStopExecutor(executor, request, expectedOutcome, traceStore, deadlineAtMs, cleanupTimeoutMs) {
|
|
3455
|
+
const timeoutMs = request.hardLimits.timeoutMs;
|
|
3456
|
+
const timeoutError = new CandidateExecutionDeadlineError(timeoutMs);
|
|
3457
|
+
if (Date.now() >= deadlineAtMs) return {
|
|
3458
|
+
kind: "error",
|
|
3459
|
+
error: timeoutError,
|
|
3460
|
+
termination: {
|
|
3461
|
+
kind: "timeout",
|
|
3462
|
+
timeoutMs
|
|
3463
|
+
},
|
|
3464
|
+
processStopped: true
|
|
3465
|
+
};
|
|
3466
|
+
const controller = new AbortController();
|
|
3467
|
+
let timer;
|
|
3468
|
+
let timedOut = false;
|
|
3469
|
+
let capture;
|
|
3470
|
+
let executionError;
|
|
3471
|
+
const executionPromise = Promise.resolve().then(() => executor.execute(request, {
|
|
3472
|
+
traceStore,
|
|
3473
|
+
signal: controller.signal,
|
|
3474
|
+
deadlineAtMs
|
|
3475
|
+
}));
|
|
3476
|
+
executionPromise.catch(() => void 0);
|
|
3477
|
+
const deadlinePromise = new Promise((_resolve, reject) => {
|
|
3478
|
+
timer = setTimeout(() => {
|
|
3479
|
+
timedOut = true;
|
|
3480
|
+
controller.abort(timeoutError);
|
|
3481
|
+
reject(timeoutError);
|
|
3482
|
+
}, Math.max(0, deadlineAtMs - Date.now()));
|
|
3483
|
+
});
|
|
3484
|
+
deadlinePromise.catch(() => void 0);
|
|
3485
|
+
try {
|
|
3486
|
+
capture = sealAgentCandidateProtectedRunCapture(await Promise.race([executionPromise, deadlinePromise]));
|
|
3487
|
+
} catch (error) {
|
|
3488
|
+
executionError = error;
|
|
3489
|
+
}
|
|
3490
|
+
if (!timedOut && capture && capture.executionId !== request.executionId) {
|
|
3491
|
+
executionError = /* @__PURE__ */ new Error("candidate execution capture id does not match the request");
|
|
3492
|
+
capture = void 0;
|
|
3493
|
+
} else if (!timedOut && capture && Date.now() >= deadlineAtMs) {
|
|
3494
|
+
timedOut = true;
|
|
3495
|
+
executionError = timeoutError;
|
|
3496
|
+
capture = void 0;
|
|
3497
|
+
} else if (!timedOut && capture?.termination.kind === "timeout") {
|
|
3498
|
+
executionError = /* @__PURE__ */ new Error("candidate executor cannot declare the runtime-owned timeout");
|
|
3499
|
+
capture = void 0;
|
|
3500
|
+
}
|
|
3501
|
+
if (!capture || timedOut) controller.abort(executionError);
|
|
3502
|
+
const stopReason = timedOut ? "timeout" : capture ? "completed" : "failed";
|
|
3503
|
+
const cleanupDeadlineAtMs = Date.now() + cleanupTimeoutMs;
|
|
3504
|
+
try {
|
|
3505
|
+
sealAgentCandidateExecutorStopAcknowledgement(await withinCandidateCleanupDeadline((cleanupSignal) => executor.stop({
|
|
3506
|
+
executionId: request.executionId,
|
|
3507
|
+
executionPlanDigest: request.executionPlan.value.digest
|
|
3508
|
+
}, {
|
|
3509
|
+
traceStore,
|
|
3510
|
+
reason: stopReason,
|
|
3511
|
+
signal: cleanupSignal,
|
|
3512
|
+
deadlineAtMs: cleanupDeadlineAtMs
|
|
3513
|
+
}), cleanupDeadlineAtMs, "candidate process termination"));
|
|
3514
|
+
} catch (stopError) {
|
|
3515
|
+
let disposalError;
|
|
3516
|
+
try {
|
|
3517
|
+
await disposeExecutor(executor, request, cleanupDeadlineAtMs);
|
|
3518
|
+
} catch (error) {
|
|
3519
|
+
disposalError = error;
|
|
3520
|
+
}
|
|
3521
|
+
if (timer) clearTimeout(timer);
|
|
3522
|
+
controller.abort(stopError);
|
|
3523
|
+
return {
|
|
3524
|
+
kind: "error",
|
|
3525
|
+
error: new Error(joinErrors(executionError, stopError, disposalError)),
|
|
3526
|
+
...timedOut ? { termination: {
|
|
3527
|
+
kind: "timeout",
|
|
3528
|
+
timeoutMs
|
|
3529
|
+
} } : {},
|
|
3530
|
+
processStopped: false
|
|
3531
|
+
};
|
|
3532
|
+
}
|
|
3533
|
+
let finalCapture;
|
|
3534
|
+
let finalCaptureError;
|
|
3535
|
+
try {
|
|
3536
|
+
finalCapture = sealAgentCandidateExecutorFinalCapture(await withinCandidateCleanupDeadline((cleanupSignal) => executor.capture({
|
|
3537
|
+
executionId: request.executionId,
|
|
3538
|
+
executionPlanDigest: request.executionPlan.value.digest
|
|
3539
|
+
}, {
|
|
3540
|
+
traceStore,
|
|
3541
|
+
signal: cleanupSignal
|
|
3542
|
+
}), cleanupDeadlineAtMs, "candidate final evidence capture"), expectedOutcome);
|
|
3543
|
+
} catch (captureError) {
|
|
3544
|
+
finalCaptureError = captureError;
|
|
3545
|
+
}
|
|
3546
|
+
let disposalError;
|
|
3547
|
+
try {
|
|
3548
|
+
await disposeExecutor(executor, request, cleanupDeadlineAtMs);
|
|
3549
|
+
} catch (disposeError) {
|
|
3550
|
+
disposalError = disposeError;
|
|
3551
|
+
}
|
|
3552
|
+
if (finalCaptureError || disposalError || !finalCapture) {
|
|
3553
|
+
if (timer) clearTimeout(timer);
|
|
3554
|
+
controller.abort(finalCaptureError ?? disposalError);
|
|
3555
|
+
return {
|
|
3556
|
+
kind: "error",
|
|
3557
|
+
error: new Error(joinErrors(executionError, finalCaptureError, disposalError)),
|
|
3558
|
+
...timedOut ? { termination: {
|
|
3559
|
+
kind: "timeout",
|
|
3560
|
+
timeoutMs
|
|
3561
|
+
} } : {},
|
|
3562
|
+
processStopped: true
|
|
3563
|
+
};
|
|
3564
|
+
}
|
|
3565
|
+
if (Date.now() >= deadlineAtMs) {
|
|
3566
|
+
timedOut = true;
|
|
3567
|
+
executionError = timeoutError;
|
|
3568
|
+
capture = void 0;
|
|
3569
|
+
controller.abort(timeoutError);
|
|
3570
|
+
}
|
|
3571
|
+
if (timer) clearTimeout(timer);
|
|
3572
|
+
if (timedOut) return {
|
|
3573
|
+
kind: "timeout",
|
|
3574
|
+
termination: {
|
|
3575
|
+
kind: "timeout",
|
|
3576
|
+
timeoutMs
|
|
3577
|
+
},
|
|
3578
|
+
finalCapture,
|
|
3579
|
+
processStopped: true
|
|
3580
|
+
};
|
|
3581
|
+
if (executionError || !capture) return {
|
|
3582
|
+
kind: "error",
|
|
3583
|
+
error: executionError ?? /* @__PURE__ */ new Error("candidate executor returned no capture"),
|
|
3584
|
+
finalCapture,
|
|
3585
|
+
processStopped: true
|
|
3586
|
+
};
|
|
3587
|
+
return {
|
|
3588
|
+
kind: "capture",
|
|
3589
|
+
capture,
|
|
3590
|
+
termination: capture.termination,
|
|
3591
|
+
finalCapture,
|
|
3592
|
+
processStopped: true
|
|
3593
|
+
};
|
|
3594
|
+
}
|
|
3595
|
+
async function disposeExecutor(executor, request, cleanupDeadlineAtMs) {
|
|
3596
|
+
const dispose = executor.dispose;
|
|
3597
|
+
if (!dispose) return;
|
|
3598
|
+
const disposed = await withinCandidateCleanupDeadline((cleanupSignal) => dispose.call(executor, {
|
|
3599
|
+
executionId: request.executionId,
|
|
3600
|
+
executionPlanDigest: request.executionPlan.value.digest
|
|
3601
|
+
}, { signal: cleanupSignal }), cleanupDeadlineAtMs, "candidate execution resource disposal");
|
|
3602
|
+
if (disposed?.disposed !== true || Object.keys(disposed).some((key) => key !== "disposed")) throw new Error("candidate executor did not acknowledge resource disposal");
|
|
3603
|
+
}
|
|
3604
|
+
async function failBeforeActivation(prepared, state, error, reason, cleanupTimeoutMs) {
|
|
3605
|
+
beginPreparedCandidateSettlement(prepared);
|
|
3606
|
+
const cleanupDeadlineAtMs = candidateCleanupDeadline(cleanupTimeoutMs);
|
|
3607
|
+
const [memoryClose, settled] = await Promise.all([closeMemoryAccess(state, reason, cleanupDeadlineAtMs), settleModelGrant(state, reason, cleanupDeadlineAtMs)]);
|
|
3608
|
+
const cleanupProven = memoryClose.closed && settled.settlement !== void 0;
|
|
3609
|
+
consumePreparedCandidateExecution(prepared, cleanupProven ? "failed" : "cleanup-failed");
|
|
3610
|
+
return failedAgentCandidateRun(state, redactProtectedReason(joinErrors(error, memoryClose.error, settled.error, !cleanupProven ? /* @__PURE__ */ new Error("prepared access cleanup remains incomplete and may be retried by disposal") : void 0), []), void 0, settled.settlement?.usage ?? null);
|
|
3611
|
+
}
|
|
3612
|
+
async function failClaimedExecution(prepared, state, lease, claimStore, outputArtifacts, error, reason, cleanupTimeoutMs, failureClass, activation, memoryActivation) {
|
|
3613
|
+
beginPreparedCandidateSettlement(prepared);
|
|
3614
|
+
const cleanupDeadlineAtMs = candidateCleanupDeadline(cleanupTimeoutMs);
|
|
3615
|
+
const [memoryClose, settled] = await Promise.all([closeMemoryAccess(state, reason, cleanupDeadlineAtMs), settleModelGrant(state, reason, cleanupDeadlineAtMs)]);
|
|
3616
|
+
const protectedValues = protectedEnvironmentValues(activation, memoryActivation);
|
|
3617
|
+
const safeReason = redactProtectedReason(joinErrors(error, memoryClose.error, settled.error), protectedValues);
|
|
3618
|
+
let finishFailed = false;
|
|
3619
|
+
let persistenceFailure;
|
|
3620
|
+
if (settled.settlement && memoryClose.closed) try {
|
|
3621
|
+
const modelSettlement = await withinCandidateCleanupDeadline(() => persistCandidateModelSettlement(state, settled.settlement, outputArtifacts), lease.expiresAtMs, "candidate pre-run model-settlement persistence");
|
|
3622
|
+
const failureEvidence = await withinCandidateCleanupDeadline(() => persistFailureEvidence(state, safeReason, failureClass, void 0, outputArtifacts), lease.expiresAtMs, "candidate pre-run failure-evidence persistence");
|
|
3623
|
+
finishFailed = !await finishClaim(claimStore, lease, {
|
|
3624
|
+
status: "failed",
|
|
3625
|
+
failureClass,
|
|
3626
|
+
usage: settled.settlement.usage,
|
|
3627
|
+
modelSettlement: modelSettlement.artifact,
|
|
3628
|
+
failureEvidence
|
|
3629
|
+
}, lease.expiresAtMs);
|
|
3630
|
+
} catch (persistenceError) {
|
|
3631
|
+
finishFailed = true;
|
|
3632
|
+
persistenceFailure = persistenceError;
|
|
3633
|
+
}
|
|
3634
|
+
consumePreparedCandidateExecution(prepared, "failed");
|
|
3635
|
+
return failedAgentCandidateRun(state, redactProtectedReason(joinErrors(safeReason, persistenceFailure, memoryClose.error, settled.error, !memoryClose.closed || !settled.settlement ? /* @__PURE__ */ new Error("candidate claim remains recoverable until protected cleanup is proven") : void 0, finishFailed ? /* @__PURE__ */ new Error("candidate execution terminal record could not be persisted") : void 0), protectedValues), void 0, settled.settlement?.usage ?? null);
|
|
3636
|
+
}
|
|
3637
|
+
async function closeMemoryAccess(state, reason, cleanupDeadlineAtMs) {
|
|
3638
|
+
if (state.memory.mode === "disabled") return { closed: true };
|
|
3639
|
+
try {
|
|
3640
|
+
const reservation = state.memoryReservation;
|
|
3641
|
+
if (!reservation) throw new Error("isolated memory reservation is missing");
|
|
3642
|
+
const closed = await withinCandidateCleanupDeadline(() => state.ports.memory.close({
|
|
3643
|
+
executionId: state.executionId,
|
|
3644
|
+
preparationId: reservation.preparationId,
|
|
3645
|
+
accessDigest: reservation.accessDigest,
|
|
3646
|
+
effectiveNamespace: reservation.effectiveNamespace,
|
|
3647
|
+
reason
|
|
3648
|
+
}), cleanupDeadlineAtMs, "isolated memory closure");
|
|
3649
|
+
if (closed.closed !== true || Object.keys(closed).some((key) => key !== "closed")) throw new Error("isolated memory access did not acknowledge closure");
|
|
3650
|
+
return { closed: true };
|
|
3651
|
+
} catch (error) {
|
|
3652
|
+
return {
|
|
3653
|
+
closed: false,
|
|
3654
|
+
error
|
|
3655
|
+
};
|
|
3656
|
+
}
|
|
3657
|
+
}
|
|
3658
|
+
async function settleModelGrant(state, reason, cleanupDeadlineAtMs) {
|
|
3659
|
+
try {
|
|
3660
|
+
return { settlement: sealAgentCandidateModelSettlement(await withinCandidateCleanupDeadline(() => state.ports.models.settleGrant({
|
|
3661
|
+
executionId: state.executionId,
|
|
3662
|
+
preparationId: state.preparationId,
|
|
3663
|
+
grantDigest: state.modelReservation.digest,
|
|
3664
|
+
resolved: state.resolvedModel,
|
|
3665
|
+
reason
|
|
3666
|
+
}), cleanupDeadlineAtMs, "protected model settlement"), {
|
|
3667
|
+
preparationId: state.preparationId,
|
|
3668
|
+
grantDigest: state.modelReservation.digest,
|
|
3669
|
+
model: state.resolvedModel.model
|
|
3670
|
+
}) };
|
|
3671
|
+
} catch (error) {
|
|
3672
|
+
return { error: new Error("protected model settlement failed", { cause: error }) };
|
|
3673
|
+
}
|
|
3674
|
+
}
|
|
3675
|
+
async function finishClaim(store, lease, terminal, deadlineAtMs) {
|
|
3676
|
+
try {
|
|
3677
|
+
const staged = await withinCandidateCleanupDeadline(() => store.stageTerminal(lease, terminal), deadlineAtMs ?? lease.expiresAtMs, "candidate terminal staging");
|
|
3678
|
+
if (!staged.staged && !staged.exactReplay) return false;
|
|
3679
|
+
const result = await withinCandidateCleanupDeadline(() => store.finish(lease, staged.terminal.terminalDigest), deadlineAtMs ?? lease.expiresAtMs, "candidate terminal publication");
|
|
3680
|
+
return result.finished || result.exactReplay;
|
|
3681
|
+
} catch {
|
|
3682
|
+
return false;
|
|
3683
|
+
}
|
|
3684
|
+
}
|
|
3685
|
+
async function persistFailureEvidence(state, reason, failureClass, termination, outputArtifacts) {
|
|
3686
|
+
const bytes = canonicalCandidateBytes({
|
|
3687
|
+
kind: "agent-candidate-execution-failure",
|
|
3688
|
+
executionId: state.executionId,
|
|
3689
|
+
bundleDigest: state.bundle.digest,
|
|
3690
|
+
executionPlanDigest: state.executionPlan.value.digest,
|
|
3691
|
+
failureClass,
|
|
3692
|
+
reason,
|
|
3693
|
+
...termination ? { termination } : {}
|
|
3694
|
+
});
|
|
3695
|
+
return await persistCandidateOutputArtifact(outputArtifacts, {
|
|
3696
|
+
executionId: state.executionId,
|
|
3697
|
+
purpose: "failure-evidence",
|
|
3698
|
+
bytes
|
|
3699
|
+
});
|
|
3700
|
+
}
|
|
3701
|
+
function protectedEnvironmentValues(activation, memoryActivation) {
|
|
3702
|
+
return [...Object.values(activation?.env ?? {}), ...Object.values(memoryActivation?.env ?? {})];
|
|
3703
|
+
}
|
|
3704
|
+
function joinErrors(...errors) {
|
|
3705
|
+
return errors.filter((error) => error !== void 0).map(errorMessage$1).join("; ");
|
|
3706
|
+
}
|
|
3707
|
+
function errorMessage$1(error) {
|
|
3708
|
+
return error instanceof Error ? error.message : String(error);
|
|
3709
|
+
}
|
|
3710
|
+
var CandidateExecutionDeadlineError = class extends Error {
|
|
3711
|
+
constructor(timeoutMs) {
|
|
3712
|
+
super(`candidate execution reached its frozen ${timeoutMs}ms deadline`);
|
|
3713
|
+
this.name = "CandidateExecutionDeadlineError";
|
|
3714
|
+
}
|
|
3715
|
+
};
|
|
3716
|
+
//#endregion
|
|
3717
|
+
//#region src/candidate-execution/prepare.ts
|
|
3718
|
+
const MIN_RESERVATION_TTL_MS = 15 * 6e4;
|
|
3719
|
+
const PREPARED_HOLD_MARGIN_MS = 5 * 6e4;
|
|
3720
|
+
/** Materializes a verified candidate into one immutable evaluator-owned execution plan. */
|
|
3721
|
+
async function prepareAgentCandidateExecution(candidate, task, ports, options = {}) {
|
|
3722
|
+
const cleanupTimeoutMs = candidateCleanupTimeout(options.cleanupTimeoutMs);
|
|
3723
|
+
assertSameVerificationPorts(getVerifiedCandidateState(candidate).ports, ports);
|
|
3724
|
+
const bundle = candidate.bundle;
|
|
3725
|
+
if (task.runCell.bundleDigest !== bundle.digest) throw new Error("candidate experiment cell does not bind the verified bundle");
|
|
3726
|
+
const benchmarkTask = task.task;
|
|
3727
|
+
const attempt = {
|
|
3728
|
+
number: task.runCell.attempt,
|
|
3729
|
+
maxAttempts: benchmarkTask.attempt.maxAttempts,
|
|
3730
|
+
retryPolicy: benchmarkTask.attempt.retryPolicy
|
|
3731
|
+
};
|
|
3732
|
+
const harness = candidateMaterializerHarness(bundle.execution.harness);
|
|
3733
|
+
assertTaskInput(task, bundle.execution.instructionDelivery);
|
|
3734
|
+
const resultTimeoutMs = candidateResultTimeout(options.resultTimeoutMs, benchmarkTask.limits.timeoutMs);
|
|
3735
|
+
const reservationWindowMs = candidateExecutionOwnerWindowMs(benchmarkTask.limits.timeoutMs, cleanupTimeoutMs, resultTimeoutMs) + PREPARED_HOLD_MARGIN_MS;
|
|
3736
|
+
if (reservationWindowMs > 2147483647) throw new Error("candidate reservation window exceeds the supported timer range");
|
|
3737
|
+
assertDisjointHostStagingRoots(task);
|
|
3738
|
+
const instructionBytes = Buffer.from(benchmarkTask.instruction, "utf8");
|
|
3739
|
+
const taskArtifacts = await verifyWorkspaceSnapshotArtifacts(benchmarkTask.workspace, ports.artifacts);
|
|
3740
|
+
await ports.workspaces.materialize({
|
|
3741
|
+
role: "task",
|
|
3742
|
+
snapshot: benchmarkTask.workspace,
|
|
3743
|
+
archive: taskArtifacts.archive,
|
|
3744
|
+
destination: task.stagingRoots.taskRoot
|
|
3745
|
+
});
|
|
3746
|
+
await verifyMaterializedWorkspace(task.stagingRoots.taskRoot, benchmarkTask.workspace.material, { ignoredProtectedRootEntries: [".git", ".sidecar"] });
|
|
3747
|
+
if (benchmarkTask.repository) await verifyTaskCheckout(task.stagingRoots.taskRoot, benchmarkTask.repository);
|
|
3748
|
+
const taskExecutorFiles = await readMaterializedWorkspaceFiles(task.stagingRoots.taskRoot, benchmarkTask.workspace.material, { ignoredProtectedRootEntries: [".git", ".sidecar"] });
|
|
3749
|
+
let candidateArchive;
|
|
3750
|
+
let candidateExecutorFiles;
|
|
3751
|
+
if (bundle.execution.workspace) {
|
|
3752
|
+
if (!task.stagingRoots.candidateRoot || !task.executionRoots.candidateRoot) throw new Error("active candidate execution requires host and container candidate roots");
|
|
3753
|
+
candidateArchive = await verifiedArtifactBytes(candidate, bundle.execution.workspace.archive);
|
|
3754
|
+
await ports.workspaces.materialize({
|
|
3755
|
+
role: "candidate",
|
|
3756
|
+
snapshot: bundle.execution.workspace,
|
|
3757
|
+
archive: candidateArchive,
|
|
3758
|
+
destination: task.stagingRoots.candidateRoot
|
|
3759
|
+
});
|
|
3760
|
+
await verifyMaterializedWorkspace(task.stagingRoots.candidateRoot, bundle.execution.workspace.material);
|
|
3761
|
+
candidateExecutorFiles = await readMaterializedWorkspaceFiles(task.stagingRoots.candidateRoot, bundle.execution.workspace.material);
|
|
3762
|
+
} else if (task.stagingRoots.candidateRoot || task.executionRoots.candidateRoot) throw new Error("disabled code cannot receive a candidate workspace root");
|
|
3763
|
+
await assertEmptyDirectory(task.stagingRoots.profileRoot);
|
|
3764
|
+
const profileWorkspacePlan = materializeCandidateProfile(bundle.profile, harness, { resolvedResources: verifiedResourceTextByDigest(candidate) });
|
|
3765
|
+
const profileApplication = applyAgentCandidateWorkspacePlan(profileWorkspacePlan, task.stagingRoots.profileRoot, bundle.execution.cwd.workspace);
|
|
3766
|
+
await verifyMaterializedProfileWorkspace(task.stagingRoots.profileRoot, profileApplication.profileActivation.profilePlan.material);
|
|
3767
|
+
const profilePlanBytes = await readVerifiedArtifact(profileApplication.profileActivation.profilePlan.artifact, ports.artifacts);
|
|
3768
|
+
if (!Buffer.from(profilePlanBytes).equals(Buffer.from(canonicalCandidateBytes(profileApplication.profileActivation.profilePlan.material)))) throw new Error("profile materializer did not capture exact canonical plan bytes");
|
|
3769
|
+
const profileActivation = profileApplication.profileActivation;
|
|
3770
|
+
const container = await resolveContainer(candidate, task, ports);
|
|
3771
|
+
const resolvedModel = await resolveModel(candidate, task, ports);
|
|
3772
|
+
const preparationId = `candidate-preparation.${randomBytes(32).toString("base64url")}`;
|
|
3773
|
+
const reservationExpiresAtMs = Date.now() + Math.max(MIN_RESERVATION_TTL_MS, reservationWindowMs);
|
|
3774
|
+
const modelReservation = await withinCandidateCleanupDeadline(() => ports.models.reserveGrant({
|
|
3775
|
+
executionId: task.executionId,
|
|
3776
|
+
preparationId,
|
|
3777
|
+
expiresAtMs: reservationExpiresAtMs,
|
|
3778
|
+
attempt,
|
|
3779
|
+
bundleDigest: bundle.digest,
|
|
3780
|
+
resolved: resolvedModel,
|
|
3781
|
+
limits: modelLimits(benchmarkTask.limits)
|
|
3782
|
+
}), candidateCleanupDeadline(cleanupTimeoutMs), "protected model reservation");
|
|
3783
|
+
let preparedMemory;
|
|
3784
|
+
try {
|
|
3785
|
+
validateProtectedModelReservation(modelReservation, benchmarkTask.limits, preparationId, reservationExpiresAtMs);
|
|
3786
|
+
preparedMemory = await prepareMemory(candidate, task, ports, preparationId, reservationExpiresAtMs, cleanupTimeoutMs);
|
|
3787
|
+
const memory = preparedMemory.value;
|
|
3788
|
+
const knowledge = await prepareAgentCandidateKnowledge(candidate, ports, task.runCell.arm);
|
|
3789
|
+
const knowledgePaths = knowledge ? candidateKnowledgeExecutionPaths(task.executionRoots.taskRoot, knowledge.retrievalConfig !== void 0) : void 0;
|
|
3790
|
+
const baseLaunch = buildLaunch(candidate, task, profileApplication.flags);
|
|
3791
|
+
const publicEnv = mergePublicEnvironment(bundle.execution.env ?? {}, profileApplication.env, bundle.execution.instructionDelivery.kind === "utf8-file" ? { [bundle.execution.instructionDelivery.env]: {
|
|
3792
|
+
kind: "public",
|
|
3793
|
+
value: bundle.execution.instructionDelivery.path
|
|
3794
|
+
} } : {}, knowledgePaths ? {
|
|
3795
|
+
[CANDIDATE_KNOWLEDGE_ROOT_ENV]: {
|
|
3796
|
+
kind: "public",
|
|
3797
|
+
value: knowledgePaths.root
|
|
3798
|
+
},
|
|
3799
|
+
...knowledgePaths.retrievalConfig ? { [CANDIDATE_KNOWLEDGE_RETRIEVAL_CONFIG_ENV]: {
|
|
3800
|
+
kind: "public",
|
|
3801
|
+
value: knowledgePaths.retrievalConfig
|
|
3802
|
+
} } : {}
|
|
3803
|
+
} : {});
|
|
3804
|
+
const routes = modelRoutes(bundle.profile, benchmarkTask.model.requested);
|
|
3805
|
+
const executionMaterial = {
|
|
3806
|
+
kind: "agent-candidate-execution-plan-material",
|
|
3807
|
+
runCell: task.runCell,
|
|
3808
|
+
executionId: task.executionId,
|
|
3809
|
+
workspaces: {
|
|
3810
|
+
taskRoot: task.executionRoots.taskRoot,
|
|
3811
|
+
...task.executionRoots.candidateRoot ? { candidateRoot: task.executionRoots.candidateRoot } : {}
|
|
3812
|
+
},
|
|
3813
|
+
codeKind: bundle.code.kind,
|
|
3814
|
+
...bundle.execution.workspace ? { candidateWorkspace: bundle.execution.workspace } : {},
|
|
3815
|
+
profile: profileApplication.application,
|
|
3816
|
+
harness: bundle.execution.harness,
|
|
3817
|
+
harnessVersion: bundle.execution.harnessVersion,
|
|
3818
|
+
instructionDelivery: bundle.execution.instructionDelivery,
|
|
3819
|
+
limits: benchmarkTask.limits,
|
|
3820
|
+
container,
|
|
3821
|
+
model: {
|
|
3822
|
+
policy: "single",
|
|
3823
|
+
resolved: resolvedModel,
|
|
3824
|
+
access: {
|
|
3825
|
+
kind: "evaluator-mediated",
|
|
3826
|
+
grantDigest: modelReservation.digest,
|
|
3827
|
+
network: modelReservation.network
|
|
3828
|
+
},
|
|
3829
|
+
routes
|
|
3830
|
+
},
|
|
3831
|
+
launch: {
|
|
3832
|
+
executable: baseLaunch.executable,
|
|
3833
|
+
args: baseLaunch.args,
|
|
3834
|
+
env: publicEnv,
|
|
3835
|
+
cwd: bundle.execution.cwd
|
|
3836
|
+
},
|
|
3837
|
+
...bundle.knowledge ? { knowledgeManifestDigest: bundle.knowledge.snapshot.digest } : {},
|
|
3838
|
+
memory,
|
|
3839
|
+
network: { mode: "disabled" }
|
|
3840
|
+
};
|
|
3841
|
+
agentCandidateExecutionPlanMaterialSchema.parse(executionMaterial);
|
|
3842
|
+
const executionBytes = canonicalCandidateBytes(executionMaterial);
|
|
3843
|
+
const executionDigest = canonicalCandidateDigest$1(executionMaterial);
|
|
3844
|
+
if (sha256Bytes(executionBytes) !== executionDigest) throw new Error("execution plan canonical serializers disagree");
|
|
3845
|
+
const executionPlan = agentCandidateExecutionPlanEvidenceSchema.parse({
|
|
3846
|
+
kind: "agent-candidate-execution-plan",
|
|
3847
|
+
digest: executionDigest,
|
|
3848
|
+
material: executionMaterial,
|
|
3849
|
+
artifact: embeddedCandidateArtifact(executionBytes)
|
|
3850
|
+
});
|
|
3851
|
+
const entrypoint = candidateEntrypointReceipt(candidate);
|
|
3852
|
+
const materializationReceipt = canonicalCandidateDocument({
|
|
3853
|
+
kind: "agent-candidate-materialization",
|
|
3854
|
+
digestAlgorithm: "rfc8785-sha256",
|
|
3855
|
+
bundleDigest: bundle.digest,
|
|
3856
|
+
benchmark: {
|
|
3857
|
+
suite: {
|
|
3858
|
+
digest: task.benchmarkSuite.digest,
|
|
3859
|
+
material: embeddedCandidateArtifact(canonicalCandidateBytes(omitTopLevelDigest(task.benchmarkSuite)))
|
|
3860
|
+
},
|
|
3861
|
+
task: {
|
|
3862
|
+
digest: benchmarkTask.digest,
|
|
3863
|
+
material: embeddedCandidateArtifact(canonicalCandidateBytes(omitTopLevelDigest(benchmarkTask)))
|
|
3864
|
+
}
|
|
3865
|
+
},
|
|
3866
|
+
profileActivation,
|
|
3867
|
+
executionPlan,
|
|
3868
|
+
...bundle.execution.workspace ? { candidateWorkspace: bundle.execution.workspace } : {},
|
|
3869
|
+
codeKind: bundle.code.kind,
|
|
3870
|
+
...candidate.materializedTree ? { materializedTree: candidate.materializedTree } : {},
|
|
3871
|
+
harness: bundle.execution.harness,
|
|
3872
|
+
harnessVersion: bundle.execution.harnessVersion,
|
|
3873
|
+
container,
|
|
3874
|
+
resolvedModel,
|
|
3875
|
+
...bundle.knowledge ? { knowledgeManifestDigest: bundle.knowledge.snapshot.digest } : {},
|
|
3876
|
+
...entrypoint ? { entrypoint } : {}
|
|
3877
|
+
});
|
|
3878
|
+
agentCandidateMaterializationReceiptSchema.parse(materializationReceipt.value);
|
|
3879
|
+
const traceRunId = `${task.executionId}:attempt-${attempt.number}:${canonicalCandidateDigest$1({ preparationId }).slice(7, 23)}`;
|
|
3880
|
+
const traceTags = {
|
|
3881
|
+
[CANDIDATE_TRACE_TAGS.executionId]: task.executionId,
|
|
3882
|
+
[CANDIDATE_TRACE_TAGS.bundleDigest]: bundle.digest,
|
|
3883
|
+
[CANDIDATE_TRACE_TAGS.executionPlanDigest]: executionPlan.digest,
|
|
3884
|
+
[CANDIDATE_TRACE_TAGS.materializationReceiptDigest]: materializationReceipt.digest
|
|
3885
|
+
};
|
|
3886
|
+
const traceEnv = {
|
|
3887
|
+
[CANDIDATE_TRACE_ENV.executionId]: task.executionId,
|
|
3888
|
+
[CANDIDATE_TRACE_ENV.bundleDigest]: bundle.digest,
|
|
3889
|
+
[CANDIDATE_TRACE_ENV.executionPlanDigest]: executionPlan.digest,
|
|
3890
|
+
[CANDIDATE_TRACE_ENV.materializationReceiptDigest]: materializationReceipt.digest,
|
|
3891
|
+
[CANDIDATE_TRACE_ENV.traceRunId]: traceRunId
|
|
3892
|
+
};
|
|
3893
|
+
assertEnvironmentDisjoint(publicEnv, traceEnv);
|
|
3894
|
+
return createPreparedCandidateExecution({
|
|
3895
|
+
ports,
|
|
3896
|
+
bundle,
|
|
3897
|
+
benchmarkSuite: task.benchmarkSuite,
|
|
3898
|
+
benchmarkTask,
|
|
3899
|
+
executionId: task.executionId,
|
|
3900
|
+
roots: {
|
|
3901
|
+
execution: { ...task.executionRoots },
|
|
3902
|
+
staging: { ...task.stagingRoots }
|
|
3903
|
+
},
|
|
3904
|
+
profilePlan: {
|
|
3905
|
+
value: profileApplication.profileActivation.profilePlan,
|
|
3906
|
+
bytes: profilePlanBytes,
|
|
3907
|
+
written: [...profileApplication.application.mountPaths]
|
|
3908
|
+
},
|
|
3909
|
+
profileActivation,
|
|
3910
|
+
executionPlan: {
|
|
3911
|
+
value: executionPlan,
|
|
3912
|
+
bytes: executionBytes
|
|
3913
|
+
},
|
|
3914
|
+
materializationReceipt,
|
|
3915
|
+
launch: {
|
|
3916
|
+
executable: baseLaunch.executable,
|
|
3917
|
+
args: baseLaunch.args.map((value) => value.value),
|
|
3918
|
+
env: unwrapPublicEnvironment(publicEnv),
|
|
3919
|
+
flags: profileApplication.flags.map((value) => value.value),
|
|
3920
|
+
cwd: absoluteExecutionCwd(bundle.execution.cwd, task.executionRoots)
|
|
3921
|
+
},
|
|
3922
|
+
instruction: {
|
|
3923
|
+
bytes: Uint8Array.from(instructionBytes),
|
|
3924
|
+
delivery: bundle.execution.instructionDelivery
|
|
3925
|
+
},
|
|
3926
|
+
resolvedModel,
|
|
3927
|
+
preparationId,
|
|
3928
|
+
reservationExpiresAtMs,
|
|
3929
|
+
cleanupTimeoutMs,
|
|
3930
|
+
resultTimeoutMs,
|
|
3931
|
+
modelReservation: {
|
|
3932
|
+
preparationId: modelReservation.preparationId,
|
|
3933
|
+
digest: modelReservation.digest,
|
|
3934
|
+
expiresAtMs: modelReservation.expiresAtMs,
|
|
3935
|
+
enforcedLimits: modelReservation.enforcedLimits,
|
|
3936
|
+
network: modelReservation.network
|
|
3937
|
+
},
|
|
3938
|
+
executorInputs: {
|
|
3939
|
+
taskFiles: taskExecutorFiles,
|
|
3940
|
+
...candidateExecutorFiles ? { candidateFiles: candidateExecutorFiles } : {},
|
|
3941
|
+
profileFiles: exactProfileExecutorFiles(profileWorkspacePlan.files, profileApplication.profileActivation.profilePlan.material.files)
|
|
3942
|
+
},
|
|
3943
|
+
...preparedMemory.accessDigest && preparedMemory.value.mode === "isolated" ? { memoryReservation: {
|
|
3944
|
+
preparationId,
|
|
3945
|
+
accessDigest: preparedMemory.accessDigest,
|
|
3946
|
+
expiresAtMs: reservationExpiresAtMs,
|
|
3947
|
+
effectiveNamespace: preparedMemory.value.effectiveNamespace
|
|
3948
|
+
} } : {},
|
|
3949
|
+
...knowledge ? { knowledge } : {},
|
|
3950
|
+
trace: {
|
|
3951
|
+
runId: traceRunId,
|
|
3952
|
+
tags: traceTags,
|
|
3953
|
+
env: traceEnv
|
|
3954
|
+
},
|
|
3955
|
+
memory
|
|
3956
|
+
});
|
|
3957
|
+
} catch (error) {
|
|
3958
|
+
const cleanupDeadlineAtMs = candidateCleanupDeadline(cleanupTimeoutMs);
|
|
3959
|
+
const cleanup = [];
|
|
3960
|
+
if (preparedMemory?.value.mode === "isolated") {
|
|
3961
|
+
const accessDigest = preparedMemory.accessDigest;
|
|
3962
|
+
const effectiveNamespace = preparedMemory.value.effectiveNamespace;
|
|
3963
|
+
if (!accessDigest) throw new Error("isolated memory preparation is missing access identity");
|
|
3964
|
+
cleanup.push(withinCandidateCleanupDeadline(async () => {
|
|
3965
|
+
const closed = await ports.memory.close({
|
|
3966
|
+
executionId: task.executionId,
|
|
3967
|
+
preparationId,
|
|
3968
|
+
accessDigest,
|
|
3969
|
+
effectiveNamespace,
|
|
3970
|
+
reason: "preparation-failed"
|
|
3971
|
+
});
|
|
3972
|
+
if (closed.closed !== true || Object.keys(closed).some((key) => key !== "closed")) throw new Error("failed preparation did not close isolated memory access");
|
|
3973
|
+
}, cleanupDeadlineAtMs, "failed preparation memory cleanup"));
|
|
3974
|
+
}
|
|
3975
|
+
cleanup.push(withinCandidateCleanupDeadline(async () => {
|
|
3976
|
+
if (sealAgentCandidateModelSettlement(await ports.models.settleGrant({
|
|
3977
|
+
executionId: task.executionId,
|
|
3978
|
+
preparationId,
|
|
3979
|
+
grantDigest: modelReservation.digest,
|
|
3980
|
+
resolved: resolvedModel,
|
|
3981
|
+
reason: "preparation-failed"
|
|
3982
|
+
}), {
|
|
3983
|
+
preparationId,
|
|
3984
|
+
grantDigest: modelReservation.digest,
|
|
3985
|
+
model: resolvedModel.model
|
|
3986
|
+
}).usage.modelCalls !== 0) throw new Error("failed preparation unexpectedly contains model calls");
|
|
3987
|
+
}, cleanupDeadlineAtMs, "failed preparation model cleanup"));
|
|
3988
|
+
const cleanupErrors = (await Promise.allSettled(cleanup)).filter((result) => result.status === "rejected").map((result) => result.reason);
|
|
3989
|
+
if (cleanupErrors.length > 0) throw new Error(`candidate preparation failed and protected access cleanup failed: ${cleanupErrors.map(errorMessage).join("; ")}`, { cause: error });
|
|
3990
|
+
throw error;
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
function assertSameVerificationPorts(verified, execution) {
|
|
3994
|
+
if (verified.artifacts !== execution.artifacts || verified.repositories !== execution.repositories) throw new Error("prepare must use the same artifact and repository ports that verified the bundle");
|
|
3995
|
+
}
|
|
3996
|
+
function assertTaskInput(task, delivery) {
|
|
3997
|
+
const benchmarkTask = agentCandidateBenchmarkTaskSchema.parse(task.task);
|
|
3998
|
+
const benchmarkSuite = agentCandidateBenchmarkSuiteSchema.parse(task.benchmarkSuite);
|
|
3999
|
+
const runCell = agentCandidateRunCellSchema.parse(task.runCell);
|
|
4000
|
+
const requiredStrings = [
|
|
4001
|
+
["executionId", task.executionId],
|
|
4002
|
+
["benchmark", benchmarkTask.benchmark.name],
|
|
4003
|
+
["benchmarkVersion", benchmarkTask.benchmark.version],
|
|
4004
|
+
["taskId", benchmarkTask.scenario.id]
|
|
4005
|
+
];
|
|
4006
|
+
if (benchmarkTask.outcome.kind === "workspace" && !benchmarkTask.repository) throw new Error("workspace task outcome requires repository identity");
|
|
4007
|
+
if (benchmarkTask.repository) requiredStrings.push(["repository identity", benchmarkTask.repository.identity], ["repository root identity", benchmarkTask.repository.rootIdentity]);
|
|
4008
|
+
for (const [name, value] of requiredStrings) if (!value.trim()) throw new Error(`${name} must be non-empty`);
|
|
4009
|
+
if (!/^[A-Za-z0-9._:-]{1,200}$/.test(task.executionId)) throw new Error("executionId must be a stable filesystem-neutral identifier");
|
|
4010
|
+
if (!benchmarkTask.instruction || !isWellFormedUnicode(benchmarkTask.instruction)) throw new Error("task instruction must be non-empty well-formed Unicode");
|
|
4011
|
+
if (canonicalCandidateDigest$1(omitTopLevelDigest(benchmarkTask)) !== benchmarkTask.digest || canonicalCandidateDigest$1(omitTopLevelDigest(benchmarkSuite)) !== benchmarkSuite.digest || canonicalCandidateDigest$1(omitTopLevelDigest(runCell)) !== runCell.digest) throw new Error("candidate experiment cell contains an invalid content digest");
|
|
4012
|
+
const cellIndex = runCell.taskIndex * benchmarkSuite.reps + runCell.repetition;
|
|
4013
|
+
if (runCell.suiteDigest !== benchmarkSuite.digest || runCell.taskDigest !== benchmarkTask.digest || benchmarkSuite.taskDigests[runCell.taskIndex] !== benchmarkTask.digest || runCell.repetition >= benchmarkSuite.reps || benchmarkSuite.seeds[cellIndex] !== runCell.seed || runCell.attempt > benchmarkTask.attempt.maxAttempts) throw new Error("candidate experiment cell does not match its signed suite and task");
|
|
4014
|
+
sha256DigestSchema.parse(benchmarkTask.benchmark.splitDigest);
|
|
4015
|
+
agentCandidateTaskOutcomeSpecSchema.parse(benchmarkTask.outcome);
|
|
4016
|
+
agentCandidateWorkspaceSnapshotEvidenceSchema.parse(benchmarkTask.workspace);
|
|
4017
|
+
agentCandidateExecutionLimitsSchema.parse(benchmarkTask.limits);
|
|
4018
|
+
if (benchmarkTask.repository) {
|
|
4019
|
+
if (!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(benchmarkTask.repository.baseCommit)) throw new Error("task repository base commit is not a full Git object id");
|
|
4020
|
+
if (!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(benchmarkTask.repository.baseTree)) throw new Error("task repository base tree is not a full Git object id");
|
|
4021
|
+
if (benchmarkTask.repository.baseCommit.length !== benchmarkTask.repository.baseTree.length) throw new Error("task repository Git object formats disagree");
|
|
4022
|
+
}
|
|
4023
|
+
for (const [name, root] of [
|
|
4024
|
+
["execution task root", task.executionRoots.taskRoot],
|
|
4025
|
+
["execution candidate root", task.executionRoots.candidateRoot],
|
|
4026
|
+
["staging task root", task.stagingRoots.taskRoot],
|
|
4027
|
+
["staging candidate root", task.stagingRoots.candidateRoot],
|
|
4028
|
+
["staging profile root", task.stagingRoots.profileRoot]
|
|
4029
|
+
]) {
|
|
4030
|
+
if (root === void 0) continue;
|
|
4031
|
+
if (!(name.startsWith("execution") ? posix.isAbsolute(root) && posix.normalize(root) === root : isAbsolute(root) && resolve(root) === root)) throw new Error(`${name} must be a canonical absolute path`);
|
|
4032
|
+
}
|
|
4033
|
+
if (!Number.isInteger(runCell.attempt) || !Number.isInteger(benchmarkTask.attempt.maxAttempts) || runCell.attempt < 1 || runCell.attempt > benchmarkTask.attempt.maxAttempts || benchmarkTask.attempt.retryPolicy === "none" && benchmarkTask.attempt.maxAttempts !== 1) throw new Error("task attempt policy is invalid");
|
|
4034
|
+
const limits = benchmarkTask.limits;
|
|
4035
|
+
if (!Number.isInteger(limits.timeoutMs) || limits.timeoutMs <= 0 || limits.timeoutMs > 2147483647 || !Number.isInteger(limits.maxSteps) || limits.maxSteps <= 0 || !Number.isInteger(limits.maxModelCalls) || limits.maxModelCalls < 0 || !Number.isInteger(limits.maxInputTokens) || limits.maxInputTokens < 0 || !Number.isInteger(limits.maxOutputTokens) || limits.maxOutputTokens < 0 || !Number.isFinite(limits.maxCostUsd) || limits.maxCostUsd < 0) throw new Error("task execution limits are invalid");
|
|
4036
|
+
usdToNanos(limits.maxCostUsd, "task maxCostUsd");
|
|
4037
|
+
if (!benchmarkTask.model.requested.trim()) throw new Error("evaluator model request must be non-empty");
|
|
4038
|
+
if (!benchmarkTask.grader.name.trim() || !benchmarkTask.grader.version.trim()) throw new Error("evaluator benchmark grader identity must be non-empty");
|
|
4039
|
+
if (!Number.isInteger(benchmarkTask.grader.artifact.byteLength) || benchmarkTask.grader.artifact.byteLength <= 0) throw new Error("evaluator benchmark grader artifact must be non-empty");
|
|
4040
|
+
sha256DigestSchema.parse(benchmarkTask.grader.artifact.sha256);
|
|
4041
|
+
if (benchmarkTask.evaluatorTaskContainer) {
|
|
4042
|
+
if (benchmarkTask.evaluatorTaskContainer.source !== "evaluator-task-container" || !benchmarkTask.evaluatorTaskContainer.image.trim() || !benchmarkTask.evaluatorTaskContainer.platform.os.trim() || !benchmarkTask.evaluatorTaskContainer.platform.architecture.trim()) throw new Error("evaluator task container evidence is incomplete");
|
|
4043
|
+
sha256DigestSchema.parse(benchmarkTask.evaluatorTaskContainer.indexDigest);
|
|
4044
|
+
sha256DigestSchema.parse(benchmarkTask.evaluatorTaskContainer.manifestDigest);
|
|
4045
|
+
agentCandidateContainerSchema.parse({
|
|
4046
|
+
image: benchmarkTask.evaluatorTaskContainer.image,
|
|
4047
|
+
indexDigest: benchmarkTask.evaluatorTaskContainer.indexDigest
|
|
4048
|
+
});
|
|
4049
|
+
}
|
|
4050
|
+
if (delivery.kind === "utf8-file") {
|
|
4051
|
+
if (executionPathsOverlap(task.executionRoots.taskRoot, delivery.path) || task.executionRoots.candidateRoot !== void 0 && executionPathsOverlap(task.executionRoots.candidateRoot, delivery.path)) throw new Error("task instruction file must remain outside execution workspaces");
|
|
4052
|
+
}
|
|
4053
|
+
}
|
|
4054
|
+
function executionPathsOverlap(left, right) {
|
|
4055
|
+
const a = posix.normalize(left);
|
|
4056
|
+
const b = posix.normalize(right);
|
|
4057
|
+
return a === b || b.startsWith(a === "/" ? "/" : `${a}/`) || a.startsWith(b === "/" ? "/" : `${b}/`);
|
|
4058
|
+
}
|
|
4059
|
+
function assertDisjointHostStagingRoots(task) {
|
|
4060
|
+
const roots = [
|
|
4061
|
+
task.stagingRoots.taskRoot,
|
|
4062
|
+
task.stagingRoots.candidateRoot,
|
|
4063
|
+
task.stagingRoots.profileRoot
|
|
4064
|
+
].filter((value) => value !== void 0).map((value) => resolve(value));
|
|
4065
|
+
for (let left = 0; left < roots.length; left++) for (let right = left + 1; right < roots.length; right++) {
|
|
4066
|
+
const a = roots[left];
|
|
4067
|
+
const b = roots[right];
|
|
4068
|
+
if (a && b && (a === b || isContainedPath(a, b) || isContainedPath(b, a))) throw new Error("host task, candidate, and profile staging roots must be disjoint");
|
|
4069
|
+
}
|
|
4070
|
+
}
|
|
4071
|
+
function isContainedPath(parent, child) {
|
|
4072
|
+
const path = relative(parent, child);
|
|
4073
|
+
return path !== "" && !path.startsWith("..") && !isAbsolute(path);
|
|
4074
|
+
}
|
|
4075
|
+
async function assertEmptyDirectory(path) {
|
|
4076
|
+
const stats = await lstat(path);
|
|
4077
|
+
if (!stats.isDirectory() || stats.isSymbolicLink()) throw new Error("profile staging root must be a real directory");
|
|
4078
|
+
if ((await readdir(path)).length !== 0) throw new Error("profile staging root must be empty before materialization");
|
|
4079
|
+
}
|
|
4080
|
+
async function resolveContainer(candidate, task, ports) {
|
|
4081
|
+
const environment = candidate.bundle.execution.environment;
|
|
4082
|
+
const evaluatorTaskContainer = task.task.evaluatorTaskContainer;
|
|
4083
|
+
const pinned = environment.kind === "pinned-container" ? environment.container : void 0;
|
|
4084
|
+
if (environment.kind === "evaluator-task-container" && !evaluatorTaskContainer) throw new Error("evaluator-task-container candidate requires an evaluator-owned task image");
|
|
4085
|
+
if (environment.kind === "pinned-container" && evaluatorTaskContainer) throw new Error("pinned candidate containers cannot be replaced by a task image");
|
|
4086
|
+
const resolved = await ports.containers.resolve({
|
|
4087
|
+
candidate: pinned,
|
|
4088
|
+
evaluatorTaskContainer
|
|
4089
|
+
});
|
|
4090
|
+
if (resolved.source !== environment.kind) throw new Error("resolved container source drifted");
|
|
4091
|
+
if (pinned && (resolved.image !== pinned.image || resolved.indexDigest !== pinned.indexDigest)) throw new Error("resolved pinned container does not match the candidate image index");
|
|
4092
|
+
if (evaluatorTaskContainer && canonicalCandidateDigest$1(resolved) !== canonicalCandidateDigest$1(evaluatorTaskContainer)) throw new Error("resolved task container does not match evaluator-owned image evidence");
|
|
4093
|
+
return resolved;
|
|
4094
|
+
}
|
|
4095
|
+
async function resolveModel(candidate, task, ports) {
|
|
4096
|
+
const hints = candidate.bundle.profile.model;
|
|
4097
|
+
const expected = task.task.model;
|
|
4098
|
+
if (hints?.default !== void 0 && hints.default !== expected.requested) throw new Error("candidate model preference conflicts with the evaluator-owned model");
|
|
4099
|
+
if (hints?.reasoningEffort !== void 0 && hints.reasoningEffort !== expected.reasoningEffort) throw new Error("candidate reasoning effort conflicts with the evaluator-owned effort");
|
|
4100
|
+
const resolved = await ports.models.resolve({
|
|
4101
|
+
requested: expected.requested,
|
|
4102
|
+
harness: candidate.bundle.execution.harness,
|
|
4103
|
+
reasoningEffort: expected.reasoningEffort
|
|
4104
|
+
});
|
|
4105
|
+
if (canonicalCandidateDigest$1(resolved) !== canonicalCandidateDigest$1(expected)) throw new Error("model resolver drifted from the signed benchmark model");
|
|
4106
|
+
return resolved;
|
|
4107
|
+
}
|
|
4108
|
+
async function prepareMemory(candidate, task, ports, preparationId, expiresAtMs, cleanupTimeoutMs) {
|
|
4109
|
+
const policy = candidate.bundle.memory;
|
|
4110
|
+
if (policy.mode === "disabled") return { value: { mode: "disabled" } };
|
|
4111
|
+
const seed = policy.seed ? await verifiedArtifactBytes(candidate, policy.seed) : void 0;
|
|
4112
|
+
const executionSegment = canonicalCandidateDigest$1({ executionId: task.executionId }).slice(7);
|
|
4113
|
+
const taskSegment = canonicalCandidateDigest$1({ taskDigest: task.task.digest }).slice(7);
|
|
4114
|
+
const preparationSegment = canonicalCandidateDigest$1({ preparationId }).slice(7, 23);
|
|
4115
|
+
const effectiveNamespace = `candidate/${candidate.bundle.digest.slice(7, 23)}/${executionSegment}/${taskSegment}/${preparationSegment}`;
|
|
4116
|
+
const reset = await withinCandidateCleanupDeadline(() => ports.memory.reset({
|
|
4117
|
+
executionId: task.executionId,
|
|
4118
|
+
preparationId,
|
|
4119
|
+
expiresAtMs,
|
|
4120
|
+
effectiveNamespace,
|
|
4121
|
+
...seed ? { seed } : {},
|
|
4122
|
+
...policy.seed ? { seedDigest: policy.seed.sha256 } : {}
|
|
4123
|
+
}), candidateCleanupDeadline(cleanupTimeoutMs), "isolated memory reset");
|
|
4124
|
+
try {
|
|
4125
|
+
if (reset.preparationId !== preparationId || reset.expiresAtMs !== expiresAtMs || !/^sha256:[a-f0-9]{64}$/.test(reset.accessDigest)) throw new Error("isolated memory reservation is not scoped to this preparation");
|
|
4126
|
+
await readVerifiedArtifact(reset.evidence, ports.artifacts);
|
|
4127
|
+
await verifyWorkspaceSnapshotArtifacts(reset.beforeState, ports.artifacts);
|
|
4128
|
+
return {
|
|
4129
|
+
value: {
|
|
4130
|
+
mode: "isolated",
|
|
4131
|
+
scope: "task",
|
|
4132
|
+
effectiveNamespace,
|
|
4133
|
+
reset: {
|
|
4134
|
+
kind: "fresh",
|
|
4135
|
+
evidence: reset.evidence,
|
|
4136
|
+
emptyStateDigest: reset.emptyStateDigest
|
|
4137
|
+
},
|
|
4138
|
+
beforeState: reset.beforeState,
|
|
4139
|
+
...policy.seed ? { seedDigest: policy.seed.sha256 } : {}
|
|
4140
|
+
},
|
|
4141
|
+
accessDigest: reset.accessDigest
|
|
4142
|
+
};
|
|
4143
|
+
} catch (error) {
|
|
4144
|
+
try {
|
|
4145
|
+
const closed = await withinCandidateCleanupDeadline(() => ports.memory.close({
|
|
4146
|
+
executionId: task.executionId,
|
|
4147
|
+
preparationId,
|
|
4148
|
+
accessDigest: reset.accessDigest,
|
|
4149
|
+
effectiveNamespace,
|
|
4150
|
+
reason: "preparation-failed"
|
|
4151
|
+
}), candidateCleanupDeadline(cleanupTimeoutMs), "invalid isolated memory reset cleanup");
|
|
4152
|
+
if (closed.closed !== true || Object.keys(closed).some((key) => key !== "closed")) throw new Error("invalid isolated memory reset did not acknowledge closure");
|
|
4153
|
+
} catch (closeError) {
|
|
4154
|
+
throw new Error("isolated memory preparation and cleanup both failed", { cause: new AggregateError([error, closeError]) });
|
|
4155
|
+
}
|
|
4156
|
+
throw error;
|
|
4157
|
+
}
|
|
4158
|
+
}
|
|
4159
|
+
function buildLaunch(candidate, task, profileFlags) {
|
|
4160
|
+
const launch = candidate.bundle.execution.launch;
|
|
4161
|
+
if (launch.kind === "container-command") return {
|
|
4162
|
+
executable: launch.executable,
|
|
4163
|
+
args: [...launch.args ?? [], ...profileFlags]
|
|
4164
|
+
};
|
|
4165
|
+
const candidateRoot = task.executionRoots.candidateRoot;
|
|
4166
|
+
if (!candidateRoot) throw new Error("candidate entrypoint requires a container candidate root");
|
|
4167
|
+
const entrypoint = posix.join(candidateRoot, launch.entrypoint);
|
|
4168
|
+
const candidateArgs = launch.args ?? [];
|
|
4169
|
+
if (launch.interpreter) return {
|
|
4170
|
+
executable: launch.interpreter,
|
|
4171
|
+
args: [
|
|
4172
|
+
{
|
|
4173
|
+
kind: "public",
|
|
4174
|
+
value: entrypoint
|
|
4175
|
+
},
|
|
4176
|
+
...candidateArgs,
|
|
4177
|
+
...profileFlags
|
|
4178
|
+
]
|
|
4179
|
+
};
|
|
4180
|
+
return {
|
|
4181
|
+
executable: entrypoint,
|
|
4182
|
+
args: [...candidateArgs, ...profileFlags]
|
|
4183
|
+
};
|
|
4184
|
+
}
|
|
4185
|
+
function mergePublicEnvironment(...records) {
|
|
4186
|
+
const output = {};
|
|
4187
|
+
for (const record of records) for (const [name, value] of Object.entries(record)) {
|
|
4188
|
+
const previous = output[name];
|
|
4189
|
+
if (previous && previous.value !== value.value) throw new Error(`candidate and profile environment disagree on ${name}`);
|
|
4190
|
+
output[name] = value;
|
|
4191
|
+
}
|
|
4192
|
+
return output;
|
|
4193
|
+
}
|
|
4194
|
+
function unwrapPublicEnvironment(values) {
|
|
4195
|
+
return Object.fromEntries(Object.entries(values).map(([name, value]) => [name, value.value]));
|
|
4196
|
+
}
|
|
4197
|
+
function modelRoutes(profile, requested) {
|
|
4198
|
+
const routes = [{
|
|
4199
|
+
kind: "primary",
|
|
4200
|
+
requested
|
|
4201
|
+
}];
|
|
4202
|
+
if (profile.model?.small) routes.push({
|
|
4203
|
+
kind: "small",
|
|
4204
|
+
requested
|
|
4205
|
+
});
|
|
4206
|
+
for (const name of Object.keys(profile.modes ?? {}).sort()) if (profile.modes?.[name]?.model) routes.push({
|
|
4207
|
+
kind: "mode",
|
|
4208
|
+
name,
|
|
4209
|
+
requested
|
|
4210
|
+
});
|
|
4211
|
+
for (const name of Object.keys(profile.subagents ?? {}).sort()) if (profile.subagents?.[name]?.model) routes.push({
|
|
4212
|
+
kind: "subagent",
|
|
4213
|
+
name,
|
|
4214
|
+
requested
|
|
4215
|
+
});
|
|
4216
|
+
return routes;
|
|
4217
|
+
}
|
|
4218
|
+
function candidateEntrypointReceipt(candidate) {
|
|
4219
|
+
const launch = candidate.bundle.execution.launch;
|
|
4220
|
+
const workspace = candidate.bundle.execution.workspace;
|
|
4221
|
+
if (launch.kind !== "candidate-entrypoint" || !workspace) return void 0;
|
|
4222
|
+
const file = workspace.material.files.find((entry) => entry.path === launch.entrypoint);
|
|
4223
|
+
if (!file) throw new Error("candidate entrypoint is absent from the verified workspace");
|
|
4224
|
+
return {
|
|
4225
|
+
path: file.path,
|
|
4226
|
+
sha256: file.sha256,
|
|
4227
|
+
byteLength: file.byteLength
|
|
4228
|
+
};
|
|
4229
|
+
}
|
|
4230
|
+
function absoluteExecutionCwd(cwd, roots) {
|
|
4231
|
+
const root = cwd.workspace === "task" ? roots.taskRoot : roots.candidateRoot;
|
|
4232
|
+
if (!root) throw new Error("candidate cwd is missing its execution workspace root");
|
|
4233
|
+
const absolute = cwd.path === "." ? root : posix.join(root, cwd.path);
|
|
4234
|
+
if (absolute !== root && !absolute.startsWith(`${root}/`)) throw new Error("candidate cwd escapes its execution workspace");
|
|
4235
|
+
return absolute;
|
|
4236
|
+
}
|
|
4237
|
+
function validateProtectedModelReservation(reservation, expectedLimits, preparationId, expiresAtMs) {
|
|
4238
|
+
if (!/^sha256:[a-f0-9]{64}$/.test(reservation.digest)) throw new Error("protected model reservation has an invalid identity digest");
|
|
4239
|
+
if (reservation.preparationId !== preparationId || reservation.expiresAtMs !== expiresAtMs) throw new Error("protected model reservation is not scoped to this preparation");
|
|
4240
|
+
const limits = modelLimits(expectedLimits);
|
|
4241
|
+
if (canonicalCandidateDigest$1(reservation.enforcedLimits) !== canonicalCandidateDigest$1(limits)) throw new Error("protected model reservation does not enforce the frozen model limits");
|
|
4242
|
+
const expectedNetworkMode = limits.maxModelCalls === 0 ? "disabled" : "gateway-only";
|
|
4243
|
+
if (agentCandidateModelAccessNetworkSchema.parse(reservation.network).mode !== expectedNetworkMode) throw new Error("protected model reservation has the wrong network policy for its call limit");
|
|
4244
|
+
}
|
|
4245
|
+
function assertEnvironmentDisjoint(publicEnv, traceEnv) {
|
|
4246
|
+
const seen = new Set(Object.keys(publicEnv));
|
|
4247
|
+
for (const name of Object.keys(traceEnv)) {
|
|
4248
|
+
if (seen.has(name)) throw new Error(`evaluator environment binding collides with ${name}`);
|
|
4249
|
+
seen.add(name);
|
|
4250
|
+
}
|
|
4251
|
+
}
|
|
4252
|
+
function modelLimits(limits) {
|
|
4253
|
+
return {
|
|
4254
|
+
maxModelCalls: limits.maxModelCalls,
|
|
4255
|
+
maxInputTokens: limits.maxInputTokens,
|
|
4256
|
+
maxOutputTokens: limits.maxOutputTokens,
|
|
4257
|
+
maxCostUsd: limits.maxCostUsd
|
|
4258
|
+
};
|
|
4259
|
+
}
|
|
4260
|
+
function exactProfileExecutorFiles(sourceFiles, expectedFiles) {
|
|
4261
|
+
const byPath = new Map(sourceFiles.map((file) => [file.relPath, file]));
|
|
4262
|
+
if (byPath.size !== sourceFiles.length || sourceFiles.length !== expectedFiles.length) throw new Error("profile source files do not match the signed profile plan");
|
|
4263
|
+
return expectedFiles.map((expected) => {
|
|
4264
|
+
const source = byPath.get(expected.relPath);
|
|
4265
|
+
const mode = source?.mode ?? 420;
|
|
4266
|
+
const bytes = Buffer.from(source?.content ?? "", "utf8");
|
|
4267
|
+
if (!source || !Number.isInteger(mode) || mode < 0 || mode > 511 || mode !== expected.mode || sha256Bytes(bytes) !== expected.contentSha256) throw new Error("profile source files do not match the signed profile plan");
|
|
4268
|
+
return {
|
|
4269
|
+
path: expected.relPath,
|
|
4270
|
+
mode,
|
|
4271
|
+
bytes: Uint8Array.from(bytes)
|
|
4272
|
+
};
|
|
4273
|
+
});
|
|
4274
|
+
}
|
|
4275
|
+
function isWellFormedUnicode(value) {
|
|
4276
|
+
for (let index = 0; index < value.length; index++) {
|
|
4277
|
+
const code = value.charCodeAt(index);
|
|
4278
|
+
if (code >= 55296 && code <= 56319) {
|
|
4279
|
+
const next = value.charCodeAt(index + 1);
|
|
4280
|
+
if (!(next >= 56320 && next <= 57343)) return false;
|
|
4281
|
+
index++;
|
|
4282
|
+
} else if (code >= 56320 && code <= 57343) return false;
|
|
4283
|
+
}
|
|
4284
|
+
return true;
|
|
4285
|
+
}
|
|
4286
|
+
function errorMessage(error) {
|
|
4287
|
+
return error instanceof Error ? error.message : String(error);
|
|
4288
|
+
}
|
|
4289
|
+
//#endregion
|
|
4290
|
+
export { parseExactAgentProfile as $, verifyMaterializedWorkspace as A, rejectedStage as B, assertNoGitIndirection as C, captureMaterializedWorkspace as D, candidateWorkspaceManifest as E, assertTerminalAllowedInPhase as F, agentCandidateProfileAsAgentProfile as G, sealTerminalDigest as H, assertTerminalMatchesClaim as I, candidateMaterializerHarness as J, applyExactAgentProfileDiff as K, assertTerminalMatchesStaged as L, candidateCleanupTimeout as M, withinCandidateCleanupDeadline as N, readVerifiedArtifact as O, assertRecoveryMatchesStaged as P, parseAgentCandidateProfileActivation as Q, recoveredTerminalRecord as R, CANDIDATE_TRACE_TAGS as S, runCandidateGit as T, terminalRecord as U, requireStagedTerminal as V, assertExactObjectKeys as W, freezeGenericAgentCandidateProfile as X, createAgentCandidateProfileActivation as Y, omitUndefinedObjectFields as Z, candidateExecutionClaim as _, persistCandidateOutputArtifact as a, deepFreezeCandidate as at, consumePreparedCandidateExecution as b, CANDIDATE_KNOWLEDGE_ROOT_ENV as c, omitTopLevelDigest as ct, verifiedResourceTextByDigest as d, parseExactAgentProfileDiff as et, verifyAgentCandidateBundle as f, candidateClaimFileInternals as g, InMemoryAgentCandidateExecutionClaimStore as h, persistCandidateModelSettlementEvidence as i, canonicalCandidateDocument as it, candidateCleanupDeadline as j, verifyBytes as k, candidateKnowledgeExecutionPaths as l, sha256Bytes as lt, usdToNanos as m, executePreparedAgentCandidate as n, canonicalCandidateBytes as nt, sealAgentCandidateExecutorStopAcknowledgement as o, embeddedCandidateArtifact as ot, sealAgentCandidateModelSettlement as p, assertCandidateProfileBinding as q, RecoveryAgentCandidateTraceStore as r, canonicalCandidateDigest$1 as rt, CANDIDATE_KNOWLEDGE_RETRIEVAL_CONFIG_ENV as s, immutableCandidateValue as st, prepareAgentCandidateExecution as t, parseExactCandidateProfile as tt, AGENT_CANDIDATE_EXECUTION_SUPPORT as u, verifyCanonicalCandidateDocument as ut, assertPreparedCandidateIntegrity as v, readCandidateGitTreeFiles as w, CANDIDATE_TRACE_ENV as x, beginPreparedCandidateDisposal as y, rejectedFinish as z };
|
|
4291
|
+
|
|
4292
|
+
//# sourceMappingURL=prepare-B53sPV7M.js.map
|