@agentskit/harness 0.1.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -32
- package/CONTRIBUTING.md +60 -12
- package/MANIFESTO.md +23 -0
- package/README.md +276 -144
- package/capabilities/public-surface.json +668 -0
- package/compatibility/manifest.json +17 -0
- package/compatibility/migration.md +10 -0
- package/compatibility/report.json +23 -0
- package/compatibility/report.md +22 -0
- package/compatibility/rollback.md +8 -0
- package/dist/cli.js +958 -239
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1338 -122
- package/dist/index.js +2273 -353
- package/dist/index.js.map +1 -1
- package/docs/ADR-0025-portable-orchestration-controls.md +27 -0
- package/docs/ADR-0026-kernel-adapters-boundary.md +82 -0
- package/docs/GETTING-STARTED.md +18 -0
- package/docs/MODULE-BOUNDARIES.md +143 -0
- package/docs/ORGANIZATION.md +46 -0
- package/docs/TROUBLESHOOTING.md +24 -0
- package/examples/minimum-profile.mjs +27 -0
- package/package.json +52 -34
- package/release/manifest.json +14 -0
- package/release/notes.md +10 -0
- package/release/qualification.json +14 -0
- package/docs/ADR-0025-ci-dogfood.md +0 -22
- package/docs/ADR-0026-ci-evidence-artifact.md +0 -22
- package/docs/ADR-0027-portable-evidence.md +0 -19
- package/docs/ADR-0028-effective-metrics.md +0 -20
- package/docs/ADR-0029-honest-ci-preparation.md +0 -20
- package/docs/ADR-0030-agentskit-os-benchmark-bridge.md +0 -20
- package/docs/ADR-0031-real-provider-baseline.md +0 -18
- package/docs/ADR-0032-harness-equivalent-benchmark.md +0 -25
- package/docs/ADR-0033-portable-agent-gate.md +0 -25
- package/docs/ADR-0034-measurement-quality-gates.md +0 -25
- package/docs/ADR-0035-reproducible-benchmark-samples.md +0 -20
- package/docs/ADR-0036-comparable-baseline-samples.md +0 -20
- package/docs/ADR-0037-replicated-baseline-collection.md +0 -27
- package/docs/ADR-0038-end-to-end-benchmark-boundary.md +0 -28
- package/docs/ADR-0039-artifact-and-protocol-metrics.md +0 -39
- package/docs/ADR-0040-benchmark-corpus-surfaces.md +0 -32
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { resolve, dirname, join, relative, sep } from 'path';
|
|
2
|
-
import { randomUUID, createPrivateKey, createPublicKey, sign, verify
|
|
3
|
-
import { existsSync, mkdirSync, openSync, writeSync, appendFileSync, closeSync, unlinkSync, readFileSync, writeFileSync, mkdtempSync, renameSync, rmSync
|
|
1
|
+
import { resolve, dirname, join, relative, basename, extname, sep } from 'path';
|
|
2
|
+
import { createHash, randomUUID, createPrivateKey, createPublicKey, sign, verify } from 'crypto';
|
|
3
|
+
import { existsSync, mkdirSync, openSync, writeSync, appendFileSync, closeSync, unlinkSync, readFileSync, writeFileSync, readdirSync, mkdtempSync, renameSync, rmSync } from 'fs';
|
|
4
4
|
import { execFile, spawn } from 'child_process';
|
|
5
5
|
import { promisify } from 'util';
|
|
6
|
-
import { tmpdir } from 'os';
|
|
6
|
+
import { cpus, loadavg, freemem, totalmem, tmpdir } from 'os';
|
|
7
7
|
|
|
8
|
-
// src/constants.ts
|
|
8
|
+
// src/kernel/constants.ts
|
|
9
9
|
var STATES = [
|
|
10
10
|
"CLARIFYING",
|
|
11
11
|
"PLANNED",
|
|
@@ -23,7 +23,7 @@ var LEGAL_TRANSITIONS = {
|
|
|
23
23
|
CLARIFYING: ["PLANNED", "BLOCKED", "CANCELLED"],
|
|
24
24
|
PLANNED: ["IMPLEMENTING", "CLARIFYING", "STALE", "CANCELLED"],
|
|
25
25
|
IMPLEMENTING: ["VERIFYING", "CLARIFYING", "STALE", "CANCELLED"],
|
|
26
|
-
VERIFYING: ["AWAITING_HUMAN_APPROVAL", "BLOCKED", "STALE", "CANCELLED"],
|
|
26
|
+
VERIFYING: ["AWAITING_HUMAN_APPROVAL", "COMPLETE", "BLOCKED", "STALE", "CANCELLED"],
|
|
27
27
|
AWAITING_HUMAN_APPROVAL: ["AWAITING_AUTHORIZATION", "COMPLETE", "BLOCKED", "IMPLEMENTING", "STALE", "CANCELLED"],
|
|
28
28
|
AWAITING_AUTHORIZATION: ["COMPLETE", "BLOCKED", "IMPLEMENTING", "STALE", "CANCELLED"],
|
|
29
29
|
COMPLETE: ["STALE", "SUPERSEDED"],
|
|
@@ -35,7 +35,8 @@ var LEGAL_TRANSITIONS = {
|
|
|
35
35
|
var REAL_CATEGORIES = /* @__PURE__ */ new Set(["endpoint", "database", "cli", "mcp", "ui"]);
|
|
36
36
|
var DECISIONS = /* @__PURE__ */ new Set(["approved", "approve", "yes", "ok", "rejected", "reject", "no"]);
|
|
37
37
|
|
|
38
|
-
// src/errors.ts
|
|
38
|
+
// src/kernel/errors.ts
|
|
39
|
+
var HARNESS_ERROR_CODES = ["HARNESS_ERROR", "INVALID_CONFIG", "INVALID_INPUT", "INVALID_STATE", "POLICY_BLOCKED", "CLARIFYING", "STALE", "WORKTREE_DIRTY", "ACTIVE_RUN", "NO_RUN", "HUMAN_APPROVAL_REQUIRED", "GIT_REQUIRED"];
|
|
39
40
|
var HarnessError = class extends Error {
|
|
40
41
|
code;
|
|
41
42
|
constructor(message, code = "HARNESS_ERROR") {
|
|
@@ -48,7 +49,41 @@ var fail = (message, code = "HARNESS_ERROR") => {
|
|
|
48
49
|
throw new HarnessError(message, code);
|
|
49
50
|
};
|
|
50
51
|
|
|
51
|
-
// src/
|
|
52
|
+
// src/kernel/error-policy.ts
|
|
53
|
+
var HARNESS_ERROR_CATALOG = {
|
|
54
|
+
HARNESS_ERROR: { disposition: "escalate", retryable: false },
|
|
55
|
+
INVALID_CONFIG: { disposition: "block", retryable: false },
|
|
56
|
+
INVALID_INPUT: { disposition: "block", retryable: false },
|
|
57
|
+
INVALID_STATE: { disposition: "block", retryable: false },
|
|
58
|
+
POLICY_BLOCKED: { disposition: "block", retryable: false },
|
|
59
|
+
CLARIFYING: { disposition: "block", retryable: false },
|
|
60
|
+
STALE: { disposition: "block", retryable: false },
|
|
61
|
+
WORKTREE_DIRTY: { disposition: "block", retryable: false },
|
|
62
|
+
ACTIVE_RUN: { disposition: "retry", retryable: true },
|
|
63
|
+
NO_RUN: { disposition: "block", retryable: false },
|
|
64
|
+
HUMAN_APPROVAL_REQUIRED: { disposition: "block", retryable: false },
|
|
65
|
+
GIT_REQUIRED: { disposition: "block", retryable: false }
|
|
66
|
+
};
|
|
67
|
+
var nonEmpty = (value, label) => {
|
|
68
|
+
if (typeof value !== "string" || !value.trim()) throw new HarnessError(`${label} is required.`, "INVALID_INPUT");
|
|
69
|
+
return value.trim();
|
|
70
|
+
};
|
|
71
|
+
var classifyHarnessError = (error) => {
|
|
72
|
+
const code = error instanceof HarnessError ? error.code : "HARNESS_ERROR";
|
|
73
|
+
const descriptor2 = HARNESS_ERROR_CATALOG[code];
|
|
74
|
+
return { code, ...descriptor2, message: error instanceof Error ? error.message : String(error) };
|
|
75
|
+
};
|
|
76
|
+
var validateHarnessErrorClassification = (value) => {
|
|
77
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new HarnessError("Error classification must be an object.", "INVALID_INPUT");
|
|
78
|
+
const candidate = value;
|
|
79
|
+
const code = candidate["code"];
|
|
80
|
+
if (typeof code !== "string" || !HARNESS_ERROR_CODES.includes(code)) throw new HarnessError("Error classification code is invalid.", "INVALID_INPUT");
|
|
81
|
+
const expected = HARNESS_ERROR_CATALOG[code];
|
|
82
|
+
if (candidate["disposition"] !== expected.disposition || candidate["retryable"] !== expected.retryable) throw new HarnessError(`Error classification for ${code} is inconsistent.`, "INVALID_INPUT");
|
|
83
|
+
return { code, disposition: expected.disposition, retryable: expected.retryable, message: nonEmpty(candidate["message"], "Error classification message") };
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// src/profiles/index.ts
|
|
52
87
|
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
53
88
|
var record = (value, label) => {
|
|
54
89
|
if (!isRecord(value)) fail(`${label} must be an object.`, "INVALID_CONFIG");
|
|
@@ -61,9 +96,11 @@ var id = (value, label) => {
|
|
|
61
96
|
var parents = (value, label) => value === void 0 ? [] : Array.isArray(value) ? value.map((item, index2) => id(item, `${label}[${index2}]`)) : [id(value, label)];
|
|
62
97
|
var merge = (base, overlay) => {
|
|
63
98
|
const result = { ...base };
|
|
64
|
-
for (const key of ["surfaces", "budget", "cleanup"]) {
|
|
99
|
+
for (const key of ["surfaces", "budget", "cleanup", "runtime", "verification"]) {
|
|
65
100
|
if (overlay[key] !== void 0) result[key] = { ...isRecord(result[key]) ? result[key] : {}, ...record(overlay[key], `profile.${key}`) };
|
|
66
101
|
}
|
|
102
|
+
if (overlay["autonomy"] !== void 0) result["autonomy"] = overlay["autonomy"];
|
|
103
|
+
if (Array.isArray(overlay["checks"])) result["checks"] = overlay["checks"];
|
|
67
104
|
if (overlay["checkOverrides"] !== void 0) {
|
|
68
105
|
if (!Array.isArray(overlay["checkOverrides"])) fail("profile.checkOverrides must be an array.", "INVALID_CONFIG");
|
|
69
106
|
const checks = Array.isArray(result["checks"]) ? [...result["checks"]] : [];
|
|
@@ -136,7 +173,7 @@ var cleanConfiguredArtifacts = (loaded) => {
|
|
|
136
173
|
};
|
|
137
174
|
var fileContents = (path) => readFileSync(path, "utf8");
|
|
138
175
|
|
|
139
|
-
// src/types.ts
|
|
176
|
+
// src/kernel/types.ts
|
|
140
177
|
var SURFACE_NAMES = ["logic", "endpoint", "database", "cli", "mcp", "ui", "docs"];
|
|
141
178
|
var CHECK_CATEGORIES = ["build", "test", "lint", ...SURFACE_NAMES, "custom"];
|
|
142
179
|
var RUN_STATES = [
|
|
@@ -153,7 +190,7 @@ var RUN_STATES = [
|
|
|
153
190
|
"SUPERSEDED"
|
|
154
191
|
];
|
|
155
192
|
|
|
156
|
-
// src/config.ts
|
|
193
|
+
// src/execution/config.ts
|
|
157
194
|
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
158
195
|
var stringValue = (value, label) => {
|
|
159
196
|
if (typeof value !== "string") return fail(`${label} is required.`, "INVALID_CONFIG");
|
|
@@ -181,18 +218,20 @@ var surface = (value, name) => {
|
|
|
181
218
|
var parseCheck = (value, index2) => {
|
|
182
219
|
const record3 = asRecord(value, `checks[${index2}]`);
|
|
183
220
|
const id2 = stringValue(record3["id"], `checks[${index2}].id`);
|
|
184
|
-
const
|
|
185
|
-
if (!CHECK_CATEGORIES.includes(
|
|
221
|
+
const category2 = stringValue(record3["category"], `checks[${index2}].category`);
|
|
222
|
+
if (!CHECK_CATEGORIES.includes(category2)) fail(`checks[${index2}].category is invalid.`, "INVALID_CONFIG");
|
|
186
223
|
const command = stringValue(record3["command"], `checks[${index2}].command`);
|
|
187
|
-
if (REAL_CATEGORIES.has(
|
|
224
|
+
if (REAL_CATEGORIES.has(category2) && record3["execution"] !== "real") fail(`checks[${index2}] must declare execution: real.`, "INVALID_CONFIG");
|
|
188
225
|
if (record3["evidence"] !== "structured") fail(`checks[${index2}] must declare evidence: structured.`, "INVALID_CONFIG");
|
|
189
226
|
if (record3["capabilities"] !== void 0 && (!Array.isArray(record3["capabilities"]) || !record3["capabilities"].every((item) => typeof item === "string"))) fail(`checks[${index2}].capabilities must be strings.`, "INVALID_CONFIG");
|
|
190
227
|
const capabilities = Array.isArray(record3["capabilities"]) ? record3["capabilities"].filter((item) => typeof item === "string") : void 0;
|
|
191
|
-
if (
|
|
192
|
-
if (
|
|
228
|
+
if (category2 === "ui" && !capabilities?.includes("real-browser")) fail(`checks[${index2}] must declare real-browser.`, "INVALID_CONFIG");
|
|
229
|
+
if (category2 === "ui" && !capabilities?.includes("screenshot")) fail(`checks[${index2}] must declare screenshot.`, "INVALID_CONFIG");
|
|
193
230
|
if (record3["required"] !== void 0 && typeof record3["required"] !== "boolean") fail(`checks[${index2}].required must be boolean.`, "INVALID_CONFIG");
|
|
231
|
+
if (record3["required"] === false && typeof record3["reason"] !== "string") fail(`checks[${index2}].reason is required when required is false.`, "INVALID_CONFIG");
|
|
194
232
|
if (record3["timeoutMs"] !== void 0 && (!Number.isInteger(record3["timeoutMs"]) || typeof record3["timeoutMs"] !== "number" || record3["timeoutMs"] < 1)) fail(`checks[${index2}].timeoutMs must be positive.`, "INVALID_CONFIG");
|
|
195
|
-
|
|
233
|
+
const dependsOn = record3["dependsOn"] === void 0 ? void 0 : stringArray(record3["dependsOn"], `checks[${index2}].dependsOn`);
|
|
234
|
+
return { id: id2, category: category2, command, required: record3["required"] !== false, ...typeof record3["reason"] === "string" ? { reason: record3["reason"] } : {}, timeoutMs: typeof record3["timeoutMs"] === "number" ? record3["timeoutMs"] : 12e4, ...record3["execution"] === "real" ? { execution: "real" } : {}, ...capabilities ? { capabilities } : {}, ...dependsOn ? { dependsOn: [...new Set(dependsOn)] } : {}, evidence: "structured" };
|
|
196
235
|
};
|
|
197
236
|
var parseOutcome = (value, index2, checks) => {
|
|
198
237
|
const record3 = asRecord(value, `contract.outcomes[${index2}]`);
|
|
@@ -206,10 +245,28 @@ var validateConfig = (rawValue) => {
|
|
|
206
245
|
const raw = resolveProfile(asRecord(rawValue, "verification config"));
|
|
207
246
|
if (raw["schemaVersion"] !== 1) fail("verification config schemaVersion must be 1.", "INVALID_CONFIG");
|
|
208
247
|
const project = stringValue(raw["project"], "verification config project");
|
|
248
|
+
const runtimeRaw = asRecord(raw["runtime"] ?? { kind: "process" }, "runtime");
|
|
249
|
+
if (runtimeRaw["kind"] !== "process" && runtimeRaw["kind"] !== "docker") fail("runtime.kind must be process or docker.", "INVALID_CONFIG");
|
|
250
|
+
const runtime = { kind: runtimeRaw["kind"] };
|
|
251
|
+
if (raw["autonomy"] !== void 0 && raw["autonomy"] !== "controlled" && raw["autonomy"] !== "yolo") fail("autonomy must be controlled or yolo.", "INVALID_CONFIG");
|
|
252
|
+
const autonomy = raw["autonomy"] ?? "controlled";
|
|
209
253
|
const contractRaw = asRecord(raw["contract"], "contract");
|
|
210
254
|
const rawChecks = raw["checks"];
|
|
211
255
|
const checks = Array.isArray(rawChecks) ? rawChecks.map(parseCheck) : fail("checks must be a non-empty array.", "INVALID_CONFIG");
|
|
212
256
|
if (!checks.length || new Set(checks.map((check) => check.id)).size !== checks.length) fail("check ids must be unique.", "INVALID_CONFIG");
|
|
257
|
+
const checkIds = new Set(checks.map((check) => check.id));
|
|
258
|
+
for (const check of checks) for (const dependency of check.dependsOn ?? []) if (!checkIds.has(dependency) || dependency === check.id) fail(`check ${check.id} has an invalid dependency: ${dependency}.`, "INVALID_CONFIG");
|
|
259
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
260
|
+
const visited = /* @__PURE__ */ new Set();
|
|
261
|
+
const visit = (id2) => {
|
|
262
|
+
if (visiting.has(id2)) fail(`check dependency cycle includes ${id2}.`, "INVALID_CONFIG");
|
|
263
|
+
if (visited.has(id2)) return;
|
|
264
|
+
visiting.add(id2);
|
|
265
|
+
for (const dependency of checks.find((check) => check.id === id2)?.dependsOn ?? []) visit(dependency);
|
|
266
|
+
visiting.delete(id2);
|
|
267
|
+
visited.add(id2);
|
|
268
|
+
};
|
|
269
|
+
for (const check of checks) visit(check.id);
|
|
213
270
|
const scopeRaw = asRecord(contractRaw["scope"], "contract.scope");
|
|
214
271
|
const scope = { inScope: stringArray(scopeRaw["inScope"], "contract.scope.inScope"), outOfScope: stringArray(scopeRaw["outOfScope"], "contract.scope.outOfScope") };
|
|
215
272
|
const ambiguities = stringArray(contractRaw["ambiguities"], "contract.ambiguities");
|
|
@@ -226,13 +283,15 @@ var validateConfig = (rawValue) => {
|
|
|
226
283
|
if (trackingRaw["required"] !== true && typeof trackingRaw["reason"] !== "string") fail("tracking.reason is required when tracking is disabled.", "INVALID_CONFIG");
|
|
227
284
|
const budgetRaw = raw["budget"] === void 0 ? void 0 : asRecord(raw["budget"], "budget");
|
|
228
285
|
if (budgetRaw && budgetRaw["maxDurationMs"] !== void 0 && (!Number.isInteger(budgetRaw["maxDurationMs"]) || typeof budgetRaw["maxDurationMs"] !== "number" || budgetRaw["maxDurationMs"] < 1)) fail("budget.maxDurationMs must be positive.", "INVALID_CONFIG");
|
|
286
|
+
const verificationRaw = raw["verification"] === void 0 ? void 0 : asRecord(raw["verification"], "verification");
|
|
287
|
+
if (verificationRaw && verificationRaw["maxConcurrency"] !== void 0 && (!Number.isInteger(verificationRaw["maxConcurrency"]) || typeof verificationRaw["maxConcurrency"] !== "number" || verificationRaw["maxConcurrency"] < 1)) fail("verification.maxConcurrency must be a positive integer.", "INVALID_CONFIG");
|
|
229
288
|
const cleanupRaw = raw["cleanup"] === void 0 ? void 0 : asRecord(raw["cleanup"], "cleanup");
|
|
230
289
|
const cleanup = cleanupRaw ? { roots: cleanupRaw["roots"] === void 0 ? void 0 : stringArray(cleanupRaw["roots"], "cleanup.roots") } : void 0;
|
|
231
290
|
const benchmarkRaw = raw["benchmark"] === void 0 ? void 0 : asRecord(raw["benchmark"], "benchmark");
|
|
232
291
|
const benchmark = benchmarkRaw ? { suiteId: stringValue(benchmarkRaw["suiteId"], "benchmark.suiteId"), taskId: stringValue(benchmarkRaw["taskId"], "benchmark.taskId"), mode: benchmarkRaw["mode"] === "harness" ? "harness" : fail("benchmark.mode must be harness.", "INVALID_CONFIG") } : void 0;
|
|
233
292
|
const contract = { intent: stringValue(contractRaw["intent"], "contract.intent"), scope, ambiguities, outcomes };
|
|
234
293
|
const tracking = { required: trackingRaw["required"] === true, ...typeof trackingRaw["target"] === "string" ? { target: trackingRaw["target"] } : {}, ...typeof trackingRaw["reason"] === "string" ? { reason: trackingRaw["reason"] } : {} };
|
|
235
|
-
return { schemaVersion: 1, project, ...typeof raw["root"] === "string" ? { root: raw["root"] } : {}, ...typeof raw["stateDir"] === "string" ? { stateDir: raw["stateDir"] } : {}, profile: typeof raw["profile"] === "string" ? raw["profile"] : "strict", contract, surfaces, checks, tracking, ...budgetRaw ? { budget: { maxDurationMs: budgetRaw["maxDurationMs"] } } : {}, ...cleanup ? { cleanup } : {}, ...benchmark ? { benchmark } : {} };
|
|
294
|
+
return { schemaVersion: 1, project, ...typeof raw["root"] === "string" ? { root: raw["root"] } : {}, ...typeof raw["stateDir"] === "string" ? { stateDir: raw["stateDir"] } : {}, profile: typeof raw["profile"] === "string" ? raw["profile"] : "strict", runtime, autonomy, contract, surfaces, checks, tracking, ...verificationRaw ? { verification: { maxConcurrency: verificationRaw["maxConcurrency"] } } : {}, ...budgetRaw ? { budget: { maxDurationMs: budgetRaw["maxDurationMs"] } } : {}, ...cleanup ? { cleanup } : {}, ...benchmark ? { benchmark } : {} };
|
|
236
295
|
};
|
|
237
296
|
var loadConfig = (configPath = ".codex/verification.json") => {
|
|
238
297
|
const absolute = resolve(configPath);
|
|
@@ -240,12 +299,12 @@ var loadConfig = (configPath = ".codex/verification.json") => {
|
|
|
240
299
|
const rawRecord = asRecord(raw, "verification config");
|
|
241
300
|
const root = resolve(dirname(absolute), typeof rawRecord["root"] === "string" ? rawRecord["root"] : ".");
|
|
242
301
|
const stateDir = resolve(root, typeof rawRecord["stateDir"] === "string" ? rawRecord["stateDir"] : ".codex/verification");
|
|
243
|
-
if (
|
|
302
|
+
if (stateDir === root) fail("stateDir must be separate from the project root.", "INVALID_CONFIG");
|
|
244
303
|
const config = validateConfig(raw);
|
|
245
304
|
return { absolute, root, stateDir, config, configHash: hashJson(config) };
|
|
246
305
|
};
|
|
247
306
|
|
|
248
|
-
// src/state-machine.ts
|
|
307
|
+
// src/kernel/state-machine.ts
|
|
249
308
|
var transition = (run, to, reason, actor = "harness") => {
|
|
250
309
|
if (!STATES.includes(to)) fail(`Unknown state ${to}.`, "INVALID_STATE");
|
|
251
310
|
if (run.state !== to && !LEGAL_TRANSITIONS[run.state].some((state) => state === to)) fail(`Illegal transition ${run.state} -> ${to}.`, "INVALID_STATE");
|
|
@@ -260,8 +319,63 @@ var approvedDecision = (decision) => {
|
|
|
260
319
|
return ["approved", "approve", "yes", "ok"].includes(decision);
|
|
261
320
|
};
|
|
262
321
|
var HARNESS_EVENT_SCHEMA_VERSION = 1;
|
|
322
|
+
var HARNESS_EVENT_ENVELOPE_SCHEMA_VERSION = 2;
|
|
263
323
|
var EVENT_LOG_GENESIS = "GENESIS";
|
|
264
|
-
var HARNESS_EVENT_TYPES = ["run.created", "state.transitioned", "context.attached", "verification.completed", "approval.recorded", "authorization.recorded", "session.started", "session.resumed", "agent.turn.started", "policy.evaluated", "tool.approval.requested", "tool.approval.recorded", "tool.requested", "tool.execution.started", "tool.recovery.recorded", "tool.blocked", "tool.completed", "tool.failed", "session.ended"];
|
|
324
|
+
var HARNESS_EVENT_TYPES = ["run.created", "state.transitioned", "context.attached", "verification.completed", "artifact.recorded", "approval.recorded", "authorization.recorded", "session.started", "session.resumed", "agent.turn.started", "policy.evaluated", "tool.approval.requested", "tool.approval.recorded", "tool.requested", "tool.execution.started", "tool.recovery.recorded", "tool.blocked", "tool.completed", "tool.failed", "session.ended"];
|
|
325
|
+
var envelopeId = (value, label) => {
|
|
326
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(value)) fail(`${label} is invalid.`, "INVALID_INPUT");
|
|
327
|
+
return value;
|
|
328
|
+
};
|
|
329
|
+
var envelopeText = (value, label) => {
|
|
330
|
+
if (typeof value !== "string") fail(`${label} is required.`, "INVALID_INPUT");
|
|
331
|
+
const result = value.trim();
|
|
332
|
+
if (!result) fail(`${label} is required.`, "INVALID_INPUT");
|
|
333
|
+
return result;
|
|
334
|
+
};
|
|
335
|
+
var envelopeDigest = (value, label) => {
|
|
336
|
+
const result = envelopeText(value, label);
|
|
337
|
+
if (!/^[a-f0-9]{64}$/.test(result)) fail(`${label} must be a lowercase SHA-256 digest.`, "INVALID_INPUT");
|
|
338
|
+
return result;
|
|
339
|
+
};
|
|
340
|
+
var envelopeProvenance = (value) => {
|
|
341
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) fail("Event provenance must be an object.", "INVALID_INPUT");
|
|
342
|
+
const candidate = value;
|
|
343
|
+
return {
|
|
344
|
+
source: envelopeText(candidate["source"], "Event provenance source"),
|
|
345
|
+
component: envelopeText(candidate["component"], "Event provenance component"),
|
|
346
|
+
version: envelopeText(candidate["version"], "Event provenance version"),
|
|
347
|
+
...candidate["actor"] === void 0 ? {} : { actor: envelopeText(candidate["actor"], "Event provenance actor") }
|
|
348
|
+
};
|
|
349
|
+
};
|
|
350
|
+
var validateHarnessEventEnvelope = (value) => {
|
|
351
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) fail("Event envelope must be an object.", "INVALID_INPUT");
|
|
352
|
+
const candidate = value;
|
|
353
|
+
if (candidate["schemaVersion"] !== HARNESS_EVENT_ENVELOPE_SCHEMA_VERSION) fail("Event envelope schemaVersion is invalid.", "INVALID_INPUT");
|
|
354
|
+
const payload = candidate["payload"];
|
|
355
|
+
if (typeof payload !== "object" || payload === null || Array.isArray(payload)) fail("Event envelope payload must be an object.", "INVALID_INPUT");
|
|
356
|
+
const issueRef = candidate["issueRef"] === void 0 ? void 0 : envelopeText(candidate["issueRef"], "Event issueRef");
|
|
357
|
+
const occurredAt = envelopeText(candidate["occurredAt"], "Event occurredAt");
|
|
358
|
+
if (!Number.isFinite(Date.parse(occurredAt))) fail("Event occurredAt must be a valid timestamp.", "INVALID_INPUT");
|
|
359
|
+
return {
|
|
360
|
+
eventId: envelopeId(candidate["eventId"], "Event eventId"),
|
|
361
|
+
eventType: envelopeId(candidate["eventType"], "Event eventType"),
|
|
362
|
+
schemaVersion: HARNESS_EVENT_ENVELOPE_SCHEMA_VERSION,
|
|
363
|
+
occurredAt,
|
|
364
|
+
runId: envelopeText(candidate["runId"], "Event runId"),
|
|
365
|
+
...issueRef === void 0 ? {} : { issueRef },
|
|
366
|
+
sourceRevision: envelopeText(candidate["sourceRevision"], "Event sourceRevision"),
|
|
367
|
+
correlationId: envelopeId(candidate["correlationId"], "Event correlationId"),
|
|
368
|
+
payload,
|
|
369
|
+
idempotencyKey: envelopeDigest(candidate["idempotencyKey"], "Event idempotencyKey"),
|
|
370
|
+
provenance: envelopeProvenance(candidate["provenance"])
|
|
371
|
+
};
|
|
372
|
+
};
|
|
373
|
+
var createHarnessEventEnvelope = (input) => {
|
|
374
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) fail("Event envelope input must be an object.", "INVALID_INPUT");
|
|
375
|
+
const identity = { eventType: input.eventType, runId: input.runId, ...input.issueRef === void 0 ? {} : { issueRef: input.issueRef }, sourceRevision: input.sourceRevision, correlationId: input.correlationId, payload: input.payload, provenance: input.provenance };
|
|
376
|
+
const candidate = { ...input, schemaVersion: HARNESS_EVENT_ENVELOPE_SCHEMA_VERSION, idempotencyKey: input.idempotencyKey ?? hashJson(identity) };
|
|
377
|
+
return validateHarnessEventEnvelope(candidate);
|
|
378
|
+
};
|
|
265
379
|
var SESSION_EVENT_TYPES = /* @__PURE__ */ new Set(["session.started", "session.resumed", "agent.turn.started", "policy.evaluated", "tool.approval.requested", "tool.approval.recorded", "tool.requested", "tool.execution.started", "tool.recovery.recorded", "tool.blocked", "tool.completed", "tool.failed", "session.ended"]);
|
|
266
380
|
var eventPath = (stateDir, runId) => join(stateDir, "runs", runId, "events.ndjson");
|
|
267
381
|
var lockPath = (stateDir, runId) => `${eventPath(stateDir, runId)}.lock`;
|
|
@@ -282,14 +396,16 @@ var readLock = (stateDir, runId) => {
|
|
|
282
396
|
var isEventType = (value) => typeof value === "string" && HARNESS_EVENT_TYPES.includes(value);
|
|
283
397
|
var digest = (value) => /^[a-f0-9]{64}$/.test(value);
|
|
284
398
|
var eventBody = (event) => {
|
|
285
|
-
const { eventHash: _eventHash, ...
|
|
286
|
-
return
|
|
399
|
+
const { eventHash: _eventHash, ...body3 } = event;
|
|
400
|
+
return body3;
|
|
287
401
|
};
|
|
288
402
|
var eventDigest = (event) => sha256(JSON.stringify(eventBody(event)));
|
|
289
403
|
var parseEvent = (value, expectedSequence) => {
|
|
290
404
|
if (typeof value !== "object" || value === null || Array.isArray(value)) fail("Event log contains a non-object record.", "HARNESS_ERROR");
|
|
291
405
|
const record3 = value;
|
|
292
|
-
|
|
406
|
+
const context = record3["correlation"];
|
|
407
|
+
const validContext = context === void 0 || typeof context === "object" && context !== null && !Array.isArray(context) && Object.entries(context).every(([key, value2]) => ["operationId", "runId", "sessionId", "turnId", "actionId", "traceId"].includes(key) && typeof value2 === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(value2)) && typeof context["operationId"] === "string";
|
|
408
|
+
if (record3["schemaVersion"] !== HARNESS_EVENT_SCHEMA_VERSION || typeof record3["runId"] !== "string" || typeof record3["sequence"] !== "number" || record3["sequence"] !== expectedSequence || typeof record3["at"] !== "string" || typeof record3["sourceRevision"] !== "string" || typeof record3["configHash"] !== "string" || !isEventType(record3["type"]) || typeof record3["payload"] !== "object" || record3["payload"] === null || record3["sessionId"] !== void 0 && (typeof record3["sessionId"] !== "string" || !record3["sessionId"].trim()) || SESSION_EVENT_TYPES.has(record3["type"]) && typeof record3["sessionId"] !== "string" || !validContext) fail("Event log is invalid or out of order.", "HARNESS_ERROR");
|
|
293
409
|
const hasPreviousHash = record3["previousHash"] !== void 0;
|
|
294
410
|
const hasEventHash = record3["eventHash"] !== void 0;
|
|
295
411
|
if (hasPreviousHash !== hasEventHash || hasPreviousHash && (typeof record3["previousHash"] !== "string" || record3["previousHash"] !== EVENT_LOG_GENESIS && !digest(record3["previousHash"]) || typeof record3["eventHash"] !== "string" || !digest(record3["eventHash"]))) fail("Event log integrity metadata is invalid.", "HARNESS_ERROR");
|
|
@@ -318,6 +434,7 @@ var FileEventStore = class {
|
|
|
318
434
|
if (!isEventType(event.type)) fail("Event type is invalid.", "INVALID_INPUT");
|
|
319
435
|
if (SESSION_EVENT_TYPES.has(event.type) && (!event.sessionId || !event.sessionId.trim())) fail("Session events require a sessionId.", "INVALID_INPUT");
|
|
320
436
|
if (event.sessionId !== void 0 && !event.sessionId.trim()) fail("Event sessionId cannot be empty.", "INVALID_INPUT");
|
|
437
|
+
if (event.correlation !== void 0 && (!event.correlation.operationId || !event.correlation.operationId.trim())) fail("Event correlation operationId is required.", "INVALID_INPUT");
|
|
321
438
|
const path = eventPath(this.stateDir, event.runId);
|
|
322
439
|
const lock = lockPath(this.stateDir, event.runId);
|
|
323
440
|
mkdirSync(join(this.stateDir, "runs", event.runId), { recursive: true });
|
|
@@ -332,8 +449,8 @@ var FileEventStore = class {
|
|
|
332
449
|
try {
|
|
333
450
|
const events = this.readUnlocked(event.runId);
|
|
334
451
|
const previous = events.at(-1);
|
|
335
|
-
const
|
|
336
|
-
const record3 = events.length && !previous?.eventHash ?
|
|
452
|
+
const body3 = { schemaVersion: HARNESS_EVENT_SCHEMA_VERSION, sequence: events.length + 1, at: (/* @__PURE__ */ new Date()).toISOString(), runId: event.runId, sourceRevision: event.sourceRevision, configHash: event.configHash, ...event.correlation ? { correlation: event.correlation } : {}, ...event.sessionId ? { sessionId: event.sessionId } : {}, ...previous?.eventHash ? { previousHash: previous.eventHash } : events.length ? {} : { previousHash: EVENT_LOG_GENESIS }, type: event.type, payload: event.payload };
|
|
453
|
+
const record3 = events.length && !previous?.eventHash ? body3 : { ...body3, eventHash: eventDigest(body3) };
|
|
337
454
|
appendFileSync(path, `${JSON.stringify(record3)}
|
|
338
455
|
`, "utf8");
|
|
339
456
|
return record3;
|
|
@@ -387,16 +504,27 @@ var recoverEventLogLock = ({ stateDir, runId, actor, maxAgeMs = 3e5 }) => {
|
|
|
387
504
|
return fail("Event log lock owner is still alive.", "HARNESS_ERROR");
|
|
388
505
|
};
|
|
389
506
|
|
|
390
|
-
// src/plugins.ts
|
|
507
|
+
// src/kernel/plugins.ts
|
|
391
508
|
var HARNESS_PLUGIN_API_VERSION = 1;
|
|
392
509
|
var createPluginSlot = (id2) => {
|
|
393
|
-
if (!id2.trim()) fail("Plugin slot id is required.", "INVALID_INPUT");
|
|
394
|
-
return { id: id2 };
|
|
510
|
+
if (typeof id2 !== "string" || !id2.trim()) fail("Plugin slot id is required.", "INVALID_INPUT");
|
|
511
|
+
return { id: id2.trim() };
|
|
395
512
|
};
|
|
396
513
|
var validId = (value, label) => {
|
|
397
|
-
if (
|
|
514
|
+
if (typeof value !== "string") fail(`${label} is required.`, "INVALID_INPUT");
|
|
515
|
+
const result = value.trim();
|
|
516
|
+
if (!result) fail(`${label} is required.`, "INVALID_INPUT");
|
|
517
|
+
return result;
|
|
518
|
+
};
|
|
519
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
520
|
+
var validEventType = (value) => {
|
|
521
|
+
if (typeof value !== "string" || !HARNESS_EVENT_TYPES.includes(value)) fail("Plugin event type is invalid.", "INVALID_INPUT");
|
|
398
522
|
return value;
|
|
399
523
|
};
|
|
524
|
+
var validSlot = (value) => {
|
|
525
|
+
if (!isRecord3(value)) fail("Plugin slot must be an object.", "INVALID_INPUT");
|
|
526
|
+
return { id: validId(value["id"], "Plugin slot id") };
|
|
527
|
+
};
|
|
400
528
|
var createPluginRegistry = () => {
|
|
401
529
|
const plugins = /* @__PURE__ */ new Map();
|
|
402
530
|
const contributions = /* @__PURE__ */ new Map();
|
|
@@ -443,11 +571,20 @@ var createPluginRegistry = () => {
|
|
|
443
571
|
register(plugin) {
|
|
444
572
|
ensureOpen();
|
|
445
573
|
if (mounted) fail("Plugins cannot be registered after mount.", "HARNESS_ERROR");
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
574
|
+
if (!isRecord3(plugin)) fail("Plugin must be an object.", "INVALID_INPUT");
|
|
575
|
+
const candidate = plugin;
|
|
576
|
+
const id2 = validId(candidate.id, "Plugin id");
|
|
577
|
+
validId(candidate.version, "Plugin version");
|
|
578
|
+
if (candidate.apiVersion !== HARNESS_PLUGIN_API_VERSION) fail(`Unsupported plugin API version: ${String(candidate.apiVersion)}.`, "INVALID_INPUT");
|
|
579
|
+
if (typeof candidate.apply !== "function") fail("Plugin apply must be a function.", "INVALID_INPUT");
|
|
580
|
+
let requires;
|
|
581
|
+
if (candidate.requires !== void 0) {
|
|
582
|
+
if (!Array.isArray(candidate.requires)) fail("Plugin requires must be an array.", "INVALID_INPUT");
|
|
583
|
+
requires = candidate.requires.map((dependency, index2) => validId(dependency, `Plugin dependency[${index2}]`));
|
|
584
|
+
if (new Set(requires).size !== requires.length) fail("Plugin dependencies must be unique.", "INVALID_INPUT");
|
|
585
|
+
}
|
|
586
|
+
if (plugins.has(id2)) fail(`Plugin already registered: ${id2}.`, "INVALID_INPUT");
|
|
587
|
+
plugins.set(id2, { ...candidate, id: id2, ...requires === void 0 ? {} : { requires } });
|
|
451
588
|
},
|
|
452
589
|
mount() {
|
|
453
590
|
ensureOpen();
|
|
@@ -456,14 +593,17 @@ var createPluginRegistry = () => {
|
|
|
456
593
|
for (const plugin of order()) {
|
|
457
594
|
const context = {
|
|
458
595
|
apiVersion: HARNESS_PLUGIN_API_VERSION,
|
|
459
|
-
register: (slot, id2, value) => registerContribution(plugin.id, slot, validId(id2, "Plugin contribution id"), value),
|
|
596
|
+
register: (slot, id2, value) => registerContribution(plugin.id, validSlot(slot), validId(id2, "Plugin contribution id"), value),
|
|
460
597
|
effect: (disposer) => {
|
|
598
|
+
if (typeof disposer !== "function") fail("Plugin disposer must be a function.", "INVALID_INPUT");
|
|
461
599
|
cleanups.push(disposer);
|
|
462
600
|
},
|
|
463
601
|
on: (type, listener) => {
|
|
464
|
-
const
|
|
602
|
+
const eventType = validEventType(type);
|
|
603
|
+
if (typeof listener !== "function") fail("Plugin event listener must be a function.", "INVALID_INPUT");
|
|
604
|
+
const handlers = listeners.get(eventType) ?? /* @__PURE__ */ new Set();
|
|
465
605
|
handlers.add(listener);
|
|
466
|
-
listeners.set(
|
|
606
|
+
listeners.set(eventType, handlers);
|
|
467
607
|
const disposer = () => {
|
|
468
608
|
handlers.delete(listener);
|
|
469
609
|
};
|
|
@@ -486,14 +626,16 @@ var createPluginRegistry = () => {
|
|
|
486
626
|
},
|
|
487
627
|
on(type, listener) {
|
|
488
628
|
ensureOpen();
|
|
489
|
-
const
|
|
629
|
+
const eventType = validEventType(type);
|
|
630
|
+
if (typeof listener !== "function") fail("Plugin event listener must be a function.", "INVALID_INPUT");
|
|
631
|
+
const handlers = listeners.get(eventType) ?? /* @__PURE__ */ new Set();
|
|
490
632
|
handlers.add(listener);
|
|
491
|
-
listeners.set(
|
|
633
|
+
listeners.set(eventType, handlers);
|
|
492
634
|
return () => {
|
|
493
635
|
handlers.delete(listener);
|
|
494
636
|
};
|
|
495
637
|
},
|
|
496
|
-
contributions: (slot) => [...contributions.get(slot.id)?.values() ?? []],
|
|
638
|
+
contributions: (slot) => [...contributions.get(validSlot(slot).id)?.values() ?? []],
|
|
497
639
|
dispose() {
|
|
498
640
|
if (disposed) return;
|
|
499
641
|
let firstError;
|
|
@@ -514,7 +656,25 @@ var createPluginRegistry = () => {
|
|
|
514
656
|
return registry;
|
|
515
657
|
};
|
|
516
658
|
|
|
517
|
-
// src/
|
|
659
|
+
// src/kernel/adapter-contract.ts
|
|
660
|
+
var ASSURANCE_LEVELS = ["unverified", "contract-tested", "runtime-attested"];
|
|
661
|
+
var nonNegative = (value, label) => {
|
|
662
|
+
if (!Number.isFinite(value) || value < 0) return fail(`${label} must be a non-negative number.`, "INVALID_INPUT");
|
|
663
|
+
return value;
|
|
664
|
+
};
|
|
665
|
+
var validateAdapterMetadata = (value) => {
|
|
666
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return fail("Adapter metadata must be an object.", "INVALID_INPUT");
|
|
667
|
+
const candidate = value;
|
|
668
|
+
if (!ASSURANCE_LEVELS.includes(candidate["assurance"])) return fail("Adapter assurance is invalid.", "INVALID_INPUT");
|
|
669
|
+
if (typeof candidate["telemetry"] !== "object" || candidate["telemetry"] === null || Array.isArray(candidate["telemetry"])) return fail("Adapter telemetry must be an object.", "INVALID_INPUT");
|
|
670
|
+
const telemetry = candidate["telemetry"];
|
|
671
|
+
if (telemetry["status"] !== "measured" && telemetry["status"] !== "unknown") return fail("Adapter telemetry status is invalid.", "INVALID_INPUT");
|
|
672
|
+
for (const key of ["durationMs", "inputTokens", "outputTokens", "totalTokens", "cacheHits", "cacheMisses", "memoryReads", "memoryWrites", "memoryRelevantHits", "memoryStaleHits", "contextReferences", "contextCostTokens", "externalMutations"]) if (telemetry[key] !== void 0) nonNegative(telemetry[key], `Adapter telemetry ${key}`);
|
|
673
|
+
return value;
|
|
674
|
+
};
|
|
675
|
+
var unknownTelemetry = () => ({ status: "unknown" });
|
|
676
|
+
|
|
677
|
+
// src/context/index.ts
|
|
518
678
|
var hashContextSnapshot = ({ providerId, query, references, sourceHash: sourceHash2 }) => hashJson({ providerId, query, references, sourceHash: sourceHash2 });
|
|
519
679
|
var hashContextSnapshots = (snapshots) => hashJson(snapshots.map(({ providerId, query, references, sourceHash: sourceHash2, snapshotHash }) => ({ providerId, query, references, sourceHash: sourceHash2, snapshotHash })));
|
|
520
680
|
var record2 = (value, label) => {
|
|
@@ -537,17 +697,23 @@ var validateContextSnapshot = (value, index2 = 0) => {
|
|
|
537
697
|
uri: requiredString(rawReference["uri"], `context snapshot ${index2}.references[${referenceIndex}].uri`),
|
|
538
698
|
...typeof rawReference["title"] === "string" ? { title: rawReference["title"] } : {},
|
|
539
699
|
...typeof rawReference["version"] === "string" ? { version: rawReference["version"] } : {},
|
|
540
|
-
...typeof rawReference["contentHash"] === "string" ? { contentHash: rawReference["contentHash"] } : {}
|
|
700
|
+
...typeof rawReference["contentHash"] === "string" ? { contentHash: rawReference["contentHash"] } : {},
|
|
701
|
+
...rawReference["relevance"] === void 0 ? {} : typeof rawReference["relevance"] === "number" && rawReference["relevance"] >= 0 && rawReference["relevance"] <= 1 ? { relevance: rawReference["relevance"] } : fail(`context snapshot ${index2}.references[${referenceIndex}].relevance must be between 0 and 1.`, "INVALID_INPUT")
|
|
541
702
|
};
|
|
542
703
|
});
|
|
543
704
|
const scope = rawQuery["scope"] === void 0 ? void 0 : Array.isArray(rawQuery["scope"]) && rawQuery["scope"].every((item) => typeof item === "string") ? rawQuery["scope"] : fail(`context snapshot ${index2}.query.scope must be an array of strings.`, "INVALID_INPUT");
|
|
705
|
+
const assurance = raw["assurance"] === void 0 ? void 0 : ASSURANCE_LEVELS.includes(raw["assurance"]) ? raw["assurance"] : fail(`context snapshot ${index2}.assurance is invalid.`, "INVALID_INPUT");
|
|
706
|
+
const telemetry = raw["telemetry"] === void 0 ? void 0 : record2(raw["telemetry"], `context snapshot ${index2}.telemetry`);
|
|
707
|
+
if (telemetry && telemetry["status"] !== "measured" && telemetry["status"] !== "unknown") fail(`context snapshot ${index2}.telemetry.status is invalid.`, "INVALID_INPUT");
|
|
544
708
|
const snapshot = {
|
|
545
709
|
providerId: requiredString(raw["providerId"], `context snapshot ${index2}.providerId`),
|
|
546
710
|
query: { query: requiredString(rawQuery["query"], `context snapshot ${index2}.query.query`), ...scope ? { scope } : {}, ...typeof rawQuery["sourceRevision"] === "string" ? { sourceRevision: rawQuery["sourceRevision"] } : {} },
|
|
547
711
|
references,
|
|
548
712
|
sourceHash: requiredString(raw["sourceHash"], `context snapshot ${index2}.sourceHash`),
|
|
549
713
|
snapshotHash: requiredString(raw["snapshotHash"], `context snapshot ${index2}.snapshotHash`),
|
|
550
|
-
resolvedAt: requiredString(raw["resolvedAt"], `context snapshot ${index2}.resolvedAt`)
|
|
714
|
+
resolvedAt: requiredString(raw["resolvedAt"], `context snapshot ${index2}.resolvedAt`),
|
|
715
|
+
...assurance === void 0 ? {} : { assurance },
|
|
716
|
+
...telemetry === void 0 ? {} : { telemetry }
|
|
551
717
|
};
|
|
552
718
|
if (snapshot.snapshotHash !== hashContextSnapshot(snapshot)) fail(`context snapshot ${index2}.snapshotHash does not match its contents.`, "INVALID_INPUT");
|
|
553
719
|
return snapshot;
|
|
@@ -559,7 +725,7 @@ var readContextSnapshots = (path) => {
|
|
|
559
725
|
var validateContextSnapshots = (snapshots) => snapshots.map((snapshot, index2) => validateContextSnapshot(snapshot, index2));
|
|
560
726
|
var CONTEXT_PROVIDER_SLOT = createPluginSlot("context.provider");
|
|
561
727
|
|
|
562
|
-
// src/runs.ts
|
|
728
|
+
// src/execution/runs.ts
|
|
563
729
|
var now = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
564
730
|
var newRunId = () => `${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
|
|
565
731
|
var saveRun2 = (stateDir, run) => {
|
|
@@ -578,8 +744,7 @@ var saveRun2 = (stateDir, run) => {
|
|
|
578
744
|
store.append({ runId: run.runId, sourceRevision: run.sourceRevision, configHash: run.configHash, type: "context.attached", payload: { providerId: snapshot.providerId, sourceHash: snapshot.sourceHash, snapshotHash: snapshot.snapshotHash, query: snapshot.query } });
|
|
579
745
|
}
|
|
580
746
|
};
|
|
581
|
-
var createRun = async ({ loaded, baseline, supersedes, dirtyBaselineAuthorized, contextSnapshots = []
|
|
582
|
-
const contractHash = hashJson(loaded.config.contract);
|
|
747
|
+
var createRun = async ({ loaded, baseline, supersedes, dirtyBaselineAuthorized, contextSnapshots = [] }) => {
|
|
583
748
|
const run = {
|
|
584
749
|
type: "agentskit-harness-run",
|
|
585
750
|
schemaVersion: 1,
|
|
@@ -587,17 +752,18 @@ var createRun = async ({ loaded, baseline, supersedes, dirtyBaselineAuthorized,
|
|
|
587
752
|
project: loaded.config.project,
|
|
588
753
|
state: "PLANNED",
|
|
589
754
|
configHash: loaded.configHash,
|
|
590
|
-
contractHash,
|
|
755
|
+
contractHash: hashJson(loaded.config.contract),
|
|
591
756
|
sourceRevision: baseline.revision,
|
|
592
757
|
sourceStatusHash: baseline.statusHash,
|
|
593
758
|
baseline,
|
|
594
|
-
|
|
595
|
-
|
|
759
|
+
autonomy: loaded.config.autonomy,
|
|
760
|
+
contractApproval: { actor: "human", at: now(), contractHash: hashJson(loaded.config.contract) },
|
|
761
|
+
checks: loaded.config.checks.map(({ id: id2, category: category2 }) => ({ id: id2, category: category2, status: "pending" })),
|
|
596
762
|
contextSnapshots,
|
|
597
763
|
...contextSnapshots.length ? { contextHash: hashContextSnapshots(contextSnapshots) } : {},
|
|
598
764
|
...loaded.config.benchmark ? { benchmark: loaded.config.benchmark } : {},
|
|
599
765
|
outcomes: loaded.config.contract.outcomes.map(({ id: id2, statement, checks }) => ({ id: id2, statement, checks, status: "pending" })),
|
|
600
|
-
transitions: [{ from: null, to: "PLANNED", at: now(), actor:
|
|
766
|
+
transitions: [{ from: null, to: "PLANNED", at: now(), actor: "human" }],
|
|
601
767
|
evidenceReferences: [],
|
|
602
768
|
...supersedes ? { supersedes } : {},
|
|
603
769
|
...dirtyBaselineAuthorized ? { dirtyBaselineAuthorized: true } : {}
|
|
@@ -616,8 +782,8 @@ var parseStructuredEvidence = (stdout) => {
|
|
|
616
782
|
}
|
|
617
783
|
return null;
|
|
618
784
|
};
|
|
619
|
-
var
|
|
620
|
-
var viewportValid = (viewport) => typeof viewport === "string" ||
|
|
785
|
+
var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
786
|
+
var viewportValid = (viewport) => typeof viewport === "string" || isRecord4(viewport) && typeof viewport["width"] === "number" && viewport["width"] > 0 && typeof viewport["height"] === "number" && viewport["height"] > 0;
|
|
621
787
|
var validateEvidence = (root, check, evidence, outcomeIds) => {
|
|
622
788
|
if (!evidence || evidence.status !== "passed") return ["structured evidence did not pass"];
|
|
623
789
|
if (!Array.isArray(evidence.criteria) || !evidence.criteria.every((id2) => typeof id2 === "string") || outcomeIds.some((id2) => !evidence.criteria.includes(id2))) return [`evidence must map criteria: ${outcomeIds.join(", ")}`];
|
|
@@ -628,7 +794,7 @@ var validateEvidence = (root, check, evidence, outcomeIds) => {
|
|
|
628
794
|
}
|
|
629
795
|
const artifacts = Array.isArray(evidence.artifacts) ? evidence.artifacts : [];
|
|
630
796
|
for (const artifactValue of artifacts) {
|
|
631
|
-
if (!
|
|
797
|
+
if (!isRecord4(artifactValue) || typeof artifactValue["path"] !== "string" || typeof artifactValue["sha256"] !== "string") {
|
|
632
798
|
failures.push("artifact requires string path and sha256");
|
|
633
799
|
continue;
|
|
634
800
|
}
|
|
@@ -650,25 +816,100 @@ var git = async (root, args) => {
|
|
|
650
816
|
};
|
|
651
817
|
var sourceSnapshot = async (root, stateDir) => {
|
|
652
818
|
const revision = await git(root, ["rev-parse", "HEAD"]);
|
|
819
|
+
if (!revision) fail("Current-source evidence requires a Git repository with a committed HEAD.", "GIT_REQUIRED");
|
|
653
820
|
const stateRelative = relative(root, stateDir).replaceAll("\\", "/");
|
|
654
821
|
const pathspec = ["--", "."];
|
|
655
822
|
if (stateRelative && stateRelative !== ".." && !stateRelative.startsWith("../")) pathspec.push(`:(exclude)${stateRelative}`);
|
|
656
823
|
const status = await git(root, ["status", "--porcelain=v1", "--untracked-files=all", ...pathspec]);
|
|
657
824
|
const diff = await git(root, ["diff", "--no-ext-diff", "--binary", "HEAD", ...pathspec]);
|
|
658
825
|
const untrackedPaths = (await git(root, ["ls-files", "--others", "--exclude-standard", "-z"])).split("\0").filter(Boolean).filter((path) => !stateRelative || path !== stateRelative && !path.startsWith(`${stateRelative}/`));
|
|
659
|
-
const untracked = untrackedPaths.
|
|
660
|
-
const absolute = resolve(root, path);
|
|
661
|
-
try {
|
|
662
|
-
return lstatSync(absolute).isFile() ? [{ path, hash: sha256(readFileSync(absolute)) }] : [];
|
|
663
|
-
} catch {
|
|
664
|
-
return [];
|
|
665
|
-
}
|
|
666
|
-
});
|
|
826
|
+
const untracked = untrackedPaths.map((path) => ({ path, hash: sha256(readFileSync(resolve(root, path))) }));
|
|
667
827
|
const fingerprint = { revision, status, diff, untracked };
|
|
668
|
-
return { revision
|
|
828
|
+
return { revision, status, statusHash: hashJson(fingerprint) };
|
|
829
|
+
};
|
|
830
|
+
var thresholds = (value = {}) => {
|
|
831
|
+
const result = { warningPercent: value.warningPercent ?? 75, criticalPercent: value.criticalPercent ?? 90 };
|
|
832
|
+
if (![result.warningPercent, result.criticalPercent].every((item) => Number.isFinite(item) && item >= 0 && item <= 100) || result.warningPercent > result.criticalPercent) fail("Machine thresholds must be between 0 and 100 and warning must not exceed critical.", "INVALID_INPUT");
|
|
833
|
+
return result;
|
|
834
|
+
};
|
|
835
|
+
var linuxSwap = () => {
|
|
836
|
+
if (process.platform !== "linux" || !existsSync("/proc/meminfo")) return void 0;
|
|
837
|
+
const values = Object.fromEntries(readFileSync("/proc/meminfo", "utf8").split(/\r?\n/).flatMap((line) => {
|
|
838
|
+
const match = line.match(/^(SwapTotal|SwapFree):\s+(\d+)\s+kB$/);
|
|
839
|
+
return match ? [[match[1], Number(match[2])]] : [];
|
|
840
|
+
}));
|
|
841
|
+
if (!values["SwapTotal"]) return void 0;
|
|
842
|
+
return Number(((1 - (values["SwapFree"] ?? 0) / values["SwapTotal"]) * 100).toFixed(2));
|
|
843
|
+
};
|
|
844
|
+
var sampleMachine = () => {
|
|
845
|
+
const cpus$1 = Math.max(1, cpus().length);
|
|
846
|
+
const load1 = Math.max(0, loadavg()[0] ?? 0);
|
|
847
|
+
const memory = Math.max(0, Math.min(100, (1 - freemem() / Math.max(1, totalmem())) * 100));
|
|
848
|
+
const swapUsedPercent = linuxSwap();
|
|
849
|
+
return {
|
|
850
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
851
|
+
cpus: cpus$1,
|
|
852
|
+
load1: Number(load1.toFixed(4)),
|
|
853
|
+
load1PerCpuPercent: Number(Math.min(100, load1 / cpus$1 * 100).toFixed(2)),
|
|
854
|
+
memoryUsedPercent: Number(memory.toFixed(2)),
|
|
855
|
+
rssBytes: process.memoryUsage().rss,
|
|
856
|
+
...swapUsedPercent === void 0 ? {} : { swapUsedPercent }
|
|
857
|
+
};
|
|
858
|
+
};
|
|
859
|
+
var summarizeMachine = (samples, sampleIntervalMs = 5e3, limits = {}) => {
|
|
860
|
+
const limit = thresholds(limits);
|
|
861
|
+
const load = samples.map((sample) => sample.load1PerCpuPercent);
|
|
862
|
+
const memory = samples.map((sample) => sample.memoryUsedPercent);
|
|
863
|
+
const rss = samples.map((sample) => sample.rssBytes);
|
|
864
|
+
return {
|
|
865
|
+
sampleIntervalMs,
|
|
866
|
+
samples,
|
|
867
|
+
peakLoad1PerCpuPercent: Number(Math.max(...load, 0).toFixed(2)),
|
|
868
|
+
peakMemoryUsedPercent: Number(Math.max(...memory, 0).toFixed(2)),
|
|
869
|
+
peakRssBytes: Math.max(...rss, 0),
|
|
870
|
+
pressureEvents: samples.filter((sample) => sample.load1PerCpuPercent >= limit.criticalPercent || sample.memoryUsedPercent >= limit.criticalPercent || (sample.swapUsedPercent ?? 0) >= limit.criticalPercent || sample.memoryPressure === "critical").length,
|
|
871
|
+
throttleEvents: 0,
|
|
872
|
+
minimumEffectiveConcurrency: 0
|
|
873
|
+
};
|
|
874
|
+
};
|
|
875
|
+
var adaptiveConcurrency = (configured, sample, limits = {}) => {
|
|
876
|
+
if (!Number.isInteger(configured) || configured < 1) throw new Error("configured concurrency must be a positive integer.");
|
|
877
|
+
const limit = thresholds(limits);
|
|
878
|
+
const critical = sample.load1PerCpuPercent >= limit.criticalPercent || sample.memoryUsedPercent >= limit.criticalPercent || (sample.swapUsedPercent ?? 0) >= limit.criticalPercent || sample.memoryPressure === "critical";
|
|
879
|
+
const warning = sample.load1PerCpuPercent >= limit.warningPercent || sample.memoryUsedPercent >= limit.warningPercent || (sample.swapUsedPercent ?? 0) >= limit.warningPercent || sample.memoryPressure === "warning";
|
|
880
|
+
if (critical) return 1;
|
|
881
|
+
if (warning) return Math.min(configured, 2);
|
|
882
|
+
return configured;
|
|
883
|
+
};
|
|
884
|
+
var createMachineMonitor = (sampleIntervalMs = 5e3, options = {}) => {
|
|
885
|
+
const sampler = options.sample ?? sampleMachine;
|
|
886
|
+
const limits = thresholds(options.thresholds);
|
|
887
|
+
const samples = [sampler()];
|
|
888
|
+
let throttleEvents = 0;
|
|
889
|
+
const effectiveConcurrency = [];
|
|
890
|
+
const record3 = () => {
|
|
891
|
+
const sample = sampler();
|
|
892
|
+
samples.push(sample);
|
|
893
|
+
return sample;
|
|
894
|
+
};
|
|
895
|
+
const timer = setInterval(record3, sampleIntervalMs);
|
|
896
|
+
timer.unref();
|
|
897
|
+
return {
|
|
898
|
+
sample: record3,
|
|
899
|
+
observeConcurrency: (value) => effectiveConcurrency.push(value),
|
|
900
|
+
markThrottle: () => {
|
|
901
|
+
throttleEvents += 1;
|
|
902
|
+
},
|
|
903
|
+
stop: () => {
|
|
904
|
+
clearInterval(timer);
|
|
905
|
+
record3();
|
|
906
|
+
const summary = summarizeMachine(samples, sampleIntervalMs, limits);
|
|
907
|
+
return { ...summary, throttleEvents, minimumEffectiveConcurrency: effectiveConcurrency.length ? Math.min(...effectiveConcurrency) : 0 };
|
|
908
|
+
}
|
|
909
|
+
};
|
|
669
910
|
};
|
|
670
911
|
|
|
671
|
-
// src/verification.ts
|
|
912
|
+
// src/execution/verification.ts
|
|
672
913
|
var now2 = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
673
914
|
var requireRun = (run) => run ?? fail("No verification run exists.", "NO_RUN");
|
|
674
915
|
var verificationProjection = (run) => ({ checks: run.checks, outcomes: run.outcomes, metrics: run.metrics });
|
|
@@ -694,6 +935,12 @@ var runCommand = (check, cwd) => new Promise((resolveResult) => {
|
|
|
694
935
|
resolveResult({ exitCode: exitCode ?? 1, timedOut, stdout, stderr, durationMs: Date.now() - started });
|
|
695
936
|
});
|
|
696
937
|
});
|
|
938
|
+
var executeCheck = async (check, cwd, checkDir, outcomes) => {
|
|
939
|
+
const result = await runCommand(check, cwd);
|
|
940
|
+
const evidence = parseStructuredEvidence(result.stdout);
|
|
941
|
+
const failures = result.exitCode === 0 && !result.timedOut && evidence ? validateEvidence(cwd, check, evidence, outcomes) : [result.timedOut ? "check timed out" : result.exitCode !== 0 ? `exit code ${result.exitCode}` : "missing final structured evidence"];
|
|
942
|
+
return { check: { id: check.id, category: check.category, status: failures.length ? "failed" : "passed", exitCode: result.exitCode, durationMs: result.durationMs, ...evidence ? { evidence } : {}, ...failures.length ? { failures } : {} }, stdout: result.stdout, stderr: result.stderr, durationMs: result.durationMs };
|
|
943
|
+
};
|
|
697
944
|
var currentBinding = async (loaded) => ({ source: await sourceSnapshot(loaded.root, loaded.stateDir), configHash: loaded.configHash });
|
|
698
945
|
var staleRun = (loaded, run, reason) => {
|
|
699
946
|
const stale = transition(run, "STALE", reason);
|
|
@@ -706,11 +953,8 @@ var isFresh = async (loaded, run) => {
|
|
|
706
953
|
return current.configHash === run.configHash && current.source.revision === run.sourceRevision && current.source.statusHash === run.sourceStatusHash;
|
|
707
954
|
};
|
|
708
955
|
var planRun = async ({ configPath, decision, actor = "human", allowDirty = false, contextSnapshots = [] }) => {
|
|
709
|
-
|
|
710
|
-
if (!
|
|
711
|
-
assertHuman(actor);
|
|
712
|
-
if (!approvedDecision(decision)) fail("Contract was not approved.", "CLARIFYING");
|
|
713
|
-
}
|
|
956
|
+
assertHuman(actor);
|
|
957
|
+
if (!approvedDecision(decision)) fail("Contract was not approved.", "CLARIFYING");
|
|
714
958
|
const loaded = loadConfig(configPath);
|
|
715
959
|
if (loaded.config.contract.ambiguities.length) fail(`Unresolved ambiguities remain: ${loaded.config.contract.ambiguities.join(" | ")}`, "CLARIFYING");
|
|
716
960
|
const validatedContextSnapshots = validateContextSnapshots(contextSnapshots);
|
|
@@ -722,7 +966,7 @@ ${meaningful.join("\n")}
|
|
|
722
966
|
Use --allow-dirty only with explicit human authorization.`, "WORKTREE_DIRTY");
|
|
723
967
|
const previous = loadLatestRun(loaded.stateDir);
|
|
724
968
|
if (previous && !["STALE", "SUPERSEDED"].includes(previous.state)) fail(`An active run already exists: ${previous.runId} (${previous.state}).`, "ACTIVE_RUN");
|
|
725
|
-
return createRun({ loaded, baseline, supersedes: previous?.runId, dirtyBaselineAuthorized: allowDirty, contextSnapshots: validatedContextSnapshots
|
|
969
|
+
return createRun({ loaded, baseline, supersedes: previous?.runId, dirtyBaselineAuthorized: allowDirty, contextSnapshots: validatedContextSnapshots });
|
|
726
970
|
};
|
|
727
971
|
var startRun = (loaded) => {
|
|
728
972
|
const run = requireRun(loadLatestRun(loaded.stateDir));
|
|
@@ -742,42 +986,81 @@ var cancelRun = async ({ configPath, runId, reason = "Run cancelled by a human."
|
|
|
742
986
|
};
|
|
743
987
|
var verifyRun = async ({ configPath }) => {
|
|
744
988
|
const loaded = loadConfig(configPath);
|
|
989
|
+
const machineMonitor = createMachineMonitor();
|
|
745
990
|
const run = requireRun(loadLatestRun(loaded.stateDir));
|
|
746
991
|
if (!["IMPLEMENTING", "VERIFYING"].includes(run.state)) {
|
|
747
992
|
if (["AWAITING_HUMAN_APPROVAL", "AWAITING_AUTHORIZATION", "COMPLETE"].includes(run.state) && !await isFresh(loaded, run)) staleRun(loaded, run, "Run is stale because source or contract changed.");
|
|
748
993
|
fail(`Cannot verify from ${run.state}.`, "INVALID_STATE");
|
|
749
994
|
}
|
|
750
995
|
if (run.configHash !== loaded.configHash) staleRun(loaded, run, "Run is stale because the verification contract changed.");
|
|
751
|
-
const
|
|
752
|
-
let current = { ...transition(run, "VERIFYING", "Verification started.", "agent"), sourceRevision:
|
|
996
|
+
const binding2 = await currentBinding(loaded);
|
|
997
|
+
let current = { ...transition(run, "VERIFYING", "Verification started.", "agent"), sourceRevision: binding2.source.revision, sourceStatusHash: binding2.source.statusHash };
|
|
753
998
|
saveRun2(loaded.stateDir, current);
|
|
754
999
|
const checkDir = join(loaded.stateDir, "runs", current.runId, "checks");
|
|
755
1000
|
mkdirSync(checkDir, { recursive: true });
|
|
756
1001
|
const outcomesByCheck = new Map(loaded.config.checks.map((check) => [check.id, loaded.config.contract.outcomes.filter((outcome) => outcome.checks.includes(check.id)).map((outcome) => outcome.id)]));
|
|
757
1002
|
let totalDurationMs = 0;
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
1003
|
+
let activeChecks = 0;
|
|
1004
|
+
let observedPeakConcurrency = 0;
|
|
1005
|
+
const verificationStarted = Date.now();
|
|
1006
|
+
const maxConcurrency = loaded.config.verification?.maxConcurrency ?? 1;
|
|
1007
|
+
const requiredChecks = loaded.config.checks.filter((check) => check.required);
|
|
1008
|
+
for (const check of loaded.config.checks.filter((item) => !item.required)) {
|
|
1009
|
+
const nextCheck = { id: check.id, category: check.category, status: "not-applicable", failures: [check.reason ?? "Not required by the selected profile."] };
|
|
1010
|
+
current = { ...current, checks: current.checks.map((item) => item.id === check.id ? nextCheck : item) };
|
|
1011
|
+
}
|
|
1012
|
+
const remaining = new Set(requiredChecks.map((check) => check.id));
|
|
1013
|
+
const completed = new Set(loaded.config.checks.filter((check) => !check.required).map((check) => check.id));
|
|
1014
|
+
while (remaining.size) {
|
|
1015
|
+
const ready = requiredChecks.filter((check) => remaining.has(check.id) && (check.dependsOn ?? []).every((dependency) => completed.has(dependency)));
|
|
1016
|
+
if (!ready.length) fail("Check dependency graph contains an unknown dependency or cycle.", "INVALID_CONFIG");
|
|
1017
|
+
const buildChecks = ready.filter((check) => check.category === "build");
|
|
1018
|
+
const queue = buildChecks.length ? buildChecks : ready;
|
|
1019
|
+
let offset = 0;
|
|
1020
|
+
while (offset < queue.length) {
|
|
1021
|
+
const effectiveConcurrency = buildChecks.length ? 1 : adaptiveConcurrency(maxConcurrency, machineMonitor.sample());
|
|
1022
|
+
machineMonitor.observeConcurrency(effectiveConcurrency);
|
|
1023
|
+
if (effectiveConcurrency < maxConcurrency) machineMonitor.markThrottle();
|
|
1024
|
+
const batch = queue.slice(offset, offset + effectiveConcurrency);
|
|
1025
|
+
const executed = await Promise.all(batch.map(async (check) => {
|
|
1026
|
+
activeChecks += 1;
|
|
1027
|
+
observedPeakConcurrency = Math.max(observedPeakConcurrency, activeChecks);
|
|
1028
|
+
try {
|
|
1029
|
+
return await executeCheck(check, loaded.root, checkDir, outcomesByCheck.get(check.id) ?? []);
|
|
1030
|
+
} finally {
|
|
1031
|
+
activeChecks -= 1;
|
|
1032
|
+
}
|
|
1033
|
+
}));
|
|
1034
|
+
for (const item of executed) {
|
|
1035
|
+
totalDurationMs += item.durationMs;
|
|
1036
|
+
const stdoutPath = join(checkDir, `${item.check.id}.stdout`);
|
|
1037
|
+
const stderrPath = join(checkDir, `${item.check.id}.stderr`);
|
|
1038
|
+
writeFileSync(stdoutPath, item.stdout, "utf8");
|
|
1039
|
+
writeFileSync(stderrPath, item.stderr, "utf8");
|
|
1040
|
+
current = { ...current, evidenceReferences: [...current.evidenceReferences, { checkId: item.check.id, stdout: relative(loaded.stateDir, stdoutPath), stderr: relative(loaded.stateDir, stderrPath) }], checks: current.checks.map((check) => check.id === item.check.id ? item.check : check) };
|
|
1041
|
+
}
|
|
1042
|
+
saveRun2(loaded.stateDir, current);
|
|
1043
|
+
for (const check of batch) {
|
|
1044
|
+
remaining.delete(check.id);
|
|
1045
|
+
completed.add(check.id);
|
|
1046
|
+
}
|
|
1047
|
+
offset += batch.length;
|
|
1048
|
+
}
|
|
770
1049
|
}
|
|
771
1050
|
const statuses = new Map(current.checks.map((check) => [check.id, check.status]));
|
|
772
1051
|
const budgetExceeded = loaded.config.budget?.maxDurationMs !== void 0 && totalDurationMs > loaded.config.budget.maxDurationMs;
|
|
773
|
-
const allPassed = loaded.config.checks.every((check) => statuses.get(check.id) === "passed") && !budgetExceeded;
|
|
774
|
-
current = { ...current, outcomes: current.outcomes.map((outcome) =>
|
|
775
|
-
|
|
776
|
-
|
|
1052
|
+
const allPassed = loaded.config.checks.every((check) => !check.required || statuses.get(check.id) === "passed") && !budgetExceeded;
|
|
1053
|
+
current = { ...current, outcomes: current.outcomes.map((outcome) => {
|
|
1054
|
+
const required16 = outcome.checks.filter((id2) => loaded.config.checks.find((check) => check.id === id2)?.required);
|
|
1055
|
+
return { ...outcome, status: required16.length === 0 ? "not-applicable" : required16.every((id2) => statuses.get(id2) === "passed") ? "passed" : "failed" };
|
|
1056
|
+
}), metrics: { totalDurationMs, wallDurationMs: Date.now() - verificationStarted, peakConcurrency: observedPeakConcurrency, budgetExceeded, machine: machineMonitor.stop() } };
|
|
1057
|
+
const digest6 = verificationDigest(current);
|
|
1058
|
+
current = { ...current, verificationDigest: digest6 };
|
|
777
1059
|
saveRun2(loaded.stateDir, current);
|
|
778
|
-
new FileEventStore(loaded.stateDir).append({ runId: current.runId, sourceRevision: current.sourceRevision, configHash: current.configHash, type: "verification.completed", payload: { verificationDigest:
|
|
779
|
-
const
|
|
780
|
-
|
|
1060
|
+
new FileEventStore(loaded.stateDir).append({ runId: current.runId, sourceRevision: current.sourceRevision, configHash: current.configHash, type: "verification.completed", payload: { verificationDigest: digest6, checkCount: current.checks.length, outcomeCount: current.outcomes.length, totalDurationMs, budgetExceeded } });
|
|
1061
|
+
const automatic = allPassed && current.autonomy === "yolo" && !loaded.config.tracking.required && loaded.config.contract.ambiguities.length === 0;
|
|
1062
|
+
const nextState = allPassed ? automatic ? "COMPLETE" : "AWAITING_HUMAN_APPROVAL" : "BLOCKED";
|
|
1063
|
+
current = { ...transition(current, nextState, automatic ? "All applicable checks passed; YOLO policy permits automatic completion." : allPassed ? "All configured checks passed; human approval is required." : budgetExceeded ? "Verification budget was exceeded." : "A configured check failed or lacked structured evidence.", "harness") };
|
|
781
1064
|
saveRun2(loaded.stateDir, current);
|
|
782
1065
|
setLatest(loaded.stateDir, current);
|
|
783
1066
|
return current;
|
|
@@ -791,11 +1074,6 @@ var assertVerificationAttestation = (loaded, run) => {
|
|
|
791
1074
|
const event = new FileEventStore(loaded.stateDir).read(run.runId).filter((item) => item.type === "verification.completed").at(-1);
|
|
792
1075
|
if (!event || event.payload.verificationDigest !== expected || event.sourceRevision !== run.sourceRevision || event.configHash !== run.configHash) fail("Verification projection attestation is missing from the event log.", "HARNESS_ERROR");
|
|
793
1076
|
};
|
|
794
|
-
var plannerForRetry = (loaded, run) => {
|
|
795
|
-
if (run.contractPreparation) return "ci";
|
|
796
|
-
if (!run.supersedes) return "human";
|
|
797
|
-
return plannerForRetry(loaded, readRun(loaded.stateDir, run.supersedes));
|
|
798
|
-
};
|
|
799
1077
|
var recordDecision = (loaded, run, type, payload) => {
|
|
800
1078
|
new FileEventStore(loaded.stateDir).append({ runId: run.runId, sourceRevision: run.sourceRevision, configHash: run.configHash, type, payload: type === "authorization.recorded" ? { ...payload, target: payload.target ?? fail("tracking.target is required when authorizing.", "INVALID_CONFIG") } : payload });
|
|
801
1079
|
};
|
|
@@ -815,7 +1093,7 @@ var reconcileRun = async ({ configPath, runId }) => {
|
|
|
815
1093
|
if (eventLog.status !== "verified") fail("Terminal run requires a verified event log.", "HARNESS_ERROR");
|
|
816
1094
|
assertVerificationAttestation(loaded, run);
|
|
817
1095
|
}
|
|
818
|
-
if (run.state === "AWAITING_AUTHORIZATION" || run.state === "COMPLETE") {
|
|
1096
|
+
if ((run.state === "AWAITING_AUTHORIZATION" || run.state === "COMPLETE") && run.autonomy !== "yolo") {
|
|
819
1097
|
const approval = events.filter((event) => event.type === "approval.recorded").at(-1) ?? fail("Terminal run is missing its human approval event.", "HARNESS_ERROR");
|
|
820
1098
|
assertDecisionProjection(run, approval.payload, run.state === "COMPLETE" && !loaded.config.tracking.required ? "COMPLETE" : "AWAITING_AUTHORIZATION");
|
|
821
1099
|
if (!run.humanApproval || run.humanApproval.actor !== "human" || run.humanApproval.verificationDigest !== run.verificationDigest || run.humanApproval.sourceRevision !== run.sourceRevision || run.humanApproval.contractHash !== run.contractHash) fail("Human approval projection is inconsistent with its audit event.", "HARNESS_ERROR");
|
|
@@ -877,13 +1155,77 @@ var retryRun = async ({ configPath }) => {
|
|
|
877
1155
|
const baseline = await sourceSnapshot(loaded.root, loaded.stateDir);
|
|
878
1156
|
const superseded = transition(previousRun, "SUPERSEDED", "Retry superseded the previous run.", "harness");
|
|
879
1157
|
saveRun2(loaded.stateDir, superseded);
|
|
880
|
-
const run = await createRun({ loaded, baseline, supersedes: previousRun.runId, dirtyBaselineAuthorized: previousRun.dirtyBaselineAuthorized
|
|
1158
|
+
const run = await createRun({ loaded, baseline, supersedes: previousRun.runId, dirtyBaselineAuthorized: previousRun.dirtyBaselineAuthorized });
|
|
881
1159
|
const next = transition(run, "IMPLEMENTING", "Retry started after a previous attempt.", "agent");
|
|
882
1160
|
saveRun2(loaded.stateDir, next);
|
|
883
1161
|
setLatest(loaded.stateDir, next);
|
|
884
1162
|
return next;
|
|
885
1163
|
};
|
|
886
1164
|
var cleanTaskArtifacts = (configPath) => cleanConfiguredArtifacts(loadConfig(configPath));
|
|
1165
|
+
|
|
1166
|
+
// src/kernel/capabilities.ts
|
|
1167
|
+
var CAPABILITY_MANIFEST_SCHEMA_VERSION = 1;
|
|
1168
|
+
var CAPABILITY_KINDS = ["kernel", "execution", "adapter", "composition"];
|
|
1169
|
+
var nonEmpty2 = (value, label) => {
|
|
1170
|
+
if (typeof value !== "string") fail(`${label} is required.`, "INVALID_INPUT");
|
|
1171
|
+
const result = value.trim();
|
|
1172
|
+
if (!result) fail(`${label} is required.`, "INVALID_INPUT");
|
|
1173
|
+
return result;
|
|
1174
|
+
};
|
|
1175
|
+
var digest2 = (value, label) => {
|
|
1176
|
+
const result = nonEmpty2(value, label);
|
|
1177
|
+
if (!/^[a-f0-9]{64}$/.test(result)) fail(`${label} must be a lowercase SHA-256 digest.`, "INVALID_INPUT");
|
|
1178
|
+
return result;
|
|
1179
|
+
};
|
|
1180
|
+
var stringList = (value, label) => {
|
|
1181
|
+
if (!Array.isArray(value)) fail(`${label} must be a non-empty string array.`, "INVALID_INPUT");
|
|
1182
|
+
if (!value.length) fail(`${label} must be a non-empty string array.`, "INVALID_INPUT");
|
|
1183
|
+
const items = value;
|
|
1184
|
+
const result = items.map((item, index2) => nonEmpty2(item, `${label}[${index2}]`));
|
|
1185
|
+
if (new Set(result).size !== result.length) fail(`${label} must not contain duplicates.`, "INVALID_INPUT");
|
|
1186
|
+
return result;
|
|
1187
|
+
};
|
|
1188
|
+
var descriptor = (value, index2) => {
|
|
1189
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) fail(`capabilities[${index2}] must be an object.`, "INVALID_INPUT");
|
|
1190
|
+
const candidate = value;
|
|
1191
|
+
const kind = candidate["kind"];
|
|
1192
|
+
if (typeof kind !== "string" || !CAPABILITY_KINDS.includes(kind)) fail(`capabilities[${index2}].kind is invalid.`, "INVALID_INPUT");
|
|
1193
|
+
const dependencies = candidate["dependencies"] === void 0 ? void 0 : stringList(candidate["dependencies"], `capabilities[${index2}].dependencies`);
|
|
1194
|
+
return {
|
|
1195
|
+
id: nonEmpty2(candidate["id"], `capabilities[${index2}].id`),
|
|
1196
|
+
version: nonEmpty2(candidate["version"], `capabilities[${index2}].version`),
|
|
1197
|
+
kind,
|
|
1198
|
+
entryPoint: nonEmpty2(candidate["entryPoint"], `capabilities[${index2}].entryPoint`),
|
|
1199
|
+
exports: stringList(candidate["exports"], `capabilities[${index2}].exports`),
|
|
1200
|
+
...dependencies === void 0 ? {} : { dependencies }
|
|
1201
|
+
};
|
|
1202
|
+
};
|
|
1203
|
+
var manifestBody = (input) => ({
|
|
1204
|
+
type: "agentskit-harness-capability-manifest",
|
|
1205
|
+
schemaVersion: CAPABILITY_MANIFEST_SCHEMA_VERSION,
|
|
1206
|
+
package: nonEmpty2(input.package, "package"),
|
|
1207
|
+
packageVersion: nonEmpty2(input.packageVersion, "packageVersion"),
|
|
1208
|
+
entryPoint: nonEmpty2(input.entryPoint, "entryPoint"),
|
|
1209
|
+
sourceDigest: digest2(input.sourceDigest, "sourceDigest"),
|
|
1210
|
+
capabilities: (Array.isArray(input.capabilities) ? input.capabilities : fail("capabilities must be an array.", "INVALID_INPUT")).map(descriptor)
|
|
1211
|
+
});
|
|
1212
|
+
var createCapabilityManifest = (input) => {
|
|
1213
|
+
const body3 = manifestBody(input);
|
|
1214
|
+
if (!body3.capabilities.length) fail("capabilities must be non-empty.", "INVALID_INPUT");
|
|
1215
|
+
if (new Set(body3.capabilities.map((item) => item.id)).size !== body3.capabilities.length) fail("capability ids must be unique.", "INVALID_INPUT");
|
|
1216
|
+
return { ...body3, digest: hashJson(body3) };
|
|
1217
|
+
};
|
|
1218
|
+
var validateCapabilityManifest = (value) => {
|
|
1219
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) fail("Capability manifest must be an object.", "INVALID_INPUT");
|
|
1220
|
+
const candidate = value;
|
|
1221
|
+
const capabilities = Array.isArray(candidate["capabilities"]) ? candidate["capabilities"] : fail("capabilities must be an array.", "INVALID_INPUT");
|
|
1222
|
+
const body3 = manifestBody({ package: nonEmpty2(candidate["package"], "package"), packageVersion: nonEmpty2(candidate["packageVersion"], "packageVersion"), entryPoint: nonEmpty2(candidate["entryPoint"], "entryPoint"), sourceDigest: digest2(candidate["sourceDigest"], "sourceDigest"), capabilities });
|
|
1223
|
+
if (new Set(body3.capabilities.map((item) => item.id)).size !== body3.capabilities.length) fail("capability ids must be unique.", "INVALID_INPUT");
|
|
1224
|
+
if (candidate["type"] !== body3.type || candidate["schemaVersion"] !== body3.schemaVersion) fail("Capability manifest type or schemaVersion is invalid.", "INVALID_INPUT");
|
|
1225
|
+
const manifestDigest = digest2(candidate["digest"], "digest");
|
|
1226
|
+
if (manifestDigest !== hashJson(body3)) fail("Capability manifest digest is invalid.", "INVALID_INPUT");
|
|
1227
|
+
return { ...body3, digest: manifestDigest };
|
|
1228
|
+
};
|
|
887
1229
|
var index = (root, indexPath) => JSON.parse(readFileSync(resolve(root, indexPath), "utf8"));
|
|
888
1230
|
var text = (entry) => [entry.id, entry.type, entry.title, entry.path, entry.description, entry.body, ...Array.isArray(entry.tags) ? entry.tags : []].filter((value) => typeof value === "string").join(" ").toLowerCase();
|
|
889
1231
|
var sourceHash = (document) => typeof document.contentHash === "string" && document.contentHash.length > 0 ? document.contentHash : hashJson(document);
|
|
@@ -897,23 +1239,1425 @@ var createDocBridgeContextProvider = ({ root, indexPath = ".doc-bridge/index.jso
|
|
|
897
1239
|
id: "doc-bridge",
|
|
898
1240
|
version: "1.0.0",
|
|
899
1241
|
resolve: async (query) => {
|
|
1242
|
+
const started = Date.now();
|
|
900
1243
|
const document = index(root, indexPath);
|
|
901
1244
|
const contentHash = sourceHash(document);
|
|
902
1245
|
const entries = Array.isArray(document.knowledge) ? document.knowledge.filter((value) => typeof value === "object" && value !== null && !Array.isArray(value)).filter((entry) => matches(entry, query)).sort((left, right) => String(left.id ?? "").localeCompare(String(right.id ?? ""))).slice(0, 8) : [];
|
|
903
|
-
const references = entries.flatMap((entry) => typeof entry.id === "string" && typeof entry.path === "string" ? [{ id: entry.id, uri: `doc-bridge://${entry.path}`, ...typeof entry.title === "string" ? { title: entry.title } : {}, contentHash }] : []);
|
|
904
|
-
|
|
1246
|
+
const references = entries.flatMap((entry) => typeof entry.id === "string" && typeof entry.path === "string" ? [{ id: entry.id, uri: `doc-bridge://${entry.path}`, ...typeof entry.title === "string" ? { title: entry.title } : {}, contentHash: typeof entry.contentHash === "string" ? entry.contentHash : contentHash, relevance: 1 }] : []);
|
|
1247
|
+
const telemetry = { status: "measured", durationMs: Date.now() - started, contextReferences: references.length, contextCostTokens: Math.max(1, Math.ceil(JSON.stringify(references).length / 4)) };
|
|
1248
|
+
return { providerId: "doc-bridge", query, references, sourceHash: contentHash, snapshotHash: hashContextSnapshot({ providerId: "doc-bridge", query, references, sourceHash: contentHash }), resolvedAt: (/* @__PURE__ */ new Date()).toISOString(), assurance: "contract-tested", telemetry };
|
|
1249
|
+
}
|
|
1250
|
+
});
|
|
1251
|
+
|
|
1252
|
+
// src/kernel/discovery.ts
|
|
1253
|
+
var required = (value, label) => {
|
|
1254
|
+
if (typeof value !== "string" || !value.trim()) fail(`${label} is required.`, "INVALID_INPUT");
|
|
1255
|
+
return value.trim();
|
|
1256
|
+
};
|
|
1257
|
+
var unique = (values, label) => {
|
|
1258
|
+
if (new Set(values).size !== values.length) fail(`${label} must be unique.`, "INVALID_INPUT");
|
|
1259
|
+
};
|
|
1260
|
+
var validate = (input) => {
|
|
1261
|
+
required(input.issueId, "issueId");
|
|
1262
|
+
required(input.sourceRevision, "sourceRevision");
|
|
1263
|
+
required(input.contractHash, "contractHash");
|
|
1264
|
+
if (!Array.isArray(input.ambiguities)) fail("ambiguities must be an array.", "INVALID_INPUT");
|
|
1265
|
+
unique(input.ambiguities.map((item) => required(item.id, "ambiguity.id")), "ambiguity ids");
|
|
1266
|
+
const assumptions = /* @__PURE__ */ new Map();
|
|
1267
|
+
for (const assumption of input.approvedAssumptions ?? []) {
|
|
1268
|
+
const id2 = required(assumption.id, "assumption.id");
|
|
1269
|
+
if (assumptions.has(id2)) fail("assumption ids must be unique.", "INVALID_INPUT");
|
|
1270
|
+
assumptions.set(id2, { id: id2, policyId: required(assumption.policyId, "assumption.policyId"), resolution: required(assumption.resolution, "assumption.resolution") });
|
|
1271
|
+
}
|
|
1272
|
+
for (const ambiguity of input.ambiguities) {
|
|
1273
|
+
required(ambiguity.question, "ambiguity.question");
|
|
1274
|
+
if (typeof ambiguity.material !== "boolean") fail("ambiguity.material must be boolean.", "INVALID_INPUT");
|
|
1275
|
+
if (!Array.isArray(ambiguity.options) || ambiguity.options.length < 2 || ambiguity.options.length > 4) fail("ambiguity.options must contain 2 to 4 options.", "INVALID_INPUT");
|
|
1276
|
+
unique(ambiguity.options.map((option) => required(option.id, "option.id")), "option ids");
|
|
1277
|
+
for (const option of ambiguity.options) {
|
|
1278
|
+
required(option.summary, "option.summary");
|
|
1279
|
+
required(option.impact, "option.impact");
|
|
1280
|
+
}
|
|
1281
|
+
if (!ambiguity.options.some((option) => option.id === ambiguity.recommendedOptionId)) fail("recommendedOptionId must identify an option.", "INVALID_INPUT");
|
|
1282
|
+
if (!ambiguity.material && (!ambiguity.assumptionId || !assumptions.has(ambiguity.assumptionId))) fail("non-material ambiguity requires an approved assumption.", "INVALID_INPUT");
|
|
1283
|
+
}
|
|
1284
|
+
return { assumptions };
|
|
1285
|
+
};
|
|
1286
|
+
var digest3 = (result) => hashJson(result);
|
|
1287
|
+
var assessDiscovery = (input) => {
|
|
1288
|
+
const { assumptions } = validate(input);
|
|
1289
|
+
const human = input.ambiguities.filter((ambiguity) => ambiguity.material);
|
|
1290
|
+
const decisionLog = input.ambiguities.map((ambiguity) => {
|
|
1291
|
+
if (ambiguity.material) return { ambiguityId: ambiguity.id, kind: "human-decision-required", detail: `Recommendation: ${ambiguity.recommendedOptionId}.` };
|
|
1292
|
+
const assumption = assumptions.get(ambiguity.assumptionId);
|
|
1293
|
+
return { ambiguityId: ambiguity.id, kind: "approved-assumption", detail: assumption.resolution, policyId: assumption.policyId };
|
|
1294
|
+
});
|
|
1295
|
+
const base = {
|
|
1296
|
+
version: 1,
|
|
1297
|
+
issueId: input.issueId,
|
|
1298
|
+
sourceRevision: input.sourceRevision,
|
|
1299
|
+
contractHash: input.contractHash,
|
|
1300
|
+
...input.contextHash ? { contextHash: input.contextHash } : {},
|
|
1301
|
+
status: human.length ? "awaiting-decision" : "ready",
|
|
1302
|
+
...human.length ? { packet: {
|
|
1303
|
+
issueId: input.issueId,
|
|
1304
|
+
contractHash: input.contractHash,
|
|
1305
|
+
sourceRevision: input.sourceRevision,
|
|
1306
|
+
...input.contextHash ? { contextHash: input.contextHash } : {},
|
|
1307
|
+
decisions: human.map((ambiguity) => ({ id: ambiguity.id, question: ambiguity.question, options: ambiguity.options, recommendedOptionId: ambiguity.recommendedOptionId }))
|
|
1308
|
+
} } : {},
|
|
1309
|
+
decisionLog
|
|
1310
|
+
};
|
|
1311
|
+
return { ...base, digest: digest3(base) };
|
|
1312
|
+
};
|
|
1313
|
+
var isDiscoveryCurrent = (result, current) => {
|
|
1314
|
+
const reasons = [];
|
|
1315
|
+
if (result.sourceRevision !== current.sourceRevision) reasons.push("source");
|
|
1316
|
+
if (result.contractHash !== current.contractHash) reasons.push("contract");
|
|
1317
|
+
if ((result.contextHash ?? "") !== (current.contextHash ?? "")) reasons.push("context");
|
|
1318
|
+
return { current: reasons.length === 0, reasons };
|
|
1319
|
+
};
|
|
1320
|
+
|
|
1321
|
+
// src/kernel/wip.ts
|
|
1322
|
+
var WIP_STATES = ["ready", "implementing", "blocked", "awaiting-decision", "awaiting-acceptance", "done", "cancelled"];
|
|
1323
|
+
var terminal = /* @__PURE__ */ new Set(["done", "cancelled"]);
|
|
1324
|
+
var required2 = (value, label) => {
|
|
1325
|
+
if (typeof value !== "string" || !value.trim()) fail(`${label} is required.`, "INVALID_INPUT");
|
|
1326
|
+
return value.trim();
|
|
1327
|
+
};
|
|
1328
|
+
var assessWip = ({ entries, candidate, maxInFlight = 3 }) => {
|
|
1329
|
+
if (!Array.isArray(entries)) fail("entries must be an array.", "INVALID_INPUT");
|
|
1330
|
+
if (!Number.isInteger(maxInFlight) || maxInFlight < 1) fail("maxInFlight must be a positive integer.", "INVALID_INPUT");
|
|
1331
|
+
const candidateId = required2(candidate.issueId, "candidate.issueId");
|
|
1332
|
+
if (candidate.kind !== "new" && candidate.kind !== "resume") fail("candidate.kind must be new or resume.", "INVALID_INPUT");
|
|
1333
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1334
|
+
const counts = Object.fromEntries(WIP_STATES.map((state) => [state, 0]));
|
|
1335
|
+
for (const entry of entries) {
|
|
1336
|
+
const id2 = required2(entry.issueId, "entry.issueId");
|
|
1337
|
+
if (ids.has(id2)) fail("entry issueIds must be unique.", "INVALID_INPUT");
|
|
1338
|
+
ids.add(id2);
|
|
1339
|
+
if (!WIP_STATES.includes(entry.state)) fail(`Unknown WIP state: ${entry.state}.`, "INVALID_INPUT");
|
|
1340
|
+
counts[entry.state] += 1;
|
|
1341
|
+
}
|
|
1342
|
+
const inFlight = entries.filter((entry) => !terminal.has(entry.state));
|
|
1343
|
+
const existing = entries.find((entry) => entry.issueId === candidateId);
|
|
1344
|
+
if (candidate.kind === "resume") {
|
|
1345
|
+
if (!existing || terminal.has(existing.state)) return { decision: "hold", inFlight, counts, reason: "A resume requires an existing non-terminal issue." };
|
|
1346
|
+
return { decision: "admit", inFlight, counts, reason: "A resume keeps its existing WIP reservation and takes priority over new work." };
|
|
1347
|
+
}
|
|
1348
|
+
if (existing) return { decision: "hold", inFlight, counts, reason: "A new admission cannot reuse an existing issue id." };
|
|
1349
|
+
if (inFlight.length >= maxInFlight) return { decision: "hold", inFlight, counts, reason: `WIP limit ${maxInFlight} reached; blocked and awaiting-human work still count.` };
|
|
1350
|
+
return { decision: "admit", inFlight, counts, reason: `WIP slot available (${inFlight.length}/${maxInFlight}).` };
|
|
1351
|
+
};
|
|
1352
|
+
|
|
1353
|
+
// src/kernel/experiment.ts
|
|
1354
|
+
var required3 = (value, label) => {
|
|
1355
|
+
if (typeof value !== "string" || !value.trim()) fail(`${label} is required.`, "INVALID_INPUT");
|
|
1356
|
+
return value.trim();
|
|
1357
|
+
};
|
|
1358
|
+
var comparable = (candidate, baseline) => {
|
|
1359
|
+
for (const key of ["sourceRevision", "contractHash", "provider", "model", "configurationHash"]) {
|
|
1360
|
+
if (candidate[key] !== baseline[key]) fail(`Candidates must share ${key}.`, "INVALID_INPUT");
|
|
1361
|
+
}
|
|
1362
|
+
};
|
|
1363
|
+
var selectRuntime = (candidates) => {
|
|
1364
|
+
if (!Array.isArray(candidates) || candidates.length < 2) fail("At least two runtime candidates are required.", "INVALID_INPUT");
|
|
1365
|
+
const names2 = /* @__PURE__ */ new Set();
|
|
1366
|
+
for (const candidate of candidates) {
|
|
1367
|
+
const runtime = required3(candidate.runtime, "candidate.runtime");
|
|
1368
|
+
if (names2.has(runtime)) fail("candidate.runtime values must be unique.", "INVALID_INPUT");
|
|
1369
|
+
names2.add(runtime);
|
|
1370
|
+
for (const key of ["sourceRevision", "contractHash", "provider", "model", "configurationHash"]) required3(candidate[key], `candidate.${key}`);
|
|
1371
|
+
for (const key of ["humanMinutes", "durationMs", "cost"]) if (!Number.isFinite(candidate[key]) || candidate[key] < 0) fail(`candidate.${key} must be a non-negative number.`, "INVALID_INPUT");
|
|
1372
|
+
comparable(candidate, candidates[0]);
|
|
1373
|
+
}
|
|
1374
|
+
const eligible = candidates.filter((candidate) => candidate.hardGatesPassed);
|
|
1375
|
+
if (!eligible.length) return { decision: "blocked", eligible, reason: "No runtime passed every hard gate." };
|
|
1376
|
+
const selected = [...eligible].sort((left, right) => left.humanMinutes - right.humanMinutes || left.durationMs - right.durationMs || left.cost - right.cost || (left.runtime === "orca" ? -1 : right.runtime === "orca" ? 1 : left.runtime.localeCompare(right.runtime)))[0];
|
|
1377
|
+
return { decision: "selected", selected, eligible, reason: "Selected by human minutes, duration, cost, then Orca tie-break." };
|
|
1378
|
+
};
|
|
1379
|
+
|
|
1380
|
+
// src/kernel/workflow.ts
|
|
1381
|
+
var validId2 = (id2) => {
|
|
1382
|
+
if (typeof id2 !== "string" || !id2.trim()) fail("Workflow node id must be non-empty.", "INVALID_INPUT");
|
|
1383
|
+
return id2.trim();
|
|
1384
|
+
};
|
|
1385
|
+
var levels = (nodes) => {
|
|
1386
|
+
const byId = new Map(nodes.map((node) => [validId2(node.id), node]));
|
|
1387
|
+
if (byId.size !== nodes.length) fail("Workflow node ids must be unique.", "INVALID_INPUT");
|
|
1388
|
+
const remaining = new Set(byId.keys());
|
|
1389
|
+
const completed = /* @__PURE__ */ new Set();
|
|
1390
|
+
const result = [];
|
|
1391
|
+
while (remaining.size) {
|
|
1392
|
+
const ready = [...remaining].sort().map((id2) => byId.get(id2)).filter((node) => (node.dependsOn ?? []).every((dependency) => completed.has(dependency)));
|
|
1393
|
+
if (!ready.length) fail("Workflow contains an unknown dependency or cycle.", "INVALID_INPUT");
|
|
1394
|
+
result.push(ready);
|
|
1395
|
+
for (const node of ready) {
|
|
1396
|
+
remaining.delete(node.id);
|
|
1397
|
+
completed.add(node.id);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
return result;
|
|
1401
|
+
};
|
|
1402
|
+
var runWorkflow = async (nodes, options) => {
|
|
1403
|
+
if (!Number.isInteger(options.maxConcurrency) || options.maxConcurrency < 1) fail("maxConcurrency must be a positive integer.", "INVALID_INPUT");
|
|
1404
|
+
const started = Date.now();
|
|
1405
|
+
const results = {};
|
|
1406
|
+
const order = [];
|
|
1407
|
+
let peakConcurrency = 0;
|
|
1408
|
+
for (const level of levels(nodes)) {
|
|
1409
|
+
const remaining = [...level];
|
|
1410
|
+
while (remaining.length) {
|
|
1411
|
+
const batch = [];
|
|
1412
|
+
const keys = /* @__PURE__ */ new Set();
|
|
1413
|
+
const limit = options.currentConcurrency ? options.currentConcurrency() : options.maxConcurrency;
|
|
1414
|
+
if (!Number.isInteger(limit) || limit < 1) fail("currentConcurrency must return a positive integer.", "INVALID_INPUT");
|
|
1415
|
+
for (const node of remaining) {
|
|
1416
|
+
const key = node.mutationKey?.trim();
|
|
1417
|
+
if (batch.length >= limit || key && keys.has(key)) continue;
|
|
1418
|
+
batch.push(node);
|
|
1419
|
+
if (key) keys.add(key);
|
|
1420
|
+
}
|
|
1421
|
+
if (!batch.length) fail("Workflow could not schedule a mutation batch.", "INVALID_INPUT");
|
|
1422
|
+
peakConcurrency = Math.max(peakConcurrency, batch.length);
|
|
1423
|
+
const values = await Promise.all(batch.map((node) => node.run()));
|
|
1424
|
+
batch.forEach((node, index2) => {
|
|
1425
|
+
results[node.id] = values[index2];
|
|
1426
|
+
order.push(node.id);
|
|
1427
|
+
});
|
|
1428
|
+
for (const node of batch) remaining.splice(remaining.indexOf(node), 1);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
return { results, order, peakConcurrency, criticalPathMs: Date.now() - started };
|
|
1432
|
+
};
|
|
1433
|
+
|
|
1434
|
+
// src/delivery/review.ts
|
|
1435
|
+
var runAdversarialReview = async ({ lenses, reviewer, binding: binding2, maxConcurrency = 3 }) => {
|
|
1436
|
+
if (!Array.isArray(lenses) || lenses.length === 0) fail("At least one review lens is required.", "INVALID_INPUT");
|
|
1437
|
+
if (!Number.isInteger(maxConcurrency) || maxConcurrency < 1) fail("maxConcurrency must be a positive integer.", "INVALID_INPUT");
|
|
1438
|
+
const normalized = lenses.map((lens) => {
|
|
1439
|
+
if (typeof lens !== "object" || lens === null || Array.isArray(lens) || typeof lens.id !== "string" || !lens.id.trim()) fail("Review lens id is required.", "INVALID_INPUT");
|
|
1440
|
+
const maxAttempts = lens.maxAttempts ?? 1;
|
|
1441
|
+
if (!Number.isInteger(maxAttempts) || maxAttempts < 1 || maxAttempts > 3) fail("Review lens maxAttempts must be between 1 and 3.", "INVALID_INPUT");
|
|
1442
|
+
return { id: lens.id.trim(), maxAttempts };
|
|
1443
|
+
});
|
|
1444
|
+
if (new Set(normalized.map((lens) => lens.id)).size !== normalized.length) fail("Review lens ids must be unique.", "INVALID_INPUT");
|
|
1445
|
+
const workflow = await runWorkflow(normalized.map((lens) => ({ id: lens.id, run: async () => {
|
|
1446
|
+
let last = { status: "unverified", reason: "Reviewer returned no verdict." };
|
|
1447
|
+
for (let attempt = 1; attempt <= lens.maxAttempts; attempt += 1) {
|
|
1448
|
+
try {
|
|
1449
|
+
const verdict = await reviewer(lens, attempt);
|
|
1450
|
+
if (!verdict || !["pass", "finding", "unverified"].includes(verdict.status)) return { status: "unverified", reason: "Reviewer returned an invalid verdict." };
|
|
1451
|
+
last = verdict;
|
|
1452
|
+
if (verdict.status !== "unverified" || verdict.retryable !== true) return verdict;
|
|
1453
|
+
} catch (error) {
|
|
1454
|
+
last = { status: "unverified", reason: error instanceof Error ? error.message : String(error) };
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
return last;
|
|
1458
|
+
} })), { maxConcurrency });
|
|
1459
|
+
const verdicts = Object.fromEntries(Object.entries(workflow.results).sort(([left], [right]) => left.localeCompare(right)));
|
|
1460
|
+
const reasons = Object.entries(verdicts).flatMap(([id2, verdict]) => verdict.status === "pass" ? [] : verdict.status === "finding" && (verdict.evidence?.trim() || verdict.reproduction?.trim()) ? [`${id2} found an issue: ${verdict.reason ?? "evidence recorded"}.`] : [`${id2} is unverified or lacks reproducible evidence.`]);
|
|
1461
|
+
const base = { verdicts, reasons, binding: binding2, peakConcurrency: workflow.peakConcurrency };
|
|
1462
|
+
return { decision: reasons.length ? "blocked" : "approved", ...base, digest: hashJson(base) };
|
|
1463
|
+
};
|
|
1464
|
+
|
|
1465
|
+
// src/delivery/index.ts
|
|
1466
|
+
var required4 = (value, label) => {
|
|
1467
|
+
if (typeof value !== "string" || !value.trim()) fail(`${label} is required.`, "INVALID_INPUT");
|
|
1468
|
+
return value.trim();
|
|
1469
|
+
};
|
|
1470
|
+
var criteriaFor = (criteria, gate) => {
|
|
1471
|
+
if (!Array.isArray(criteria)) fail("criteria must be an array.", "INVALID_INPUT");
|
|
1472
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1473
|
+
for (const criterion of criteria) {
|
|
1474
|
+
const id2 = required4(criterion.id, "criterion.id");
|
|
1475
|
+
if (ids.has(id2)) fail("criterion ids must be unique.", "INVALID_INPUT");
|
|
1476
|
+
ids.add(id2);
|
|
1477
|
+
if (!["G2", "G3", "G4", "G5"].includes(criterion.gate)) fail("criterion.gate is invalid.", "INVALID_INPUT");
|
|
1478
|
+
if (!["passed", "failed", "pending", "not-applicable"].includes(criterion.status)) fail("criterion.status is invalid.", "INVALID_INPUT");
|
|
1479
|
+
if (criterion.status === "not-applicable" && !criterion.reason?.trim()) fail("not-applicable criteria require a reason.", "INVALID_INPUT");
|
|
1480
|
+
}
|
|
1481
|
+
return criteria.filter((criterion) => criterion.gate === gate);
|
|
1482
|
+
};
|
|
1483
|
+
var binding = (value) => ({ candidateRevision: required4(value.candidateRevision, "binding.candidateRevision"), contractHash: required4(value.contractHash, "binding.contractHash"), configHash: required4(value.configHash, "binding.configHash") });
|
|
1484
|
+
var assessed = (gate, decision, reasons, current) => {
|
|
1485
|
+
const base = { gate, decision, reasons, binding: binding(current) };
|
|
1486
|
+
return { ...base, digest: hashJson(base) };
|
|
1487
|
+
};
|
|
1488
|
+
var assessPreflight = ({ criteria, repairAttempts = 0, implementerId, reviewerId, reviewKind, reviewApproved, binding: current }) => {
|
|
1489
|
+
required4(implementerId, "implementerId");
|
|
1490
|
+
if (!Number.isInteger(repairAttempts) || repairAttempts < 0) fail("repairAttempts must be a non-negative integer.", "INVALID_INPUT");
|
|
1491
|
+
const g2 = criteriaFor(criteria, "G2");
|
|
1492
|
+
const reasons = [
|
|
1493
|
+
...g2.length ? [] : ["No G2 criteria are defined."],
|
|
1494
|
+
...g2.filter((criterion) => criterion.status === "failed" || criterion.status === "pending").map((criterion) => `${criterion.id} is ${criterion.status}.`),
|
|
1495
|
+
...reviewApproved && reviewerId && reviewerId !== implementerId && reviewKind === "adversarial" ? [] : ["An approved adversarial review by a reviewer different from the implementer is required."],
|
|
1496
|
+
...repairAttempts <= 2 ? [] : ["The two-repair limit was exceeded; preserve diagnostics and return blocked."]
|
|
1497
|
+
];
|
|
1498
|
+
return assessed("G2", reasons.length ? "blocked" : "approved", reasons, current);
|
|
1499
|
+
};
|
|
1500
|
+
var composePullRequest = ({ draft, g2, remote }) => {
|
|
1501
|
+
for (const [label, value] of Object.entries({ issueId: draft.issueId, candidateRevision: draft.candidateRevision, contractHash: draft.contractHash, configHash: draft.configHash, g2Digest: draft.g2Digest, risk: draft.risk, rollback: draft.rollback })) required4(value, `draft.${label}`);
|
|
1502
|
+
if (g2.gate !== "G2" || g2.decision !== "approved" || g2.digest !== draft.g2Digest || g2.binding.candidateRevision !== draft.candidateRevision || g2.binding.contractHash !== draft.contractHash || g2.binding.configHash !== draft.configHash) return { decision: "blocked", reason: "A current approved G2 assessment is required before a PR can be created.", idempotencyKey: hashJson(draft) };
|
|
1503
|
+
const idempotencyKey = hashJson({ issueId: draft.issueId, contractHash: draft.contractHash, action: "pull-request", revision: draft.candidateRevision });
|
|
1504
|
+
if (remote?.state === "uncertain") return { decision: "blocked", reason: "Remote PR state is uncertain; reconcile before retrying.", idempotencyKey };
|
|
1505
|
+
if (remote?.state === "confirmed") {
|
|
1506
|
+
if (remote.candidateRevision !== draft.candidateRevision || !remote.url) return { decision: "blocked", reason: "Confirmed remote PR does not match the candidate revision.", idempotencyKey };
|
|
1507
|
+
return { decision: "reuse", reason: "The idempotent remote PR already exists for this candidate revision.", idempotencyKey };
|
|
1508
|
+
}
|
|
1509
|
+
const body3 = [`## Contract`, `- Issue: ${draft.issueId}`, `- Candidate: ${draft.candidateRevision}`, `- Contract: ${draft.contractHash}`, `- Configuration: ${draft.configHash}`, "", "## G2 evidence", ...draft.evidence.map((item) => `- ${item}`), "", "## Documentation", ...draft.documentation.map((item) => `- ${item}`), "", "## Risk and rollback", `- Risk: ${draft.risk}`, `- Rollback: ${draft.rollback}`, "", "## Later gates", ...draft.pendingCriteria.length ? draft.pendingCriteria.map((item) => `- Pending: ${item}`) : ["- None."]].join("\n");
|
|
1510
|
+
return { decision: "create", body: body3, reason: "G2 is current and the remote PR is absent.", idempotencyKey };
|
|
1511
|
+
};
|
|
1512
|
+
var createPullRequestApproval = ({ body: body3, metadata, approvedBy, candidateRevision, contractHash, configHash }) => {
|
|
1513
|
+
if (approvedBy !== "human") fail("Pull request approval requires a human actor.", "HUMAN_APPROVAL_REQUIRED");
|
|
1514
|
+
const normalizedBody = required4(body3, "PR body");
|
|
1515
|
+
const binding2 = { approvedBy, candidateRevision: required4(candidateRevision, "candidateRevision"), contractHash: required4(contractHash, "contractHash"), configHash: required4(configHash, "configHash"), bodyHash: hashJson(normalizedBody), metadataHash: hashJson(metadata) };
|
|
1516
|
+
return { ...binding2, digest: hashJson(binding2) };
|
|
1517
|
+
};
|
|
1518
|
+
var verifyPullRequestApproval = ({ approval, body: body3, metadata, candidateRevision, contractHash, configHash }) => {
|
|
1519
|
+
const expected = createPullRequestApproval({ body: body3, metadata, approvedBy: "human", candidateRevision, contractHash, configHash });
|
|
1520
|
+
if (approval.digest !== expected.digest || approval.bodyHash !== expected.bodyHash || approval.metadataHash !== expected.metadataHash) fail("Approved pull request content or metadata changed.", "STALE");
|
|
1521
|
+
return approval;
|
|
1522
|
+
};
|
|
1523
|
+
var assessQaTransition = ({ featureValidated, g5, qaPassed, issue }) => {
|
|
1524
|
+
required4(issue, "issue");
|
|
1525
|
+
const base = { issue, featureValidated, g5: g5.digest, qaPassed };
|
|
1526
|
+
if (!featureValidated || g5.gate !== "G5" || g5.decision !== "approved") return { decision: "blocked", target: "verification", invalidatesDownstream: false, reason: "Feature validation and approved G5 acceptance are required before moving the issue to QA.", idempotencyKey: hashJson(base) };
|
|
1527
|
+
if (!qaPassed) return { decision: "return-to-verification", target: "verification", invalidatesDownstream: true, reason: "QA failed; downstream evidence is invalidated and verification must be repeated.", idempotencyKey: hashJson(base) };
|
|
1528
|
+
return { decision: "move-to-qa", target: "qa", invalidatesDownstream: false, reason: "Feature validation and G5 acceptance are current.", idempotencyKey: hashJson(base) };
|
|
1529
|
+
};
|
|
1530
|
+
var assessIntegration = ({ g2, candidateRevision, evidenceRevision, contractHash, configHash, ci }) => {
|
|
1531
|
+
required4(candidateRevision, "candidateRevision");
|
|
1532
|
+
required4(evidenceRevision, "evidenceRevision");
|
|
1533
|
+
if (!["passed", "failed", "pending", "not-applicable"].includes(ci)) fail("ci is invalid.", "INVALID_INPUT");
|
|
1534
|
+
const reasons = [
|
|
1535
|
+
...g2.gate === "G2" && g2.decision === "approved" ? [] : ["G2 is not approved."],
|
|
1536
|
+
...g2.binding.candidateRevision === candidateRevision && g2.binding.contractHash === contractHash && g2.binding.configHash === configHash ? [] : ["G2 is not bound to the current candidate, contract, and configuration."],
|
|
1537
|
+
...candidateRevision === evidenceRevision ? [] : ["Candidate revision changed; G3 evidence must be revalidated."],
|
|
1538
|
+
...ci === "passed" ? [] : [`Integration CI is ${ci}.`]
|
|
1539
|
+
];
|
|
1540
|
+
return assessed("G3", reasons.length ? "blocked" : "approved", reasons, { candidateRevision, contractHash, configHash });
|
|
1541
|
+
};
|
|
1542
|
+
var assessWorktreeCleanup = ({ branch, candidateRevision, contractHash, configHash, remoteBranchRevision, remotePr, integration }) => {
|
|
1543
|
+
required4(branch, "branch");
|
|
1544
|
+
required4(candidateRevision, "candidateRevision");
|
|
1545
|
+
if (remotePr === "uncertain") return { decision: "preserve", reason: "Remote PR state is uncertain; preserve the worktree for reconciliation." };
|
|
1546
|
+
if (remotePr !== "confirmed") return { decision: "preserve", reason: "No confirmed remote PR exists; preserve the worktree." };
|
|
1547
|
+
if (remoteBranchRevision !== candidateRevision) return { decision: "preserve", reason: "Remote branch SHA does not match the candidate revision." };
|
|
1548
|
+
if (integration.gate !== "G3" || integration.decision !== "approved") return { decision: "preserve", reason: "G3 is not approved." };
|
|
1549
|
+
if (integration.binding.candidateRevision !== candidateRevision || integration.binding.contractHash !== contractHash || integration.binding.configHash !== configHash) return { decision: "preserve", reason: "G3 is not bound to the current candidate, contract, and configuration." };
|
|
1550
|
+
return { decision: "clean", reason: "Remote branch, PR, and G3 evidence are confirmed for the candidate revision." };
|
|
1551
|
+
};
|
|
1552
|
+
var profileReasons = (profile) => Object.entries(profile).flatMap(([key, value]) => Array.isArray(value) ? value.length ? [] : [`RepositoryProfile.${key} is required.`] : typeof value === "string" && value.trim() ? [] : [`RepositoryProfile.${key} is required.`]);
|
|
1553
|
+
var assessProduction = ({ profile, integration, artifact, isolated, acceptanceArtifact, lowRisk = true, observationMinutes, technicalPassed, evidence, containmentPreauthorized, containmentAction, linkedDefect }) => {
|
|
1554
|
+
required4(artifact, "artifact");
|
|
1555
|
+
if (!Number.isFinite(observationMinutes) || observationMinutes < 0) fail("observationMinutes must be non-negative.", "INVALID_INPUT");
|
|
1556
|
+
const evidenceReasons = [required4(evidence.tenant, "evidence.tenant"), required4(evidence.realFlow, "evidence.realFlow"), ...Array.isArray(evidence.logs) && evidence.logs.length ? [] : ["Production evidence requires logs."], ...Array.isArray(evidence.metrics) && evidence.metrics.length ? [] : ["Production evidence requires metrics."]].filter((item) => item.startsWith("Production evidence"));
|
|
1557
|
+
const reasons = [
|
|
1558
|
+
...profileReasons(profile),
|
|
1559
|
+
...integration.gate === "G3" && integration.decision === "approved" ? [] : ["G3 is not approved."],
|
|
1560
|
+
...evidenceReasons,
|
|
1561
|
+
...isolated || acceptanceArtifact === artifact ? [] : ["Exposure requires isolation or acceptance linked to this artifact version."],
|
|
1562
|
+
...technicalPassed ? [] : [containmentPreauthorized && containmentAction?.trim() && linkedDefect?.trim() ? "Technical validation failed; pre-authorized containment and linked defect are recorded." : containmentPreauthorized ? "Technical validation failed; containment action and linked defect are required." : "Technical validation failed."],
|
|
1563
|
+
...lowRisk && observationMinutes < 15 ? ["Low-risk production validation requires a 15-minute observation window."] : []
|
|
1564
|
+
];
|
|
1565
|
+
return assessed("G4", reasons.length ? "blocked" : "approved", reasons, integration.binding);
|
|
1566
|
+
};
|
|
1567
|
+
var assessAcceptance = ({ production, acceptanceRequired, accepted, notApplicableReason, materialChange }) => {
|
|
1568
|
+
const reasons = [
|
|
1569
|
+
...production.gate === "G4" && production.decision === "approved" ? [] : ["G4 is not approved."],
|
|
1570
|
+
...materialChange ? ["A material change invalidated acceptance; return to the affected gate."] : []
|
|
1571
|
+
];
|
|
1572
|
+
if (reasons.length) return assessed("G5", "blocked", reasons, production.binding);
|
|
1573
|
+
if (acceptanceRequired && !accepted) return assessed("G5", "awaiting-acceptance", ["Business or UX acceptance is still required."], production.binding);
|
|
1574
|
+
if (!acceptanceRequired && !notApplicableReason?.trim()) return assessed("G5", "blocked", ["Acceptance marked not applicable requires a contractual reason."], production.binding);
|
|
1575
|
+
return assessed("G5", "approved", acceptanceRequired ? [] : [`Acceptance is not applicable: ${notApplicableReason}.`], production.binding);
|
|
1576
|
+
};
|
|
1577
|
+
|
|
1578
|
+
// src/kernel/pilot.ts
|
|
1579
|
+
var required5 = (value, label) => {
|
|
1580
|
+
if (typeof value !== "string" || !value.trim()) fail(`${label} is required.`, "INVALID_INPUT");
|
|
1581
|
+
return value.trim();
|
|
1582
|
+
};
|
|
1583
|
+
var assessPilot = (manifest) => {
|
|
1584
|
+
required5(manifest.policyHash, "policyHash");
|
|
1585
|
+
required5(manifest.baselineReference, "baselineReference");
|
|
1586
|
+
if (!Array.isArray(manifest.entries)) fail("entries must be an array.", "INVALID_INPUT");
|
|
1587
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1588
|
+
const reasons = [];
|
|
1589
|
+
const included = [];
|
|
1590
|
+
for (const entry of manifest.entries) {
|
|
1591
|
+
const issueId = required5(entry.issueId, "entry.issueId");
|
|
1592
|
+
if (ids.has(issueId)) fail("entry issueIds must be unique; an issue cannot be substituted in the same pilot.", "INVALID_INPUT");
|
|
1593
|
+
ids.add(issueId);
|
|
1594
|
+
if (!["normal", "incident", "sensitive"].includes(entry.classification)) fail("entry.classification is invalid.", "INVALID_INPUT");
|
|
1595
|
+
if (!["included", "excluded", "aborted"].includes(entry.status)) fail("entry.status is invalid.", "INVALID_INPUT");
|
|
1596
|
+
if (entry.status !== "included" && !entry.reason?.trim()) reasons.push(`${issueId} is ${entry.status} without an auditable reason.`);
|
|
1597
|
+
if (entry.status === "included") {
|
|
1598
|
+
included.push(issueId);
|
|
1599
|
+
if (entry.classification !== "normal") reasons.push(`${issueId} is ${entry.classification}; only normal issues can enter the pilot.`);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
if (included.length !== 10) reasons.push(`Pilot requires exactly 10 included issues; found ${included.length}.`);
|
|
1603
|
+
const base = { decision: reasons.length ? "blocked" : "ready", included, reasons };
|
|
1604
|
+
return { ...base, digest: hashJson({ ...manifest, ...base }) };
|
|
1605
|
+
};
|
|
1606
|
+
var IMPROVEMENT_CYCLE_STEPS = ["adversarial-review", "g2-preflight", "baseline-record", "pilot-execution", "comparison"];
|
|
1607
|
+
var nonEmpty3 = (value, label) => {
|
|
1608
|
+
if (typeof value !== "string" || !value.trim()) return fail(`${label} must be a non-empty string.`, "INVALID_INPUT");
|
|
1609
|
+
return value.trim();
|
|
1610
|
+
};
|
|
1611
|
+
var validateMetrics = (metrics, index2) => {
|
|
1612
|
+
if (metrics === void 0) return void 0;
|
|
1613
|
+
for (const [key, value] of Object.entries(metrics)) {
|
|
1614
|
+
if (value !== void 0 && (typeof value !== "number" || !Number.isFinite(value) || value < 0)) return fail(`iterations[${index2}].metrics.${key} must be a non-negative number.`, "INVALID_INPUT");
|
|
1615
|
+
}
|
|
1616
|
+
return metrics;
|
|
1617
|
+
};
|
|
1618
|
+
var validateIteration = (iteration, index2) => {
|
|
1619
|
+
if (typeof iteration !== "object" || iteration === null || Array.isArray(iteration)) return fail(`iterations[${index2}] must be an object.`, "INVALID_INPUT");
|
|
1620
|
+
if (!Number.isInteger(iteration.iteration) || iteration.iteration < 1) return fail(`iterations[${index2}].iteration must be a positive integer.`, "INVALID_INPUT");
|
|
1621
|
+
if (!Array.isArray(iteration.steps) || iteration.steps.length !== IMPROVEMENT_CYCLE_STEPS.length) return fail(`iterations[${index2}].steps must contain the five cycle steps exactly once, in order.`, "INVALID_INPUT");
|
|
1622
|
+
iteration.steps.forEach((result, stepIndex) => {
|
|
1623
|
+
if (typeof result !== "object" || result === null || Array.isArray(result)) return fail(`iterations[${index2}].steps[${stepIndex}] must be an object.`, "INVALID_INPUT");
|
|
1624
|
+
if (result.step !== IMPROVEMENT_CYCLE_STEPS[stepIndex]) return fail(`iterations[${index2}].steps[${stepIndex}] must be ${IMPROVEMENT_CYCLE_STEPS[stepIndex]}.`, "INVALID_INPUT");
|
|
1625
|
+
if (!["passed", "failed", "blocked", "pending"].includes(result.status)) return fail(`iterations[${index2}].steps[${stepIndex}].status is invalid.`, "INVALID_INPUT");
|
|
1626
|
+
if (result.status !== "passed" && !nonEmpty3(result.reason, `iterations[${index2}].steps[${stepIndex}].reason`)) return fail(`iterations[${index2}].steps[${stepIndex}].reason is required when the step does not pass.`, "INVALID_INPUT");
|
|
1627
|
+
});
|
|
1628
|
+
if (iteration.adjustment !== void 0) nonEmpty3(iteration.adjustment, `iterations[${index2}].adjustment`);
|
|
1629
|
+
return { ...iteration, metrics: validateMetrics(iteration.metrics, index2) };
|
|
1630
|
+
};
|
|
1631
|
+
var assessImprovementCycle = (input) => {
|
|
1632
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) return fail("cycle input must be an object.", "INVALID_INPUT");
|
|
1633
|
+
const cycleId = nonEmpty3(input.cycleId, "cycleId");
|
|
1634
|
+
if (!Number.isInteger(input.maxIterations) || input.maxIterations < 1) return fail("maxIterations must be a positive integer.", "INVALID_INPUT");
|
|
1635
|
+
if (!Array.isArray(input.iterations) || input.iterations.length < 1) return fail("iterations must be non-empty.", "INVALID_INPUT");
|
|
1636
|
+
if (input.iterations.length > input.maxIterations) return fail("iterations cannot exceed maxIterations.", "INVALID_INPUT");
|
|
1637
|
+
const iterations = input.iterations.map(validateIteration);
|
|
1638
|
+
iterations.forEach((iteration, index2) => {
|
|
1639
|
+
if (iteration.iteration !== index2 + 1) return fail("iterations must be sequential and start at 1.", "INVALID_INPUT");
|
|
1640
|
+
if (index2 > 0 && iterations[index2 - 1]?.steps.every((step) => step.status === "passed")) return fail("a completed cycle cannot have later iterations.", "INVALID_INPUT");
|
|
1641
|
+
if (index2 < iterations.length - 1 && !iteration.adjustment) return fail(`iterations[${index2}].adjustment is required before repeating.`, "INVALID_INPUT");
|
|
1642
|
+
});
|
|
1643
|
+
const matrix = iterations.map((iteration) => {
|
|
1644
|
+
const statuses = Object.fromEntries(iteration.steps.map((step) => [step.step, step.status]));
|
|
1645
|
+
const passedSteps = iteration.steps.filter((step) => step.status === "passed").length;
|
|
1646
|
+
return { iteration: iteration.iteration, passedSteps, totalSteps: IMPROVEMENT_CYCLE_STEPS.length, passRate: Number((passedSteps / IMPROVEMENT_CYCLE_STEPS.length).toFixed(4)), statuses, ...iteration.adjustment ? { adjustment: iteration.adjustment } : {}, ...iteration.metrics ? { metrics: iteration.metrics } : {} };
|
|
1647
|
+
});
|
|
1648
|
+
const latest = iterations[iterations.length - 1];
|
|
1649
|
+
const complete = latest.steps.every((step) => step.status === "passed");
|
|
1650
|
+
const reasons = complete ? ["All five cycle steps passed."] : iterations.length >= input.maxIterations ? ["Maximum cycle iterations reached; human adjustment is required."] : latest.adjustment ? ["A failed or blocked step remains; repeat with the recorded adjustment."] : ["A failed or blocked step remains; an explicit adjustment is required before repeating."];
|
|
1651
|
+
const decision = complete ? "complete" : iterations.length >= input.maxIterations || !latest.adjustment ? "blocked" : "repeat";
|
|
1652
|
+
const result = { type: "agentskit-harness-improvement-cycle", cycleId, decision, ...decision === "repeat" ? { nextIteration: latest.iteration + 1 } : {}, reasons, matrix };
|
|
1653
|
+
const digest6 = createHash("sha256").update(JSON.stringify(result)).digest("hex");
|
|
1654
|
+
return { ...result, digest: digest6 };
|
|
1655
|
+
};
|
|
1656
|
+
|
|
1657
|
+
// src/kernel/eval.ts
|
|
1658
|
+
var EVAL_MANIFEST_SCHEMA_VERSION = 1;
|
|
1659
|
+
var EVAL_LAYERS = ["contract", "deterministic", "integration", "quality", "regression", "resource"];
|
|
1660
|
+
var EVAL_COMPONENTS = ["core", "workflow", "memory", "cache", "doc-bridge", "agent-model", "orca-worktree", "runtime", "code-review", "github-linear", "eval-metrics"];
|
|
1661
|
+
var nonEmpty4 = (value, label) => {
|
|
1662
|
+
const text7 = typeof value === "string" ? value.trim() : "";
|
|
1663
|
+
if (!text7) fail(`${label} is required.`, "INVALID_INPUT");
|
|
1664
|
+
return text7;
|
|
1665
|
+
};
|
|
1666
|
+
var digest4 = (value, label) => {
|
|
1667
|
+
const result = nonEmpty4(value, label);
|
|
1668
|
+
if (!/^[a-f0-9]{64}$/.test(result)) fail(`${label} must be a lowercase SHA-256 digest.`, "INVALID_INPUT");
|
|
1669
|
+
return result;
|
|
1670
|
+
};
|
|
1671
|
+
var score = (value, label) => {
|
|
1672
|
+
const numeric = typeof value === "number" ? value : Number.NaN;
|
|
1673
|
+
if (!Number.isFinite(numeric) || numeric < 0 || numeric > 100) fail(`${label} must be a number between 0 and 100.`, "INVALID_INPUT");
|
|
1674
|
+
return numeric;
|
|
1675
|
+
};
|
|
1676
|
+
var validateCase = (value, index2) => {
|
|
1677
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) fail(`cases[${index2}] must be an object.`, "INVALID_INPUT");
|
|
1678
|
+
const candidate = value;
|
|
1679
|
+
const layer = nonEmpty4(candidate["layer"], `cases[${index2}].layer`);
|
|
1680
|
+
if (!EVAL_LAYERS.includes(layer)) fail(`cases[${index2}].layer is invalid.`, "INVALID_INPUT");
|
|
1681
|
+
const components = candidate["components"];
|
|
1682
|
+
if (!Array.isArray(components) || !components.length) fail(`cases[${index2}].components must be a non-empty array.`, "INVALID_INPUT");
|
|
1683
|
+
const normalizedComponents = components.map((item, componentIndex) => {
|
|
1684
|
+
const component2 = nonEmpty4(item, `cases[${index2}].components[${componentIndex}]`);
|
|
1685
|
+
if (!EVAL_COMPONENTS.includes(component2)) fail(`cases[${index2}].components[${componentIndex}] is invalid.`, "INVALID_INPUT");
|
|
1686
|
+
return component2;
|
|
1687
|
+
});
|
|
1688
|
+
if (new Set(normalizedComponents).size !== normalizedComponents.length) fail(`cases[${index2}].components must not contain duplicates.`, "INVALID_INPUT");
|
|
1689
|
+
const baselineScore = candidate["baselineScore"] === void 0 ? void 0 : score(candidate["baselineScore"], `cases[${index2}].baselineScore`);
|
|
1690
|
+
return {
|
|
1691
|
+
id: nonEmpty4(candidate["id"], `cases[${index2}].id`),
|
|
1692
|
+
layer,
|
|
1693
|
+
components: normalizedComponents,
|
|
1694
|
+
grader: nonEmpty4(candidate["grader"], `cases[${index2}].grader`),
|
|
1695
|
+
input: nonEmpty4(candidate["input"], `cases[${index2}].input`),
|
|
1696
|
+
...candidate["critical"] === void 0 ? {} : { critical: candidate["critical"] === true },
|
|
1697
|
+
...candidate["subjective"] === void 0 ? {} : { subjective: candidate["subjective"] === true },
|
|
1698
|
+
...baselineScore === void 0 ? {} : { baselineScore }
|
|
1699
|
+
};
|
|
1700
|
+
};
|
|
1701
|
+
var manifestBody2 = (value) => {
|
|
1702
|
+
const casesValue = value["cases"];
|
|
1703
|
+
if (!Array.isArray(casesValue) || !casesValue.length) fail("cases must be a non-empty array.", "INVALID_INPUT");
|
|
1704
|
+
const cases = casesValue.map(validateCase);
|
|
1705
|
+
if (new Set(cases.map((item) => item.id)).size !== cases.length) fail("case ids must be unique.", "INVALID_INPUT");
|
|
1706
|
+
const layers = new Set(cases.map((item) => item.layer));
|
|
1707
|
+
const missingLayers = EVAL_LAYERS.filter((layer) => !layers.has(layer));
|
|
1708
|
+
if (missingLayers.length) fail(`cases must cover layers: ${missingLayers.join(", ")}.`, "INVALID_INPUT");
|
|
1709
|
+
const coveredComponents = new Set(cases.flatMap((item) => item.components));
|
|
1710
|
+
const missingComponents = EVAL_COMPONENTS.filter((component2) => !coveredComponents.has(component2));
|
|
1711
|
+
if (missingComponents.length) fail(`cases must cover components: ${missingComponents.join(", ")}.`, "INVALID_INPUT");
|
|
1712
|
+
const gradersValue = value["graders"];
|
|
1713
|
+
if (!Array.isArray(gradersValue) || !gradersValue.length) fail("graders must be a non-empty array.", "INVALID_INPUT");
|
|
1714
|
+
const graders = gradersValue.map((item, index2) => nonEmpty4(item, `graders[${index2}]`));
|
|
1715
|
+
const thresholdsValue = value["thresholds"];
|
|
1716
|
+
if (typeof thresholdsValue !== "object" || thresholdsValue === null || Array.isArray(thresholdsValue)) fail("thresholds must be an object.", "INVALID_INPUT");
|
|
1717
|
+
const thresholds2 = thresholdsValue;
|
|
1718
|
+
const repetitions = value["repetitions"];
|
|
1719
|
+
if (!Number.isInteger(repetitions) || repetitions < 1) fail("repetitions must be a positive integer.", "INVALID_INPUT");
|
|
1720
|
+
return {
|
|
1721
|
+
type: "agentskit-harness-eval-manifest",
|
|
1722
|
+
schemaVersion: EVAL_MANIFEST_SCHEMA_VERSION,
|
|
1723
|
+
suiteId: nonEmpty4(value["suiteId"], "suiteId"),
|
|
1724
|
+
name: nonEmpty4(value["name"], "name"),
|
|
1725
|
+
cases,
|
|
1726
|
+
graders,
|
|
1727
|
+
thresholds: { subjectiveQuality: score(thresholds2["subjectiveQuality"] ?? 80, "thresholds.subjectiveQuality"), maxRegression: score(thresholds2["maxRegression"] ?? 5, "thresholds.maxRegression") },
|
|
1728
|
+
repetitions,
|
|
1729
|
+
provider: nonEmpty4(value["provider"], "provider"),
|
|
1730
|
+
model: nonEmpty4(value["model"], "model"),
|
|
1731
|
+
promptHash: digest4(value["promptHash"], "promptHash"),
|
|
1732
|
+
toolHash: digest4(value["toolHash"], "toolHash"),
|
|
1733
|
+
evidenceOutputs: Array.isArray(value["evidenceOutputs"]) && value["evidenceOutputs"].length ? value["evidenceOutputs"].map((item, index2) => nonEmpty4(item, `evidenceOutputs[${index2}]`)) : fail("evidenceOutputs must be a non-empty array.", "INVALID_INPUT")
|
|
1734
|
+
};
|
|
1735
|
+
};
|
|
1736
|
+
var createEvalManifest = (input) => {
|
|
1737
|
+
const body3 = manifestBody2(input);
|
|
1738
|
+
return { ...body3, digest: hashJson(body3) };
|
|
1739
|
+
};
|
|
1740
|
+
var validateEvalManifest = (value) => {
|
|
1741
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) fail("Eval manifest must be an object.", "INVALID_INPUT");
|
|
1742
|
+
const candidate = value;
|
|
1743
|
+
const body3 = manifestBody2(candidate);
|
|
1744
|
+
if (candidate["type"] !== body3.type || candidate["schemaVersion"] !== body3.schemaVersion) fail("Eval manifest type or schemaVersion is invalid.", "INVALID_INPUT");
|
|
1745
|
+
const manifestDigest = digest4(candidate["digest"], "digest");
|
|
1746
|
+
if (manifestDigest !== hashJson(body3)) fail("Eval manifest digest is invalid.", "INVALID_INPUT");
|
|
1747
|
+
return { ...body3, digest: manifestDigest };
|
|
1748
|
+
};
|
|
1749
|
+
var median = (values) => {
|
|
1750
|
+
if (!values.length) return null;
|
|
1751
|
+
const ordered = [...values].sort((left, right) => left - right);
|
|
1752
|
+
const middle = Math.floor(ordered.length / 2);
|
|
1753
|
+
return ordered.length % 2 ? ordered[middle] : (ordered[middle - 1] + ordered[middle]) / 2;
|
|
1754
|
+
};
|
|
1755
|
+
var runEvalBattery = async ({ manifest, evaluate }) => {
|
|
1756
|
+
const validated = validateEvalManifest(manifest);
|
|
1757
|
+
const reports = [];
|
|
1758
|
+
for (const testCase of validated.cases) {
|
|
1759
|
+
const observations = [];
|
|
1760
|
+
for (let repetition = 1; repetition <= validated.repetitions; repetition += 1) observations.push(await evaluate(testCase, repetition));
|
|
1761
|
+
const blockers2 = [];
|
|
1762
|
+
const statuses = observations.map((observation) => observation.status);
|
|
1763
|
+
if (statuses.some((status) => status === "unknown" || status === "stale" || status === "unverified")) blockers2.push("unknown, stale, or unverified evidence");
|
|
1764
|
+
if (statuses.some((status) => status === "failed")) blockers2.push("failed observation");
|
|
1765
|
+
const values = observations.map((observation) => observation.score).filter((value) => typeof value === "number");
|
|
1766
|
+
const minimum = values.length ? Math.min(...values) : null;
|
|
1767
|
+
const baseline = testCase.baselineScore;
|
|
1768
|
+
if (testCase.critical && (minimum === null || minimum < 100)) blockers2.push("critical case requires 100/100");
|
|
1769
|
+
if (testCase.subjective && (median(values) ?? 0) < validated.thresholds.subjectiveQuality) blockers2.push(`subjective score below ${validated.thresholds.subjectiveQuality}/100`);
|
|
1770
|
+
if (baseline !== void 0 && minimum !== null && minimum < baseline - validated.thresholds.maxRegression && !observations.every((observation) => observation.decision)) blockers2.push(`regression exceeds ${validated.thresholds.maxRegression} points without a decision`);
|
|
1771
|
+
reports.push({ id: testCase.id, repetitions: observations.length, min: minimum, median: median(values), max: values.length ? Math.max(...values) : null, statuses, blockers: blockers2 });
|
|
1772
|
+
}
|
|
1773
|
+
const blockers = reports.flatMap((report) => report.blockers.map((reason) => `${report.id}: ${reason}`));
|
|
1774
|
+
return { suiteId: validated.suiteId, repetitions: validated.repetitions, cases: reports, status: blockers.length ? "blocked" : "passed", blockers };
|
|
1775
|
+
};
|
|
1776
|
+
var pass = (expected, output) => typeof expected === "string" ? output === expected : expected(output);
|
|
1777
|
+
var runAgentEval = async ({ suite, agent, concurrency = 1 }) => {
|
|
1778
|
+
if (!suite.name.trim() || !suite.cases.length) fail("Eval suite must have a name and at least one case.", "INVALID_INPUT");
|
|
1779
|
+
if (!Number.isInteger(concurrency) || concurrency < 1) fail("Eval concurrency must be a positive integer.", "INVALID_INPUT");
|
|
1780
|
+
const failures = [];
|
|
1781
|
+
let passed = 0;
|
|
1782
|
+
for (let offset = 0; offset < suite.cases.length; offset += concurrency) {
|
|
1783
|
+
const batch = suite.cases.slice(offset, offset + concurrency);
|
|
1784
|
+
const outputs = await Promise.all(batch.map((testCase) => agent(testCase.input)));
|
|
1785
|
+
batch.forEach((testCase, index2) => {
|
|
1786
|
+
if (pass(testCase.expected, outputs[index2])) passed += 1;
|
|
1787
|
+
else failures.push(testCase.id);
|
|
1788
|
+
});
|
|
1789
|
+
}
|
|
1790
|
+
return { suite: suite.name, total: suite.cases.length, passed, failed: suite.cases.length - passed, accuracy: passed / suite.cases.length, failures };
|
|
1791
|
+
};
|
|
1792
|
+
var assessAgentEval = (report, minimumAccuracy) => {
|
|
1793
|
+
if (!Number.isFinite(minimumAccuracy) || minimumAccuracy < 0 || minimumAccuracy > 1) fail("minimumAccuracy must be between 0 and 1.", "INVALID_INPUT");
|
|
1794
|
+
if (report.total < 1 || report.passed + report.failed !== report.total || report.accuracy !== report.passed / report.total) fail("Eval report is inconsistent.", "INVALID_INPUT");
|
|
1795
|
+
return report.accuracy >= minimumAccuracy ? { status: "passed", reason: `Accuracy ${report.accuracy.toFixed(4)} meets ${minimumAccuracy.toFixed(4)}.`, report } : { status: "blocked", reason: `Accuracy ${report.accuracy.toFixed(4)} is below ${minimumAccuracy.toFixed(4)}.`, report };
|
|
1796
|
+
};
|
|
1797
|
+
|
|
1798
|
+
// src/kernel/cache.ts
|
|
1799
|
+
var validateCacheableOperation = (operation) => {
|
|
1800
|
+
if (operation !== "context" && operation !== "read-only") fail("Only context and read-only operations may use the LLM cache.", "POLICY_BLOCKED");
|
|
1801
|
+
return operation;
|
|
1802
|
+
};
|
|
1803
|
+
var createLlmCacheKey = (input) => {
|
|
1804
|
+
validateCacheableOperation(input.operation);
|
|
1805
|
+
return hashJson(input);
|
|
1806
|
+
};
|
|
1807
|
+
var createLlmCache = () => {
|
|
1808
|
+
const values = /* @__PURE__ */ new Map();
|
|
1809
|
+
let hits = 0;
|
|
1810
|
+
let misses = 0;
|
|
1811
|
+
let invalidations = 0;
|
|
1812
|
+
return {
|
|
1813
|
+
assurance: "contract-tested",
|
|
1814
|
+
telemetry: () => ({ status: "measured", cacheHits: hits, cacheMisses: misses }),
|
|
1815
|
+
async getOrCompute(key, compute) {
|
|
1816
|
+
const cached = values.get(key);
|
|
1817
|
+
if (cached !== void 0) {
|
|
1818
|
+
hits += 1;
|
|
1819
|
+
return cached;
|
|
1820
|
+
}
|
|
1821
|
+
misses += 1;
|
|
1822
|
+
const value = await compute();
|
|
1823
|
+
values.set(key, value);
|
|
1824
|
+
return value;
|
|
1825
|
+
},
|
|
1826
|
+
invalidate(key) {
|
|
1827
|
+
if (key === void 0) {
|
|
1828
|
+
invalidations += values.size;
|
|
1829
|
+
values.clear();
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
if (values.delete(key)) invalidations += 1;
|
|
1833
|
+
},
|
|
1834
|
+
stats: () => ({ hits, misses, invalidations })
|
|
1835
|
+
};
|
|
1836
|
+
};
|
|
1837
|
+
|
|
1838
|
+
// src/kernel/optimization.ts
|
|
1839
|
+
var nonNegative2 = (value, label) => {
|
|
1840
|
+
if (!Number.isFinite(value) || value < 0) fail(`${label} must be a non-negative number.`, "INVALID_INPUT");
|
|
1841
|
+
return value;
|
|
1842
|
+
};
|
|
1843
|
+
var nonNegativeInteger = (value, label) => {
|
|
1844
|
+
nonNegative2(value, label);
|
|
1845
|
+
if (!Number.isInteger(value)) fail(`${label} must be an integer.`, "INVALID_INPUT");
|
|
1846
|
+
return value;
|
|
1847
|
+
};
|
|
1848
|
+
var required6 = (value, label) => {
|
|
1849
|
+
if (typeof value !== "string" || !value.trim()) fail(`${label} is required.`, "INVALID_INPUT");
|
|
1850
|
+
return value.trim();
|
|
1851
|
+
};
|
|
1852
|
+
var validateOptimizationObservation = (observation) => {
|
|
1853
|
+
required6(observation.sourceRevision, "sourceRevision");
|
|
1854
|
+
required6(observation.contractHash, "contractHash");
|
|
1855
|
+
required6(observation.configHash, "configHash");
|
|
1856
|
+
required6(observation.provider, "provider");
|
|
1857
|
+
required6(observation.model, "model");
|
|
1858
|
+
nonNegative2(observation.durationMs, "durationMs");
|
|
1859
|
+
if (observation.accuracy !== void 0 && (!Number.isFinite(observation.accuracy) || observation.accuracy < 0 || observation.accuracy > 1)) fail("accuracy must be between 0 and 1.", "INVALID_INPUT");
|
|
1860
|
+
if (observation.tokens) {
|
|
1861
|
+
const tokens = observation.tokens;
|
|
1862
|
+
for (const key of ["inputTokens", "outputTokens", "totalTokens"]) nonNegativeInteger(tokens[key], `tokens.${key}`);
|
|
1863
|
+
if (tokens.totalTokens !== tokens.inputTokens + tokens.outputTokens) fail("tokens.totalTokens must equal inputTokens + outputTokens.", "INVALID_INPUT");
|
|
1864
|
+
for (const key of ["cacheReadTokens", "cacheWriteTokens"]) if (tokens[key] !== void 0) nonNegativeInteger(tokens[key], `tokens.${key}`);
|
|
1865
|
+
}
|
|
1866
|
+
if (observation.memory) for (const key of ["reads", "writes", "relevantHits", "staleHits"]) nonNegativeInteger(observation.memory[key], `memory.${key}`);
|
|
1867
|
+
if (observation.cache) {
|
|
1868
|
+
for (const key of ["hits", "misses", "invalidations"]) nonNegativeInteger(observation.cache[key], `cache.${key}`);
|
|
1869
|
+
if (observation.cache.tokensSaved !== void 0) nonNegativeInteger(observation.cache.tokensSaved, "cache.tokensSaved");
|
|
1870
|
+
}
|
|
1871
|
+
if (observation.parallelism) {
|
|
1872
|
+
for (const key of ["tasks", "peakConcurrency"]) nonNegativeInteger(observation.parallelism[key], `parallelism.${key}`);
|
|
1873
|
+
nonNegative2(observation.parallelism.criticalPathMs, "parallelism.criticalPathMs");
|
|
1874
|
+
if (observation.parallelism.queueWaitMs !== void 0) nonNegative2(observation.parallelism.queueWaitMs, "parallelism.queueWaitMs");
|
|
1875
|
+
if (observation.parallelism.tasks > 0 && observation.parallelism.peakConcurrency < 1) fail("parallelism.peakConcurrency must be positive when tasks exist.", "INVALID_INPUT");
|
|
1876
|
+
}
|
|
1877
|
+
return observation;
|
|
1878
|
+
};
|
|
1879
|
+
var rate = (hits, total) => total ? Number((hits / total).toFixed(4)) : void 0;
|
|
1880
|
+
var compareOptimization = (baseline, candidate) => {
|
|
1881
|
+
validateOptimizationObservation(baseline);
|
|
1882
|
+
validateOptimizationObservation(candidate);
|
|
1883
|
+
for (const key of ["sourceRevision", "contractHash", "configHash", "provider", "model"]) if (baseline[key] !== candidate[key]) return { comparable: false, reason: `Bindings differ: ${key}.`, digest: hashJson({ baseline, candidate }) };
|
|
1884
|
+
const result = {
|
|
1885
|
+
comparable: true,
|
|
1886
|
+
reason: "Observations share source, contract, configuration, provider, and model bindings.",
|
|
1887
|
+
digest: hashJson({ baseline, candidate }),
|
|
1888
|
+
durationDeltaMs: candidate.durationMs - baseline.durationMs,
|
|
1889
|
+
...baseline.accuracy !== void 0 && candidate.accuracy !== void 0 ? { accuracyDelta: Number((candidate.accuracy - baseline.accuracy).toFixed(4)) } : {},
|
|
1890
|
+
...baseline.tokens && candidate.tokens ? { tokenDelta: candidate.tokens.totalTokens - baseline.tokens.totalTokens } : {},
|
|
1891
|
+
...baseline.cache && candidate.cache ? { cacheHitRateDelta: (rate(candidate.cache.hits, candidate.cache.hits + candidate.cache.misses) ?? 0) - (rate(baseline.cache.hits, baseline.cache.hits + baseline.cache.misses) ?? 0) } : {},
|
|
1892
|
+
...baseline.memory && candidate.memory ? { memoryRelevantHitRateDelta: (rate(candidate.memory.relevantHits, candidate.memory.reads) ?? 0) - (rate(baseline.memory.relevantHits, baseline.memory.reads) ?? 0) } : {},
|
|
1893
|
+
...baseline.parallelism && candidate.parallelism ? { peakConcurrencyDelta: candidate.parallelism.peakConcurrency - baseline.parallelism.peakConcurrency } : {}
|
|
1894
|
+
};
|
|
1895
|
+
return result;
|
|
1896
|
+
};
|
|
1897
|
+
|
|
1898
|
+
// src/kernel/memory.ts
|
|
1899
|
+
var MEMORY_SCOPES = ["issue", "project", "global"];
|
|
1900
|
+
var text2 = (value, label) => {
|
|
1901
|
+
if (typeof value !== "string" || !value.trim()) fail(label + " must be a non-empty string.", "INVALID_INPUT");
|
|
1902
|
+
return value.trim();
|
|
1903
|
+
};
|
|
1904
|
+
var validateMemoryRecord = (record3) => {
|
|
1905
|
+
text2(record3.id, "memory.id");
|
|
1906
|
+
if (!MEMORY_SCOPES.includes(record3.scope)) fail("memory.scope is invalid.", "INVALID_INPUT");
|
|
1907
|
+
text2(record3.summary, "memory.summary");
|
|
1908
|
+
text2(record3.source, "memory.source");
|
|
1909
|
+
text2(record3.sourceRevision, "memory.sourceRevision");
|
|
1910
|
+
text2(record3.contentHash, "memory.contentHash");
|
|
1911
|
+
if (record3.approved !== true) fail("Only approved memory may enter the shared store.", "POLICY_BLOCKED");
|
|
1912
|
+
return record3;
|
|
1913
|
+
};
|
|
1914
|
+
var createInMemoryMemoryAdapter = (options = {}) => {
|
|
1915
|
+
const records = /* @__PURE__ */ new Map();
|
|
1916
|
+
let reads = 0;
|
|
1917
|
+
let writes = 0;
|
|
1918
|
+
let relevantHits = 0;
|
|
1919
|
+
let staleHits = 0;
|
|
1920
|
+
return {
|
|
1921
|
+
id: options.id ?? "in-memory",
|
|
1922
|
+
version: options.version ?? "1",
|
|
1923
|
+
assurance: "contract-tested",
|
|
1924
|
+
telemetry: () => ({ status: "measured", memoryReads: reads, memoryWrites: writes, memoryRelevantHits: relevantHits, memoryStaleHits: staleHits }),
|
|
1925
|
+
async remember(record3) {
|
|
1926
|
+
records.set(validateMemoryRecord(record3).id, record3);
|
|
1927
|
+
writes += 1;
|
|
1928
|
+
},
|
|
1929
|
+
async recall({ query, issueId, project, sourceRevision }) {
|
|
1930
|
+
reads += 1;
|
|
1931
|
+
const needle = query.trim().toLowerCase();
|
|
1932
|
+
const hits = [...records.values()].filter((record3) => {
|
|
1933
|
+
const scopeMatch = record3.scope === "global" || (record3.scope === "issue" ? Boolean(issueId && record3.source.includes(issueId)) : Boolean(project && record3.source.includes(project)));
|
|
1934
|
+
return scopeMatch && (!needle || `${record3.summary} ${record3.source}`.toLowerCase().includes(needle));
|
|
1935
|
+
}).map((record3) => ({ record: record3, relevant: true, stale: sourceRevision !== void 0 && record3.sourceRevision !== sourceRevision }));
|
|
1936
|
+
relevantHits += hits.length;
|
|
1937
|
+
staleHits += hits.filter((hit) => hit.stale).length;
|
|
1938
|
+
return hits;
|
|
1939
|
+
}
|
|
1940
|
+
};
|
|
1941
|
+
};
|
|
1942
|
+
var createKvMemoryAdapter = (store, options = {}) => {
|
|
1943
|
+
const indexKey = "agentskit-harness:memory:index";
|
|
1944
|
+
let reads = 0;
|
|
1945
|
+
let writes = 0;
|
|
1946
|
+
let relevantHits = 0;
|
|
1947
|
+
let staleHits = 0;
|
|
1948
|
+
const matches2 = (record3, query, issueId, project) => {
|
|
1949
|
+
const scopeMatch = record3.scope === "global" || (record3.scope === "issue" ? Boolean(issueId && record3.source.includes(issueId)) : Boolean(project && record3.source.includes(project)));
|
|
1950
|
+
return scopeMatch && (!query || `${record3.summary} ${record3.source}`.toLowerCase().includes(query));
|
|
1951
|
+
};
|
|
1952
|
+
return {
|
|
1953
|
+
id: options.id ?? "agentskit-kv",
|
|
1954
|
+
version: options.version ?? "1",
|
|
1955
|
+
assurance: "contract-tested",
|
|
1956
|
+
telemetry: () => ({ status: "measured", memoryReads: reads, memoryWrites: writes, memoryRelevantHits: relevantHits, memoryStaleHits: staleHits }),
|
|
1957
|
+
async remember(record3) {
|
|
1958
|
+
const valid = validateMemoryRecord(record3);
|
|
1959
|
+
const ids = await store.get(indexKey);
|
|
1960
|
+
const index2 = Array.isArray(ids) ? ids.filter((id2) => typeof id2 === "string") : [];
|
|
1961
|
+
if (!index2.includes(valid.id)) await store.set(indexKey, [...index2, valid.id].sort());
|
|
1962
|
+
await store.set(`agentskit-harness:memory:${valid.id}`, valid);
|
|
1963
|
+
writes += 1;
|
|
1964
|
+
},
|
|
1965
|
+
async recall({ query, issueId, project, sourceRevision }) {
|
|
1966
|
+
reads += 1;
|
|
1967
|
+
const ids = await store.get(indexKey);
|
|
1968
|
+
const records = Array.isArray(ids) ? await Promise.all(ids.filter((id2) => typeof id2 === "string").map((id2) => store.get(`agentskit-harness:memory:${id2}`))) : [];
|
|
1969
|
+
const hits = records.filter((record3) => Boolean(record3 && typeof record3 === "object" && record3.approved === true)).filter((record3) => matches2(record3, query.trim().toLowerCase(), issueId, project)).map((record3) => ({ record: record3, relevant: true, stale: sourceRevision !== void 0 && record3.sourceRevision !== sourceRevision }));
|
|
1970
|
+
relevantHits += hits.length;
|
|
1971
|
+
staleHits += hits.filter((hit) => hit.stale).length;
|
|
1972
|
+
return hits;
|
|
1973
|
+
}
|
|
1974
|
+
};
|
|
1975
|
+
};
|
|
1976
|
+
|
|
1977
|
+
// src/kernel/phase-executor.ts
|
|
1978
|
+
var PHASE_MODES = ["safe", "yolo", "dry-run"];
|
|
1979
|
+
var PHASE_EFFECTS = ["read", "write", "external"];
|
|
1980
|
+
var PHASE_EFFECT_ACTIONS = ["allow", "preview", "block", "escalate"];
|
|
1981
|
+
var PHASE_DECISIONS = ["pass", "block", "escalate", "retry", "cancel", "resume"];
|
|
1982
|
+
var requiredId = (value, label) => {
|
|
1983
|
+
if (typeof value !== "string") return fail(`${label} must be a non-empty string.`, "INVALID_INPUT");
|
|
1984
|
+
if (!value.trim()) return fail(`${label} must be a non-empty string.`, "INVALID_INPUT");
|
|
1985
|
+
return value.trim();
|
|
1986
|
+
};
|
|
1987
|
+
var names = (values, label) => {
|
|
1988
|
+
if (values === void 0) return [];
|
|
1989
|
+
if (!Array.isArray(values)) fail(`${label} must be an array.`, "INVALID_INPUT");
|
|
1990
|
+
const normalized = values.map((value, index2) => requiredId(value, `${label}[${index2}]`));
|
|
1991
|
+
if (new Set(normalized).size !== normalized.length) fail(`${label} must contain unique names.`, "INVALID_INPUT");
|
|
1992
|
+
return normalized;
|
|
1993
|
+
};
|
|
1994
|
+
var boundedPositive = (value, label, fallback) => {
|
|
1995
|
+
const result = value ?? fallback;
|
|
1996
|
+
if (!Number.isInteger(result) || result < 1 || result > 100) fail(`${label} must be a bounded positive integer (1-100).`, "INVALID_INPUT");
|
|
1997
|
+
return result;
|
|
1998
|
+
};
|
|
1999
|
+
var duration = (value, label) => {
|
|
2000
|
+
if (value === void 0) return void 0;
|
|
2001
|
+
if (!Number.isInteger(value) || value < 1) fail(`${label} must be a positive integer.`, "INVALID_INPUT");
|
|
2002
|
+
return value;
|
|
2003
|
+
};
|
|
2004
|
+
var defaultEffects = (mode) => mode === "dry-run" ? { read: "allow", write: "preview", external: "preview" } : mode === "safe" ? { read: "allow", write: "allow", external: "escalate" } : { read: "allow", write: "allow", external: "allow" };
|
|
2005
|
+
var normalize = (profile) => {
|
|
2006
|
+
if (typeof profile !== "object" || profile === null || Array.isArray(profile)) return fail("profile must be an object.", "INVALID_INPUT");
|
|
2007
|
+
const id2 = requiredId(profile.id, "profile.id");
|
|
2008
|
+
if (!PHASE_MODES.includes(profile.mode)) fail("profile.mode is invalid.", "INVALID_INPUT");
|
|
2009
|
+
if (!Array.isArray(profile.phases) || !profile.phases.length) fail("profile.phases must be non-empty.", "INVALID_INPUT");
|
|
2010
|
+
profile.phases.forEach((phase2, index2) => {
|
|
2011
|
+
if (typeof phase2 !== "object" || phase2 === null || Array.isArray(phase2)) fail(`phases[${index2}] must be an object.`, "INVALID_INPUT");
|
|
2012
|
+
});
|
|
2013
|
+
const ids = profile.phases.map((phase2, index2) => requiredId(phase2.id, `phases[${index2}].id`));
|
|
2014
|
+
if (new Set(ids).size !== ids.length) fail("Phase ids must be unique.", "INVALID_INPUT");
|
|
2015
|
+
const known = new Set(ids);
|
|
2016
|
+
const outputOwners = /* @__PURE__ */ new Map();
|
|
2017
|
+
const phases = profile.phases.map((phase2, index2) => {
|
|
2018
|
+
if (typeof phase2 !== "object" || phase2 === null || Array.isArray(phase2)) return fail(`phases[${index2}] must be an object.`, "INVALID_INPUT");
|
|
2019
|
+
if (!PHASE_EFFECTS.includes(phase2.effect)) fail(`phases[${index2}].effect is invalid.`, "INVALID_INPUT");
|
|
2020
|
+
const dependsOn = names(phase2.dependsOn, `phases[${index2}].dependsOn`);
|
|
2021
|
+
if (dependsOn.includes(ids[index2])) fail(`phases[${index2}] cannot depend on itself.`, "INVALID_INPUT");
|
|
2022
|
+
if (dependsOn.some((dependency) => !known.has(dependency))) fail(`phases[${index2}] has an unknown dependency.`, "INVALID_INPUT");
|
|
2023
|
+
const inputs = names(phase2.inputs, `phases[${index2}].inputs`);
|
|
2024
|
+
const outputs = names(phase2.outputs, `phases[${index2}].outputs`);
|
|
2025
|
+
for (const output of outputs) {
|
|
2026
|
+
const owner = outputOwners.get(output);
|
|
2027
|
+
if (owner) fail(`Output ${output} is declared by both ${owner} and ${ids[index2]}.`, "INVALID_INPUT");
|
|
2028
|
+
outputOwners.set(output, ids[index2]);
|
|
2029
|
+
}
|
|
2030
|
+
const gates = names(phase2.gates, `phases[${index2}].gates`);
|
|
2031
|
+
const maxAttempts = boundedPositive(phase2.retries?.maxAttempts, `phases[${index2}].retries.maxAttempts`, 1);
|
|
2032
|
+
return { id: ids[index2], effect: phase2.effect, inputs, outputs, dependsOn, gates, ...maxAttempts > 1 ? { retries: { maxAttempts } } : {}, ...duration(phase2.budgetMs, `phases[${index2}].budgetMs`) ? { budgetMs: phase2.budgetMs } : {} };
|
|
2033
|
+
});
|
|
2034
|
+
const defaults = defaultEffects(profile.mode);
|
|
2035
|
+
const effectPolicy = { ...defaults, ...profile.effectPolicy ?? {} };
|
|
2036
|
+
for (const effect of PHASE_EFFECTS) if (!PHASE_EFFECT_ACTIONS.includes(effectPolicy[effect])) fail(`effectPolicy.${effect} is invalid.`, "INVALID_INPUT");
|
|
2037
|
+
const maxConcurrency = boundedPositive(profile.maxConcurrency, "profile.maxConcurrency", 1);
|
|
2038
|
+
const budgetMs = duration(profile.budgetMs, "profile.budgetMs");
|
|
2039
|
+
calculateLevels(phases);
|
|
2040
|
+
return { id: id2, mode: profile.mode, phases, effectPolicy, maxConcurrency, ...budgetMs ? { budgetMs } : {} };
|
|
2041
|
+
};
|
|
2042
|
+
var calculateLevels = (phases) => {
|
|
2043
|
+
const byId = new Map(phases.map((phase2) => [phase2.id, phase2]));
|
|
2044
|
+
const remaining = new Set(byId.keys());
|
|
2045
|
+
const completed = /* @__PURE__ */ new Set();
|
|
2046
|
+
const levels2 = [];
|
|
2047
|
+
while (remaining.size) {
|
|
2048
|
+
const ready = [...remaining].sort().filter((id2) => (byId.get(id2)?.dependsOn ?? []).every((dependency) => completed.has(dependency)));
|
|
2049
|
+
if (!ready.length) fail("Phase profile contains an unknown dependency or cycle.", "INVALID_INPUT");
|
|
2050
|
+
levels2.push(ready);
|
|
2051
|
+
ready.forEach((id2) => {
|
|
2052
|
+
remaining.delete(id2);
|
|
2053
|
+
completed.add(id2);
|
|
2054
|
+
});
|
|
2055
|
+
}
|
|
2056
|
+
return levels2;
|
|
2057
|
+
};
|
|
2058
|
+
var createPhaseProfile = (profile) => normalize(profile);
|
|
2059
|
+
var planPhaseProfile = (profile) => {
|
|
2060
|
+
const normalized = normalize(profile);
|
|
2061
|
+
return { profileId: normalized.id, mode: normalized.mode, levels: calculateLevels(normalized.phases), phases: normalized.phases, effectPolicy: normalized.effectPolicy, maxConcurrency: normalized.maxConcurrency, ...normalized.budgetMs ? { budgetMs: normalized.budgetMs } : {} };
|
|
2062
|
+
};
|
|
2063
|
+
var resultDecision = (value) => {
|
|
2064
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) fail("Phase handler must return a decision object.", "INVALID_INPUT");
|
|
2065
|
+
const result = value;
|
|
2066
|
+
if (!PHASE_DECISIONS.includes(result.decision)) fail("Phase handler returned an invalid decision.", "INVALID_INPUT");
|
|
2067
|
+
if (result.outputs !== void 0 && (typeof result.outputs !== "object" || result.outputs === null || Array.isArray(result.outputs))) fail("Phase outputs must be an object.", "INVALID_INPUT");
|
|
2068
|
+
return result;
|
|
2069
|
+
};
|
|
2070
|
+
var gateDecision = (value) => typeof value === "boolean" ? { decision: value ? "pass" : "block" } : value;
|
|
2071
|
+
var packet = (phaseIds, ambiguities) => ambiguities.length ? { id: "phase-preflight", phaseIds: [...phaseIds].sort(), ambiguities } : void 0;
|
|
2072
|
+
var timeout = async (operation, budgetMs) => {
|
|
2073
|
+
if (budgetMs === void 0) return operation;
|
|
2074
|
+
let timer;
|
|
2075
|
+
const limit = new Promise((_, reject) => {
|
|
2076
|
+
timer = setTimeout(() => reject(new Error(`phase budget exceeded after ${budgetMs}ms`)), budgetMs);
|
|
2077
|
+
});
|
|
2078
|
+
try {
|
|
2079
|
+
return await Promise.race([operation, limit]);
|
|
2080
|
+
} finally {
|
|
2081
|
+
if (timer) clearTimeout(timer);
|
|
905
2082
|
}
|
|
2083
|
+
};
|
|
2084
|
+
var executePhaseProfile = async (profile, options = {}) => {
|
|
2085
|
+
const plan = planPhaseProfile(profile);
|
|
2086
|
+
const started = (options.now ?? Date.now)();
|
|
2087
|
+
const inputValues = { ...options.inputs ?? {} };
|
|
2088
|
+
const outputValues = { ...options.resume?.outputs ?? {} };
|
|
2089
|
+
const completed = options.resume?.completed ?? {};
|
|
2090
|
+
const phasesById = new Map(plan.phases.map((phase2) => [phase2.id, phase2]));
|
|
2091
|
+
const mutating = plan.phases.filter((phase2) => phase2.effect !== "read");
|
|
2092
|
+
const preflightAmbiguities = [];
|
|
2093
|
+
const preflightAmbiguityPhaseIds = /* @__PURE__ */ new Set();
|
|
2094
|
+
const preflightBlocked = [];
|
|
2095
|
+
const preflightEscalated = [];
|
|
2096
|
+
for (const phase2 of mutating) {
|
|
2097
|
+
const action = plan.effectPolicy[phase2.effect];
|
|
2098
|
+
if (action === "block") {
|
|
2099
|
+
preflightBlocked.push({ id: phase2.id, effect: phase2.effect, decision: "block", attempts: 0, skipped: true, reason: `Effect ${phase2.effect} is blocked by profile policy.` });
|
|
2100
|
+
continue;
|
|
2101
|
+
}
|
|
2102
|
+
if (action === "escalate") {
|
|
2103
|
+
preflightEscalated.push({ id: phase2.id, effect: phase2.effect, decision: "escalate", attempts: 0, skipped: true, reason: `Effect ${phase2.effect} requires escalation in ${plan.mode} mode.` });
|
|
2104
|
+
continue;
|
|
2105
|
+
}
|
|
2106
|
+
if (!options.preflight && action === "allow") {
|
|
2107
|
+
preflightBlocked.push({ id: phase2.id, effect: phase2.effect, decision: "block", attempts: 0, skipped: true, reason: `Preflight is required before ${phase2.effect} effects.` });
|
|
2108
|
+
continue;
|
|
2109
|
+
}
|
|
2110
|
+
if (!options.preflight) continue;
|
|
2111
|
+
const context = { phase: phase2, attempt: 0, mode: plan.mode, inputs: inputValues, outputs: outputValues, dryRun: action === "preview" };
|
|
2112
|
+
const check = await options.preflight(context);
|
|
2113
|
+
if (check.ambiguities?.length) {
|
|
2114
|
+
preflightAmbiguityPhaseIds.add(phase2.id);
|
|
2115
|
+
preflightAmbiguities.push(...check.ambiguities);
|
|
2116
|
+
}
|
|
2117
|
+
if (check.decision === "block") preflightBlocked.push({ id: phase2.id, effect: phase2.effect, decision: "block", attempts: 0, skipped: true, ...check.reason ? { reason: check.reason } : {} });
|
|
2118
|
+
if (check.decision === "escalate") preflightEscalated.push({ id: phase2.id, effect: phase2.effect, decision: "escalate", attempts: 0, skipped: true, ...check.reason ? { reason: check.reason } : {} });
|
|
2119
|
+
}
|
|
2120
|
+
const decisionPacket = packet([...preflightAmbiguityPhaseIds, ...preflightEscalated.map((phase2) => phase2.id)], preflightAmbiguities);
|
|
2121
|
+
if (decisionPacket || preflightEscalated.length) return { status: "escalated", plan, phases: [...preflightBlocked, ...preflightEscalated], order: [], outputs: outputValues, resumed: false, ...decisionPacket ? { decisionPacket } : {}, durationMs: (options.now ?? Date.now)() - started };
|
|
2122
|
+
if (preflightBlocked.length) return { status: "blocked", plan, phases: preflightBlocked, order: [], outputs: outputValues, resumed: false, durationMs: (options.now ?? Date.now)() - started };
|
|
2123
|
+
const executions = [];
|
|
2124
|
+
let resumed = false;
|
|
2125
|
+
let dryRun = false;
|
|
2126
|
+
const statusOf = (phase2, decision, attempts, skipped, reason, outputs) => ({ id: phase2.id, effect: phase2.effect, decision, attempts, skipped, ...reason ? { reason } : {}, ...outputs ? { outputs } : {} });
|
|
2127
|
+
const runPhase = async (phase2, levelOutputs) => {
|
|
2128
|
+
const prior = completed[phase2.id];
|
|
2129
|
+
if (prior && (prior.decision === "pass" || prior.decision === "resume")) {
|
|
2130
|
+
resumed = true;
|
|
2131
|
+
const restored = prior.outputs ?? {};
|
|
2132
|
+
return { execution: statusOf(phase2, "resume", 0, true, "Resumed from a completed phase.", restored), outputs: restored };
|
|
2133
|
+
}
|
|
2134
|
+
const action = plan.effectPolicy[phase2.effect];
|
|
2135
|
+
if (action === "block") return { execution: statusOf(phase2, "block", 0, true, `Effect ${phase2.effect} is blocked by profile policy.`), outputs: {} };
|
|
2136
|
+
if (action === "escalate") return { execution: statusOf(phase2, "escalate", 0, true, `Effect ${phase2.effect} requires escalation in ${plan.mode} mode.`), outputs: {} };
|
|
2137
|
+
if (action === "preview") {
|
|
2138
|
+
dryRun = true;
|
|
2139
|
+
return { execution: statusOf(phase2, "pass", 0, true, "Effect previewed; handler was not invoked."), outputs: {} };
|
|
2140
|
+
}
|
|
2141
|
+
const values = { ...inputValues, ...levelOutputs };
|
|
2142
|
+
const inputs = {};
|
|
2143
|
+
for (const name of phase2.inputs ?? []) {
|
|
2144
|
+
if (!(name in values)) return { execution: statusOf(phase2, "block", 0, true, `Missing phase input: ${name}.`), outputs: {} };
|
|
2145
|
+
inputs[name] = values[name];
|
|
2146
|
+
}
|
|
2147
|
+
const handler = options.handlers?.[phase2.id];
|
|
2148
|
+
if (!handler) return { execution: statusOf(phase2, "block", 0, true, `No handler registered for phase ${phase2.id}.`), outputs: {} };
|
|
2149
|
+
const gateContext = (attempt) => ({ phase: phase2, attempt, mode: plan.mode, inputs, outputs: levelOutputs, dryRun: false });
|
|
2150
|
+
for (const gateId of phase2.gates ?? []) {
|
|
2151
|
+
const evaluator = options.gates?.[gateId];
|
|
2152
|
+
if (!evaluator) return { execution: statusOf(phase2, "block", 0, true, `No evaluator registered for gate ${gateId}.`), outputs: {} };
|
|
2153
|
+
const gate = gateDecision(await evaluator(gateContext(0)));
|
|
2154
|
+
if (gate.decision !== "pass") return { execution: statusOf(phase2, gate.decision, 0, true, gate.reason ?? `Gate ${gateId} did not pass.`), outputs: {} };
|
|
2155
|
+
}
|
|
2156
|
+
const maxAttempts = phase2.retries?.maxAttempts ?? 1;
|
|
2157
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
2158
|
+
let result;
|
|
2159
|
+
try {
|
|
2160
|
+
result = resultDecision(await timeout(Promise.resolve(handler(gateContext(attempt))), phase2.budgetMs));
|
|
2161
|
+
} catch (error) {
|
|
2162
|
+
return { execution: statusOf(phase2, "block", attempt, false, error instanceof Error ? error.message : String(error)), outputs: {} };
|
|
2163
|
+
}
|
|
2164
|
+
if (result.decision === "retry") {
|
|
2165
|
+
if (attempt < maxAttempts) continue;
|
|
2166
|
+
return { execution: statusOf(phase2, "block", attempt, false, result.reason ?? "Phase retry budget exhausted."), outputs: {} };
|
|
2167
|
+
}
|
|
2168
|
+
if (result.decision === "pass" || result.decision === "resume") {
|
|
2169
|
+
const produced = { ...result.outputs ?? {} };
|
|
2170
|
+
const declared = new Set(phase2.outputs ?? []);
|
|
2171
|
+
if ([...Object.keys(produced)].some((name) => !declared.has(name))) return { execution: statusOf(phase2, "block", attempt, false, "Phase returned an undeclared output."), outputs: {} };
|
|
2172
|
+
if ([...phase2.outputs ?? []].some((name) => !(name in produced))) return { execution: statusOf(phase2, "block", attempt, false, "Phase did not produce every declared output."), outputs: {} };
|
|
2173
|
+
return { execution: statusOf(phase2, result.decision, attempt, false, result.reason, produced), outputs: produced };
|
|
2174
|
+
}
|
|
2175
|
+
return { execution: statusOf(phase2, result.decision, attempt, false, result.reason), outputs: {} };
|
|
2176
|
+
}
|
|
2177
|
+
return { execution: statusOf(phase2, "block", maxAttempts, false, "Phase did not resolve."), outputs: {} };
|
|
2178
|
+
};
|
|
2179
|
+
for (const level of plan.levels) {
|
|
2180
|
+
if (plan.budgetMs !== void 0 && (options.now ?? Date.now)() - started > plan.budgetMs) {
|
|
2181
|
+
const phase2 = phasesById.get(level[0]);
|
|
2182
|
+
executions.push(statusOf(phase2, "block", 0, true, `Profile budget exceeded after ${plan.budgetMs}ms.`));
|
|
2183
|
+
break;
|
|
2184
|
+
}
|
|
2185
|
+
const levelSnapshot = { ...outputValues };
|
|
2186
|
+
const workflow = await runWorkflow(level.map((id2) => ({ id: id2, run: () => runPhase(phasesById.get(id2), levelSnapshot) })), { maxConcurrency: plan.maxConcurrency });
|
|
2187
|
+
let stop = false;
|
|
2188
|
+
for (const id2 of level) {
|
|
2189
|
+
const step = workflow.results[id2];
|
|
2190
|
+
executions.push(step.execution);
|
|
2191
|
+
if (step.execution.decision === "pass" || step.execution.decision === "resume") Object.assign(outputValues, step.outputs);
|
|
2192
|
+
else stop = true;
|
|
2193
|
+
}
|
|
2194
|
+
if (plan.budgetMs !== void 0 && (options.now ?? Date.now)() - started > plan.budgetMs && executions.length) {
|
|
2195
|
+
const last = executions.length - 1;
|
|
2196
|
+
executions[last] = { ...executions[last], decision: "block", reason: `Profile budget exceeded after ${plan.budgetMs}ms.` };
|
|
2197
|
+
stop = true;
|
|
2198
|
+
}
|
|
2199
|
+
if (stop) break;
|
|
2200
|
+
}
|
|
2201
|
+
const failed = executions.find((execution) => execution.decision === "block" || execution.decision === "escalate" || execution.decision === "cancel");
|
|
2202
|
+
const status = failed?.decision === "cancel" ? "cancelled" : failed?.decision === "escalate" ? "escalated" : failed ? "blocked" : dryRun ? "dry-run" : "passed";
|
|
2203
|
+
return { status, plan, phases: executions, order: executions.map((execution) => execution.id), outputs: outputValues, resumed, durationMs: (options.now ?? Date.now)() - started };
|
|
2204
|
+
};
|
|
2205
|
+
var ARTIFACT_SCHEMA_VERSION = 1;
|
|
2206
|
+
var ARTIFACT_TYPES = ["plan", "finding", "decision", "repair", "blocker", "approval", "phase"];
|
|
2207
|
+
var text3 = (value, label) => {
|
|
2208
|
+
if (typeof value !== "string" || !value.trim()) return fail(`${label} is required.`, "INVALID_INPUT");
|
|
2209
|
+
return value.trim();
|
|
2210
|
+
};
|
|
2211
|
+
var digest5 = (value, label) => {
|
|
2212
|
+
const result = text3(value, label);
|
|
2213
|
+
if (!/^[a-f0-9]{64}$/.test(result)) fail(`${label} must be a lowercase SHA-256 digest.`, "INVALID_INPUT");
|
|
2214
|
+
return result;
|
|
2215
|
+
};
|
|
2216
|
+
var artifactId = (value) => {
|
|
2217
|
+
const result = text3(value, "Artifact artifactId");
|
|
2218
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(result)) fail("Artifact artifactId is invalid.", "INVALID_INPUT");
|
|
2219
|
+
return result;
|
|
2220
|
+
};
|
|
2221
|
+
var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2222
|
+
var artifactBody = (artifact) => ({
|
|
2223
|
+
type: artifact.type,
|
|
2224
|
+
schemaVersion: artifact.schemaVersion,
|
|
2225
|
+
artifactId: artifact.artifactId,
|
|
2226
|
+
artifactType: artifact.artifactType,
|
|
2227
|
+
artifactVersion: artifact.artifactVersion,
|
|
2228
|
+
runId: artifact.runId,
|
|
2229
|
+
issueRef: artifact.issueRef,
|
|
2230
|
+
sourceRevision: artifact.sourceRevision,
|
|
2231
|
+
contractHash: artifact.contractHash,
|
|
2232
|
+
configHash: artifact.configHash,
|
|
2233
|
+
contextHash: artifact.contextHash,
|
|
2234
|
+
phase: artifact.phase,
|
|
2235
|
+
payload: artifact.payload,
|
|
2236
|
+
payloadHash: artifact.payloadHash
|
|
906
2237
|
});
|
|
2238
|
+
var expectedArtifactHash = (artifact) => hashJson(artifactBody(artifact));
|
|
2239
|
+
var validateArtifactEnvelope = (value) => {
|
|
2240
|
+
if (!isRecord5(value)) return fail("Artifact envelope must be an object.", "INVALID_INPUT");
|
|
2241
|
+
if (value["type"] !== "agentskit-harness-artifact" || value["schemaVersion"] !== ARTIFACT_SCHEMA_VERSION) fail("Artifact envelope type or schemaVersion is invalid.", "INVALID_INPUT");
|
|
2242
|
+
if (!ARTIFACT_TYPES.includes(value["artifactType"])) fail("Artifact artifactType is invalid.", "INVALID_INPUT");
|
|
2243
|
+
if (!Number.isInteger(value["artifactVersion"]) || value["artifactVersion"] < 1) fail("Artifact artifactVersion must be a positive integer.", "INVALID_INPUT");
|
|
2244
|
+
const createdAt = text3(value["createdAt"], "Artifact createdAt");
|
|
2245
|
+
if (!Number.isFinite(Date.parse(createdAt))) fail("Artifact createdAt must be a valid timestamp.", "INVALID_INPUT");
|
|
2246
|
+
const payloadHash = digest5(value["payloadHash"], "Artifact payloadHash");
|
|
2247
|
+
if (hashJson(value["payload"]) !== payloadHash) fail("Artifact payloadHash does not match payload.", "INVALID_INPUT");
|
|
2248
|
+
const artifact = {
|
|
2249
|
+
type: "agentskit-harness-artifact",
|
|
2250
|
+
schemaVersion: ARTIFACT_SCHEMA_VERSION,
|
|
2251
|
+
artifactId: artifactId(value["artifactId"]),
|
|
2252
|
+
artifactType: value["artifactType"],
|
|
2253
|
+
artifactVersion: value["artifactVersion"],
|
|
2254
|
+
runId: text3(value["runId"], "Artifact runId"),
|
|
2255
|
+
issueRef: text3(value["issueRef"], "Artifact issueRef"),
|
|
2256
|
+
sourceRevision: text3(value["sourceRevision"], "Artifact sourceRevision"),
|
|
2257
|
+
contractHash: digest5(value["contractHash"], "Artifact contractHash"),
|
|
2258
|
+
configHash: digest5(value["configHash"], "Artifact configHash"),
|
|
2259
|
+
contextHash: digest5(value["contextHash"], "Artifact contextHash"),
|
|
2260
|
+
phase: text3(value["phase"], "Artifact phase"),
|
|
2261
|
+
createdAt,
|
|
2262
|
+
payload: value["payload"],
|
|
2263
|
+
payloadHash
|
|
2264
|
+
};
|
|
2265
|
+
if (digest5(value["artifactHash"], "Artifact artifactHash") !== expectedArtifactHash(artifact)) fail("Artifact artifactHash does not match envelope.", "INVALID_INPUT");
|
|
2266
|
+
return { ...artifact, artifactHash: value["artifactHash"] };
|
|
2267
|
+
};
|
|
2268
|
+
var createArtifactEnvelope = (input) => {
|
|
2269
|
+
if (!ARTIFACT_TYPES.includes(input.artifactType)) fail("Artifact artifactType is invalid.", "INVALID_INPUT");
|
|
2270
|
+
const payloadHash = input.payloadHash ?? hashJson(input.payload);
|
|
2271
|
+
if (payloadHash !== hashJson(input.payload)) fail("Artifact payloadHash does not match payload.", "INVALID_INPUT");
|
|
2272
|
+
const identity = {
|
|
2273
|
+
type: "agentskit-harness-artifact",
|
|
2274
|
+
schemaVersion: ARTIFACT_SCHEMA_VERSION,
|
|
2275
|
+
artifactType: input.artifactType,
|
|
2276
|
+
artifactVersion: input.artifactVersion,
|
|
2277
|
+
runId: input.runId,
|
|
2278
|
+
issueRef: input.issueRef,
|
|
2279
|
+
sourceRevision: input.sourceRevision,
|
|
2280
|
+
contractHash: input.contractHash,
|
|
2281
|
+
configHash: input.configHash,
|
|
2282
|
+
contextHash: input.contextHash,
|
|
2283
|
+
phase: input.phase,
|
|
2284
|
+
payload: input.payload,
|
|
2285
|
+
payloadHash
|
|
2286
|
+
};
|
|
2287
|
+
const id2 = input.artifactId ?? hashJson(identity);
|
|
2288
|
+
const artifact = {
|
|
2289
|
+
...identity,
|
|
2290
|
+
artifactId: id2,
|
|
2291
|
+
createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
2292
|
+
};
|
|
2293
|
+
const artifactHash = input.artifactHash ?? expectedArtifactHash(artifact);
|
|
2294
|
+
return validateArtifactEnvelope({ ...artifact, artifactHash });
|
|
2295
|
+
};
|
|
2296
|
+
var renderArtifactMarkdown = (artifact) => [
|
|
2297
|
+
`# ${artifact.artifactType} artifact ${artifact.artifactId}`,
|
|
2298
|
+
"",
|
|
2299
|
+
`- Schema: ${artifact.schemaVersion}`,
|
|
2300
|
+
`- Version: ${artifact.artifactVersion}`,
|
|
2301
|
+
`- Run: ${artifact.runId}`,
|
|
2302
|
+
`- Issue: ${artifact.issueRef}`,
|
|
2303
|
+
`- Phase: ${artifact.phase}`,
|
|
2304
|
+
`- Source revision: ${artifact.sourceRevision}`,
|
|
2305
|
+
`- Contract hash: ${artifact.contractHash}`,
|
|
2306
|
+
`- Configuration hash: ${artifact.configHash}`,
|
|
2307
|
+
`- Context hash: ${artifact.contextHash}`,
|
|
2308
|
+
`- Artifact hash: ${artifact.artifactHash}`,
|
|
2309
|
+
"",
|
|
2310
|
+
"## Payload",
|
|
2311
|
+
"",
|
|
2312
|
+
"```json",
|
|
2313
|
+
JSON.stringify(artifact.payload, null, 2),
|
|
2314
|
+
"```",
|
|
2315
|
+
""
|
|
2316
|
+
].join("\n");
|
|
2317
|
+
var artifactFilePath = (stateDir, runId, id2) => join(stateDir, "runs", runId, "artifacts", `${id2}.json`);
|
|
2318
|
+
var artifactMarkdownPath = (stateDir, runId, id2) => join(stateDir, "runs", runId, "artifacts", `${id2}.md`);
|
|
2319
|
+
var FileArtifactStore = class {
|
|
2320
|
+
constructor(stateDir) {
|
|
2321
|
+
this.stateDir = stateDir;
|
|
2322
|
+
}
|
|
2323
|
+
stateDir;
|
|
2324
|
+
write(input) {
|
|
2325
|
+
const artifact = validateArtifactEnvelope(input);
|
|
2326
|
+
const path = artifactFilePath(this.stateDir, artifact.runId, artifact.artifactId);
|
|
2327
|
+
mkdirSync(join(this.stateDir, "runs", artifact.runId, "artifacts"), { recursive: true });
|
|
2328
|
+
if (existsSync(path)) {
|
|
2329
|
+
const existing = validateArtifactEnvelope(JSON.parse(readFileSync(path, "utf8")));
|
|
2330
|
+
if (existing.artifactHash !== artifact.artifactHash) fail(`Artifact ${artifact.artifactId} already exists with different content.`, "HARNESS_ERROR");
|
|
2331
|
+
return existing;
|
|
2332
|
+
}
|
|
2333
|
+
writeFileSync(path, `${JSON.stringify(artifact, null, 2)}
|
|
2334
|
+
`, "utf8");
|
|
2335
|
+
writeFileSync(artifactMarkdownPath(this.stateDir, artifact.runId, artifact.artifactId), renderArtifactMarkdown(artifact), "utf8");
|
|
2336
|
+
new FileEventStore(this.stateDir).append({
|
|
2337
|
+
runId: artifact.runId,
|
|
2338
|
+
sourceRevision: artifact.sourceRevision,
|
|
2339
|
+
configHash: artifact.configHash,
|
|
2340
|
+
type: "artifact.recorded",
|
|
2341
|
+
payload: { artifactId: artifact.artifactId, artifactType: artifact.artifactType, artifactVersion: artifact.artifactVersion, artifactHash: artifact.artifactHash, phase: artifact.phase, representation: "json+markdown" }
|
|
2342
|
+
});
|
|
2343
|
+
return artifact;
|
|
2344
|
+
}
|
|
2345
|
+
read(runId, id2) {
|
|
2346
|
+
return validateArtifactEnvelope(JSON.parse(readFileSync(artifactFilePath(this.stateDir, runId, artifactId(id2)), "utf8")));
|
|
2347
|
+
}
|
|
2348
|
+
list(runId) {
|
|
2349
|
+
const directory = join(this.stateDir, "runs", runId, "artifacts");
|
|
2350
|
+
if (!existsSync(directory)) return [];
|
|
2351
|
+
return readdirSync(directory).filter((name) => name.endsWith(".json")).sort().map((name) => validateArtifactEnvelope(JSON.parse(readFileSync(join(directory, name), "utf8"))));
|
|
2352
|
+
}
|
|
2353
|
+
};
|
|
2354
|
+
var artifactIsFresh = (artifact, binding2) => artifact.runId === binding2.runId && artifact.issueRef === binding2.issueRef && artifact.sourceRevision === binding2.sourceRevision && artifact.contractHash === binding2.contractHash && artifact.configHash === binding2.configHash && artifact.contextHash === binding2.contextHash && (binding2.phase === void 0 || artifact.phase === binding2.phase);
|
|
2355
|
+
var resumeStateFromArtifacts = (artifacts) => {
|
|
2356
|
+
const completed = {};
|
|
2357
|
+
const outputs = {};
|
|
2358
|
+
for (const artifact of artifacts.filter((item) => item.artifactType === "phase").sort((left, right) => left.phase.localeCompare(right.phase) || left.artifactVersion - right.artifactVersion)) {
|
|
2359
|
+
if (!isRecord5(artifact.payload) || artifact.payload["decision"] !== "pass") continue;
|
|
2360
|
+
const phaseOutputs = isRecord5(artifact.payload["outputs"]) ? artifact.payload["outputs"] : {};
|
|
2361
|
+
completed[artifact.phase] = { decision: "pass", outputs: phaseOutputs };
|
|
2362
|
+
Object.assign(outputs, phaseOutputs);
|
|
2363
|
+
}
|
|
2364
|
+
return { completed, outputs };
|
|
2365
|
+
};
|
|
2366
|
+
var createPhaseArtifact = (base, execution) => createArtifactEnvelope({ ...base, artifactType: "phase", phase: execution.id, payload: { decision: execution.decision, outputs: execution.outputs ?? {} } });
|
|
2367
|
+
var readArtifactFile = (path) => validateArtifactEnvelope(JSON.parse(readFileSync(path, "utf8")));
|
|
2368
|
+
var artifactDigest = (artifact) => sha256(JSON.stringify(artifact));
|
|
2369
|
+
|
|
2370
|
+
// src/kernel/quality.ts
|
|
2371
|
+
var QUALITY_DIMENSIONS = ["correctness", "completeness", "speed", "cost", "resource", "reliability"];
|
|
2372
|
+
var finite = (value, label, max) => {
|
|
2373
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || max !== void 0 && value > max) return fail(`${label} is invalid.`, "INVALID_INPUT");
|
|
2374
|
+
return value;
|
|
2375
|
+
};
|
|
2376
|
+
var integer = (value, label) => {
|
|
2377
|
+
const result = finite(value, label);
|
|
2378
|
+
if (!Number.isInteger(result)) return fail(`${label} must be an integer.`, "INVALID_INPUT");
|
|
2379
|
+
return result;
|
|
2380
|
+
};
|
|
2381
|
+
var phase = (value) => {
|
|
2382
|
+
if (typeof value !== "string" || !value.trim()) return fail("phaseId is required.", "INVALID_INPUT");
|
|
2383
|
+
return value.trim();
|
|
2384
|
+
};
|
|
2385
|
+
var validatePhaseTelemetry = (value) => {
|
|
2386
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return fail("Phase telemetry must be an object.", "INVALID_INPUT");
|
|
2387
|
+
const raw = value;
|
|
2388
|
+
const outcome = raw["outcome"];
|
|
2389
|
+
if (!["pass", "block", "escalate", "cancel", "unknown"].includes(outcome)) return fail("Phase telemetry outcome is invalid.", "INVALID_INPUT");
|
|
2390
|
+
const tokens = raw["tokens"] === void 0 ? void 0 : raw["tokens"];
|
|
2391
|
+
if (tokens) {
|
|
2392
|
+
for (const key of ["inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens", "costUsd"]) if (tokens[key] !== void 0) finite(tokens[key], `tokens.${key}`);
|
|
2393
|
+
}
|
|
2394
|
+
const machine = raw["machine"] === void 0 ? void 0 : raw["machine"];
|
|
2395
|
+
if (machine) {
|
|
2396
|
+
for (const key of ["cpuPercent", "memoryUsedPercent", "peakConcurrency", "queueWaitMs", "contentionMs", "saturationPercent"]) if (machine[key] !== void 0) finite(machine[key], `machine.${key}`, ["cpuPercent", "memoryUsedPercent", "saturationPercent"].includes(key) ? 100 : void 0);
|
|
2397
|
+
}
|
|
2398
|
+
return {
|
|
2399
|
+
phaseId: phase(raw["phaseId"]),
|
|
2400
|
+
...raw["durationMs"] === void 0 ? {} : { durationMs: finite(raw["durationMs"], "durationMs") },
|
|
2401
|
+
...raw["attempts"] === void 0 ? {} : { attempts: integer(raw["attempts"], "attempts") },
|
|
2402
|
+
outcome,
|
|
2403
|
+
...raw["failureClass"] === void 0 ? {} : { failureClass: phase(raw["failureClass"]) },
|
|
2404
|
+
...raw["evidenceCoverage"] === void 0 ? {} : { evidenceCoverage: finite(raw["evidenceCoverage"], "evidenceCoverage", 1) },
|
|
2405
|
+
...tokens ? { tokens } : {},
|
|
2406
|
+
...machine ? { machine } : {}
|
|
2407
|
+
};
|
|
2408
|
+
};
|
|
2409
|
+
var average = (values) => values.length ? Number((values.reduce((sum, value) => sum + value, 0) / values.length).toFixed(2)) : null;
|
|
2410
|
+
var score2 = (value, source, baseline = null) => ({ score: value === null ? null : Math.max(0, Math.min(100, Number(value.toFixed(2)))), status: value === null ? "unknown" : "measured", baselineDelta: value === null || baseline === null ? null : Number((value - baseline).toFixed(2)), source });
|
|
2411
|
+
var evaluateWatchdog = ({ phases, budget }) => {
|
|
2412
|
+
const blockers = [];
|
|
2413
|
+
const duration5 = phases.every((phase2) => phase2.durationMs !== void 0) ? phases.reduce((sum, phase2) => sum + (phase2.durationMs ?? 0), 0) : void 0;
|
|
2414
|
+
const totalTokens = phases.every((phase2) => phase2.tokens?.inputTokens !== void 0 && phase2.tokens.outputTokens !== void 0) ? phases.reduce((sum, phase2) => sum + (phase2.tokens?.inputTokens ?? 0) + (phase2.tokens?.outputTokens ?? 0), 0) : void 0;
|
|
2415
|
+
if (budget.maxDurationMs !== void 0 && duration5 !== void 0 && duration5 > budget.maxDurationMs) blockers.push({ class: "budget", reason: `Duration budget exceeded: ${duration5}ms > ${budget.maxDurationMs}ms.` });
|
|
2416
|
+
if (budget.maxTotalTokens !== void 0 && totalTokens !== void 0 && totalTokens > budget.maxTotalTokens) blockers.push({ class: "budget", reason: `Token budget exceeded: ${totalTokens} > ${budget.maxTotalTokens}.` });
|
|
2417
|
+
for (const phase2 of phases) {
|
|
2418
|
+
if (budget.maxMemoryUsedPercent !== void 0 && phase2.machine?.memoryUsedPercent !== void 0 && phase2.machine.memoryUsedPercent > budget.maxMemoryUsedPercent) blockers.push({ class: "resource", phaseId: phase2.phaseId, reason: `Memory saturation exceeded: ${phase2.machine.memoryUsedPercent}% > ${budget.maxMemoryUsedPercent}%.` });
|
|
2419
|
+
if (budget.maxSaturationPercent !== void 0 && phase2.machine?.saturationPercent !== void 0 && phase2.machine.saturationPercent > budget.maxSaturationPercent) blockers.push({ class: "contention", phaseId: phase2.phaseId, reason: `Saturation exceeded: ${phase2.machine.saturationPercent}% > ${budget.maxSaturationPercent}%.` });
|
|
2420
|
+
}
|
|
2421
|
+
return { status: blockers.length ? "blocked" : "ok", blockers };
|
|
2422
|
+
};
|
|
2423
|
+
var createQualityMatrix = ({ phases, baseline, budget = {} }) => {
|
|
2424
|
+
const current = phases.map(validatePhaseTelemetry);
|
|
2425
|
+
const prior = baseline?.map(validatePhaseTelemetry) ?? [];
|
|
2426
|
+
const currentDurations = current.flatMap((phase2) => phase2.durationMs === void 0 ? [] : [phase2.durationMs]);
|
|
2427
|
+
const priorDurations = prior.flatMap((phase2) => phase2.durationMs === void 0 ? [] : [phase2.durationMs]);
|
|
2428
|
+
const currentTokens = current.flatMap((phase2) => phase2.tokens?.inputTokens !== void 0 && phase2.tokens.outputTokens !== void 0 ? [phase2.tokens.inputTokens + phase2.tokens.outputTokens] : []);
|
|
2429
|
+
const priorTokens = prior.flatMap((phase2) => phase2.tokens?.inputTokens !== void 0 && phase2.tokens.outputTokens !== void 0 ? [phase2.tokens.inputTokens + phase2.tokens.outputTokens] : []);
|
|
2430
|
+
const correctness = score2(average(current.map((phase2) => phase2.evidenceCoverage === void 0 ? 0 : phase2.evidenceCoverage * 100)), "mean evidence coverage");
|
|
2431
|
+
const completeness = score2(current.length ? current.filter((phase2) => phase2.outcome === "pass").length / current.length * 100 : null, "passed phases / total phases");
|
|
2432
|
+
const speed = score2(currentDurations.length && priorDurations.length ? average(priorDurations) / Math.max(1, average(currentDurations)) * 100 : null, "baseline duration / current duration", 100);
|
|
2433
|
+
const cost = score2(currentTokens.length && priorTokens.length ? average(priorTokens) / Math.max(1, average(currentTokens)) * 100 : null, "baseline tokens / current tokens", 100);
|
|
2434
|
+
const resourceValues = current.flatMap((phase2) => phase2.machine?.cpuPercent !== void 0 && phase2.machine.memoryUsedPercent !== void 0 ? [100 - Math.max(phase2.machine.cpuPercent, phase2.machine.memoryUsedPercent)] : []);
|
|
2435
|
+
const resource = score2(average(resourceValues), "100 - max(cpu%, memory%)");
|
|
2436
|
+
const reliability = score2(current.length ? current.filter((phase2) => phase2.outcome === "pass").length / current.length * 100 : null, "passed phases / total phases");
|
|
2437
|
+
const dimensions = { correctness, completeness, speed, cost, resource, reliability };
|
|
2438
|
+
const measured = Object.values(dimensions).filter((item) => item.score !== null).map((item) => item.score);
|
|
2439
|
+
const overall = score2(average(measured), "mean of measured dimensions");
|
|
2440
|
+
const unknownMetricCount = Object.values(dimensions).filter((item) => item.status === "unknown").length + current.filter((phase2) => phase2.durationMs === void 0 || phase2.tokens === void 0 || phase2.machine === void 0).length;
|
|
2441
|
+
const blockers = evaluateWatchdog({ phases: current, budget }).blockers;
|
|
2442
|
+
const body3 = { type: "agentskit-harness-quality-matrix", schemaVersion: 1, dimensions, overall, phaseCount: current.length, unknownMetricCount, blockers };
|
|
2443
|
+
return { ...body3, digest: hashJson(body3) };
|
|
2444
|
+
};
|
|
2445
|
+
|
|
2446
|
+
// src/kernel/compatibility.ts
|
|
2447
|
+
var COMPATIBILITY_SCHEMA_VERSION = 1;
|
|
2448
|
+
var COMPATIBILITY_COMPONENTS = ["core", "memory", "eval", "doc-bridge", "code-review", "adapter-boundary", "runtime"];
|
|
2449
|
+
var text4 = (value, label) => {
|
|
2450
|
+
const result = typeof value === "string" ? value.trim() : "";
|
|
2451
|
+
if (!result) fail(`${label} is required.`, "INVALID_INPUT");
|
|
2452
|
+
return result;
|
|
2453
|
+
};
|
|
2454
|
+
var sha = (value, label) => {
|
|
2455
|
+
const result = text4(value, label);
|
|
2456
|
+
if (!/^[a-f0-9]{40,64}$/.test(result)) fail(`${label} must be a pinned git revision.`, "INVALID_INPUT");
|
|
2457
|
+
return result;
|
|
2458
|
+
};
|
|
2459
|
+
var component = (value, index2) => {
|
|
2460
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) fail(`components[${index2}] must be an object.`, "INVALID_INPUT");
|
|
2461
|
+
const candidate = value;
|
|
2462
|
+
const id2 = text4(candidate["id"], `components[${index2}].id`);
|
|
2463
|
+
if (!COMPATIBILITY_COMPONENTS.includes(id2)) fail(`components[${index2}].id is invalid.`, "INVALID_INPUT");
|
|
2464
|
+
if (candidate["adapterBoundary"] !== "real-adapter") fail(`components[${index2}] must use the real-adapter boundary.`, "INVALID_INPUT");
|
|
2465
|
+
return {
|
|
2466
|
+
id: id2,
|
|
2467
|
+
package: text4(candidate["package"], `components[${index2}].package`),
|
|
2468
|
+
version: text4(candidate["version"], `components[${index2}].version`),
|
|
2469
|
+
revision: sha(candidate["revision"], `components[${index2}].revision`),
|
|
2470
|
+
repository: text4(candidate["repository"], `components[${index2}].repository`),
|
|
2471
|
+
adapterBoundary: "real-adapter",
|
|
2472
|
+
testCommand: text4(candidate["testCommand"], `components[${index2}].testCommand`),
|
|
2473
|
+
evalCommand: text4(candidate["evalCommand"], `components[${index2}].evalCommand`),
|
|
2474
|
+
previousVersion: text4(candidate["previousVersion"], `components[${index2}].previousVersion`),
|
|
2475
|
+
noHarnessBaseline: text4(candidate["noHarnessBaseline"], `components[${index2}].noHarnessBaseline`),
|
|
2476
|
+
migrationEvidence: text4(candidate["migrationEvidence"], `components[${index2}].migrationEvidence`),
|
|
2477
|
+
rollbackEvidence: text4(candidate["rollbackEvidence"], `components[${index2}].rollbackEvidence`)
|
|
2478
|
+
};
|
|
2479
|
+
};
|
|
2480
|
+
var body = (value) => {
|
|
2481
|
+
const componentsValue = value["components"];
|
|
2482
|
+
if (!Array.isArray(componentsValue) || !componentsValue.length) fail("components must be a non-empty array.", "INVALID_INPUT");
|
|
2483
|
+
const components = componentsValue.map(component);
|
|
2484
|
+
if (new Set(components.map((item) => item.id)).size !== components.length) fail("component ids must be unique.", "INVALID_INPUT");
|
|
2485
|
+
const missing = COMPATIBILITY_COMPONENTS.filter((id2) => !components.some((item) => item.id === id2));
|
|
2486
|
+
if (missing.length) fail(`components must cover: ${missing.join(", ")}.`, "INVALID_INPUT");
|
|
2487
|
+
const outputs = value["evidenceOutputs"];
|
|
2488
|
+
if (!Array.isArray(outputs) || !outputs.length) fail("evidenceOutputs must be a non-empty array.", "INVALID_INPUT");
|
|
2489
|
+
return {
|
|
2490
|
+
type: "agentskit-harness-compatibility-manifest",
|
|
2491
|
+
schemaVersion: COMPATIBILITY_SCHEMA_VERSION,
|
|
2492
|
+
harnessVersion: text4(value["harnessVersion"], "harnessVersion"),
|
|
2493
|
+
sourceRevision: sha(value["sourceRevision"], "sourceRevision"),
|
|
2494
|
+
components,
|
|
2495
|
+
evidenceOutputs: outputs.map((item, index2) => text4(item, `evidenceOutputs[${index2}]`))
|
|
2496
|
+
};
|
|
2497
|
+
};
|
|
2498
|
+
var createCompatibilityManifest = (input) => {
|
|
2499
|
+
const value = body(input);
|
|
2500
|
+
return { ...value, digest: hashJson(value) };
|
|
2501
|
+
};
|
|
2502
|
+
var validateCompatibilityManifest = (value) => {
|
|
2503
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) fail("Compatibility manifest must be an object.", "INVALID_INPUT");
|
|
2504
|
+
const candidate = value;
|
|
2505
|
+
const valueBody = body(candidate);
|
|
2506
|
+
if (candidate["type"] !== valueBody.type || candidate["schemaVersion"] !== valueBody.schemaVersion) fail("Compatibility manifest type or schemaVersion is invalid.", "INVALID_INPUT");
|
|
2507
|
+
const digest6 = text4(candidate["digest"], "digest");
|
|
2508
|
+
if (digest6 !== hashJson(valueBody)) fail("Compatibility manifest digest is invalid.", "INVALID_INPUT");
|
|
2509
|
+
return { ...valueBody, digest: digest6 };
|
|
2510
|
+
};
|
|
2511
|
+
var assessCompatibility = ({ manifest, observations }) => {
|
|
2512
|
+
const validated = validateCompatibilityManifest(manifest);
|
|
2513
|
+
const expected = new Set(validated.components.map((item) => item.id));
|
|
2514
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2515
|
+
const blockers = [];
|
|
2516
|
+
observations.forEach((observation) => {
|
|
2517
|
+
if (!expected.has(observation.componentId) || seen.has(observation.componentId)) blockers.push(`${observation.componentId}: unexpected or duplicate observation`);
|
|
2518
|
+
seen.add(observation.componentId);
|
|
2519
|
+
if (observation.status !== "passed") blockers.push(`${observation.componentId}: ${observation.status} compatibility evidence`);
|
|
2520
|
+
if (!observation.evidence) blockers.push(`${observation.componentId}: missing evidence`);
|
|
2521
|
+
});
|
|
2522
|
+
validated.components.forEach((item) => {
|
|
2523
|
+
if (!seen.has(item.id)) blockers.push(`${item.id}: missing observation`);
|
|
2524
|
+
const observation = observations.find((candidate) => candidate.componentId === item.id);
|
|
2525
|
+
if (observation && observation.previousVersion !== item.previousVersion) blockers.push(`${item.id}: previous version binding mismatch`);
|
|
2526
|
+
if (observation && observation.noHarnessBaseline !== item.noHarnessBaseline) blockers.push(`${item.id}: no-Harness baseline binding mismatch`);
|
|
2527
|
+
});
|
|
2528
|
+
return { status: blockers.length ? "blocked" : "passed", componentCount: validated.components.length, observations, blockers };
|
|
2529
|
+
};
|
|
2530
|
+
|
|
2531
|
+
// src/kernel/resilience.ts
|
|
2532
|
+
var positiveInteger = (value, label) => {
|
|
2533
|
+
if (!Number.isInteger(value) || value < 1) fail(`${label} must be a positive integer.`, "INVALID_INPUT");
|
|
2534
|
+
return value;
|
|
2535
|
+
};
|
|
2536
|
+
var nonNegativeInteger2 = (value, label) => {
|
|
2537
|
+
if (!Number.isInteger(value) || value < 0) fail(`${label} must be a non-negative integer.`, "INVALID_INPUT");
|
|
2538
|
+
return value;
|
|
2539
|
+
};
|
|
2540
|
+
var classifyFailure = (error) => {
|
|
2541
|
+
const value = error;
|
|
2542
|
+
const code = typeof value?.code === "string" ? value.code.toUpperCase() : "";
|
|
2543
|
+
const message = typeof value?.message === "string" ? value.message : String(error);
|
|
2544
|
+
const text7 = `${code} ${message}`.toLowerCase();
|
|
2545
|
+
if (/quota|rate.?limit|too many requests|429/.test(text7)) return { class: "quota", retryable: true, reason: message };
|
|
2546
|
+
if (/timeout|timed out|deadline/.test(text7)) return { class: "timeout", retryable: true, reason: message };
|
|
2547
|
+
if (/policy|forbidden|permission|approval/.test(text7)) return { class: "policy", retryable: false, reason: message };
|
|
2548
|
+
if (/invalid|schema|argument|config|validation/.test(text7)) return { class: "validation", retryable: false, reason: message };
|
|
2549
|
+
if (/network|connection|econn|503|502|external/.test(text7)) return { class: "external", retryable: true, reason: message };
|
|
2550
|
+
return { class: "unknown", retryable: false, reason: message };
|
|
2551
|
+
};
|
|
2552
|
+
var recoveryDelayMs = (attempt, policy) => {
|
|
2553
|
+
positiveInteger(attempt, "attempt");
|
|
2554
|
+
nonNegativeInteger2(policy.baseDelayMs, "baseDelayMs");
|
|
2555
|
+
nonNegativeInteger2(policy.maxDelayMs, "maxDelayMs");
|
|
2556
|
+
if (policy.maxDelayMs < policy.baseDelayMs) fail("maxDelayMs must be greater than or equal to baseDelayMs.", "INVALID_INPUT");
|
|
2557
|
+
return Math.min(policy.maxDelayMs, policy.baseDelayMs * 2 ** Math.max(0, attempt - 1));
|
|
2558
|
+
};
|
|
2559
|
+
var wait = (delayMs, sleep) => delayMs > 0 ? sleep(delayMs) : Promise.resolve();
|
|
2560
|
+
var runWithRecovery = async (operation, options) => {
|
|
2561
|
+
const maxAttempts = positiveInteger(options.maxAttempts, "maxAttempts");
|
|
2562
|
+
const baseDelayMs = nonNegativeInteger2(options.baseDelayMs, "baseDelayMs");
|
|
2563
|
+
const maxDelayMs = nonNegativeInteger2(options.maxDelayMs, "maxDelayMs");
|
|
2564
|
+
if (maxDelayMs < baseDelayMs) fail("maxDelayMs must be greater than or equal to baseDelayMs.", "INVALID_INPUT");
|
|
2565
|
+
if (options.timeoutMs !== void 0) positiveInteger(options.timeoutMs, "timeoutMs");
|
|
2566
|
+
const sleep = options.sleep ?? ((delayMs) => new Promise((resolve6) => setTimeout(resolve6, delayMs)));
|
|
2567
|
+
const observations = [];
|
|
2568
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
2569
|
+
const controller = new AbortController();
|
|
2570
|
+
let timer;
|
|
2571
|
+
try {
|
|
2572
|
+
const operationPromise = operation(controller.signal, attempt);
|
|
2573
|
+
const value = options.timeoutMs === void 0 ? await operationPromise : await Promise.race([
|
|
2574
|
+
operationPromise,
|
|
2575
|
+
new Promise((_, reject) => {
|
|
2576
|
+
timer = setTimeout(() => {
|
|
2577
|
+
controller.abort();
|
|
2578
|
+
reject(new Error("operation timed out"));
|
|
2579
|
+
}, options.timeoutMs);
|
|
2580
|
+
})
|
|
2581
|
+
]);
|
|
2582
|
+
return { status: "completed", attempts: attempt, observations, value };
|
|
2583
|
+
} catch (error) {
|
|
2584
|
+
const failure = classifyFailure(error);
|
|
2585
|
+
const delayMs = failure.retryable && attempt < maxAttempts ? recoveryDelayMs(attempt, { baseDelayMs, maxDelayMs }) : 0;
|
|
2586
|
+
const observation = { attempt, failure, delayMs };
|
|
2587
|
+
observations.push(observation);
|
|
2588
|
+
options.onObservation?.(observation);
|
|
2589
|
+
if (!failure.retryable || attempt >= maxAttempts) return { status: "failed", attempts: attempt, observations, failure };
|
|
2590
|
+
await wait(delayMs, sleep);
|
|
2591
|
+
} finally {
|
|
2592
|
+
if (timer) clearTimeout(timer);
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
return fail("Recovery loop exhausted unexpectedly.", "HARNESS_ERROR");
|
|
2596
|
+
};
|
|
2597
|
+
|
|
2598
|
+
// src/adapters/agent.ts
|
|
2599
|
+
var required7 = (value, label) => {
|
|
2600
|
+
if (typeof value !== "string" || !value.trim()) return fail(`${label} is required.`, "INVALID_INPUT");
|
|
2601
|
+
return value.trim();
|
|
2602
|
+
};
|
|
2603
|
+
var duration2 = (value) => Number.isFinite(value) && value >= 0 ? value : fail("Agent durationMs must be non-negative.", "INVALID_INPUT");
|
|
2604
|
+
var usage = (value) => {
|
|
2605
|
+
if (value === void 0) return { status: "unknown" };
|
|
2606
|
+
if (value.status !== "measured" && value.status !== "unknown") return fail("Agent usage status is invalid.", "INVALID_INPUT");
|
|
2607
|
+
for (const key of ["inputTokens", "outputTokens", "totalTokens"]) if (value[key] !== void 0 && (!Number.isFinite(value[key]) || value[key] < 0)) return fail(`Agent usage ${key} must be non-negative.`, "INVALID_INPUT");
|
|
2608
|
+
return value;
|
|
2609
|
+
};
|
|
2610
|
+
var createCodingAgentAdapter = ({ id: id2, version, assurance = "contract-tested", timeoutMs = 12e4, execute }) => {
|
|
2611
|
+
const adapterId = required7(id2, "agent.id");
|
|
2612
|
+
const adapterVersion = required7(version, "agent.version");
|
|
2613
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 1) return fail("agent.timeoutMs must be a positive integer.", "INVALID_INPUT");
|
|
2614
|
+
return {
|
|
2615
|
+
id: adapterId,
|
|
2616
|
+
version: adapterVersion,
|
|
2617
|
+
assurance,
|
|
2618
|
+
execute: async (request) => {
|
|
2619
|
+
const issueRef = required7(request.issueRef, "agent.issueRef");
|
|
2620
|
+
const prompt = required7(request.prompt, "agent.prompt");
|
|
2621
|
+
const sourceRevision = required7(request.sourceRevision, "agent.sourceRevision");
|
|
2622
|
+
const controller = new AbortController();
|
|
2623
|
+
const signal = request.signal;
|
|
2624
|
+
if (signal?.aborted) return { status: "cancelled", output: {}, diff: "", usage: { status: "unknown" }, durationMs: 0, failure: { class: "policy", retryable: false, reason: "Agent execution was cancelled before start." }, metadata: { assurance, telemetry: { status: "measured", durationMs: 0 } } };
|
|
2625
|
+
const onAbort = () => controller.abort();
|
|
2626
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2627
|
+
const started = Date.now();
|
|
2628
|
+
let timer;
|
|
2629
|
+
let timedOut = false;
|
|
2630
|
+
try {
|
|
2631
|
+
const operation = Promise.resolve(execute({ issueRef, prompt, sourceRevision, ...request.contextHash ? { contextHash: request.contextHash } : {}, signal: controller.signal }));
|
|
2632
|
+
const timeout2 = new Promise((_, reject) => {
|
|
2633
|
+
timer = setTimeout(() => {
|
|
2634
|
+
timedOut = true;
|
|
2635
|
+
controller.abort();
|
|
2636
|
+
reject(new Error("agent execution timed out"));
|
|
2637
|
+
}, timeoutMs);
|
|
2638
|
+
});
|
|
2639
|
+
const result = await Promise.race([operation, timeout2]);
|
|
2640
|
+
if (!result || typeof result !== "object" || Array.isArray(result) || typeof result.output !== "object" || result.output === null || Array.isArray(result.output) || typeof result.diff !== "string") return fail("Agent result must contain structured output and diff.", "INVALID_INPUT");
|
|
2641
|
+
const measuredUsage = usage(result.usage);
|
|
2642
|
+
const durationMs = duration2(Date.now() - started);
|
|
2643
|
+
return { status: "completed", output: result.output, diff: result.diff, usage: measuredUsage, durationMs, metadata: { assurance, telemetry: { status: measuredUsage.status, durationMs, ...measuredUsage.inputTokens === void 0 ? {} : { inputTokens: measuredUsage.inputTokens }, ...measuredUsage.outputTokens === void 0 ? {} : { outputTokens: measuredUsage.outputTokens }, ...measuredUsage.totalTokens === void 0 ? {} : { totalTokens: measuredUsage.totalTokens } } } };
|
|
2644
|
+
} catch (error) {
|
|
2645
|
+
const failure = timedOut ? { class: "timeout", retryable: true, reason: "Agent execution timed out." } : classifyFailure(error);
|
|
2646
|
+
const status = timedOut ? "timeout" : controller.signal.aborted ? "cancelled" : "failed";
|
|
2647
|
+
return { status, output: {}, diff: "", usage: { status: "unknown" }, durationMs: duration2(Date.now() - started), failure, metadata: { assurance, telemetry: { status: "unknown", durationMs: duration2(Date.now() - started) } } };
|
|
2648
|
+
} finally {
|
|
2649
|
+
if (timer) clearTimeout(timer);
|
|
2650
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
};
|
|
2654
|
+
};
|
|
907
2655
|
var BENCHMARK_SCHEMA_VERSION = 1;
|
|
908
2656
|
var percentage = (part, total) => total ? Number((part / total).toFixed(4)) : null;
|
|
909
|
-
var DEFAULT_POLICY = { minComparableTasks: 3, maxDurationRegressionRate: 0.2, minCompletedRunsPerTask: 3, minBaselineSamplesPerTask: 3, requireZeroEscapedIncomplete: true };
|
|
910
2657
|
var improvementRate = (baseline, current) => baseline === void 0 || current === void 0 || baseline === 0 ? null : Number(((baseline - current) / baseline).toFixed(4));
|
|
911
|
-
var increaseRate = (baseline, current) => baseline === void 0 || current === void 0 || baseline === 0 ? null : Number(((current - baseline) / baseline).toFixed(4));
|
|
912
|
-
var increaseDelta = (baseline, current) => baseline === void 0 || current === null ? null : Number((current - baseline).toFixed(4));
|
|
913
|
-
var increaseDirection = (rate2, delta) => delta === null ? improvementDirection(rate2) : delta > 0 ? "improved" : delta < 0 ? "regressed" : "unchanged";
|
|
914
2658
|
var improvementDirection = (rate2) => rate2 === null ? "unavailable" : rate2 > 0 ? "improved" : rate2 < 0 ? "regressed" : "unchanged";
|
|
915
2659
|
var count = (items, predicate) => items.filter(predicate).length;
|
|
916
|
-
var
|
|
2660
|
+
var median2 = (values) => {
|
|
917
2661
|
if (!values.length) return null;
|
|
918
2662
|
const sorted = [...values].sort((left, right) => left - right);
|
|
919
2663
|
const middle = Math.floor(sorted.length / 2);
|
|
@@ -926,25 +2670,13 @@ var reviewMinutes = (run) => {
|
|
|
926
2670
|
const elapsed = Date.parse(run.humanApproval.at) - Date.parse(reviewStart);
|
|
927
2671
|
return Number.isFinite(elapsed) && elapsed >= 0 ? Number((elapsed / 6e4).toFixed(2)) : void 0;
|
|
928
2672
|
};
|
|
929
|
-
var confidence = (comparable, completedRuns, policy) => !comparable ? "insufficient" : completedRuns >= policy.minCompletedRunsPerTask ? "reliable" : "directional";
|
|
930
|
-
var artifactAcceptanceRate = (run) => {
|
|
931
|
-
const rates = run.checks.flatMap((check) => {
|
|
932
|
-
const evidence = check.evidence;
|
|
933
|
-
if (!evidence) return [];
|
|
934
|
-
const direct = typeof evidence["artifactAcceptanceRate"] === "number" ? [evidence["artifactAcceptanceRate"]] : [];
|
|
935
|
-
const reports = Array.isArray(evidence["reports"]) ? evidence["reports"].flatMap((report) => typeof report === "object" && report !== null && typeof report["artifactAcceptanceRate"] === "number" ? [report["artifactAcceptanceRate"]] : []) : [];
|
|
936
|
-
return [...direct, ...reports].filter((rate2) => Number.isFinite(rate2) && rate2 >= 0 && rate2 <= 1);
|
|
937
|
-
});
|
|
938
|
-
return rates.length ? Number((rates.reduce((total, rate2) => total + rate2, 0) / rates.length).toFixed(4)) : void 0;
|
|
939
|
-
};
|
|
940
2673
|
var projectRun = (run) => {
|
|
941
2674
|
const checks = { total: run.checks.length, passed: count(run.checks, (check) => check.status === "passed"), failed: count(run.checks, (check) => check.status === "failed") };
|
|
942
2675
|
const outcomes = { total: run.outcomes.length, passed: count(run.outcomes, (outcome) => outcome.status === "passed"), failed: count(run.outcomes, (outcome) => outcome.status === "failed") };
|
|
943
2676
|
const evidence = { total: run.checks.length, attached: count(run.checks, (check) => check.evidence !== void 0) };
|
|
944
|
-
const acceptanceRate = artifactAcceptanceRate(run);
|
|
945
2677
|
const humanReviewMinutes = reviewMinutes(run);
|
|
946
2678
|
const escapedIncomplete = run.state === "COMPLETE" && checks.failed === 0 && outcomes.failed === 0 && evidence.attached === evidence.total ? 0 : void 0;
|
|
947
|
-
return { runId: run.runId, state: run.state, sourceRevision: run.sourceRevision, configHash: run.configHash, contractHash: run.contractHash, ...run.supersedes ? { supersedes: run.supersedes } : {}, ...run.metrics ? { durationMs: run.metrics.totalDurationMs } : {}, checks, outcomes, evidence, ...
|
|
2679
|
+
return { runId: run.runId, state: run.state, sourceRevision: run.sourceRevision, configHash: run.configHash, contractHash: run.contractHash, ...run.supersedes ? { supersedes: run.supersedes } : {}, ...run.metrics ? { durationMs: run.metrics.totalDurationMs } : {}, checks, outcomes, evidence, ...escapedIncomplete === void 0 ? {} : { escapedIncomplete }, humanApproved: run.humanApproval !== void 0, ...humanReviewMinutes === void 0 ? {} : { humanReviewMinutes }, authorized: run.authorization !== void 0, ...run.metrics?.machine ? { machine: run.metrics.machine } : {}, ...run.benchmark ? { benchmark: run.benchmark } : {} };
|
|
948
2680
|
};
|
|
949
2681
|
var summarize = (runs) => {
|
|
950
2682
|
const stateCounts = Object.fromEntries(RUN_STATES.map((state) => [state, count(runs, (run) => run.state === state)]));
|
|
@@ -955,14 +2687,6 @@ var summarize = (runs) => {
|
|
|
955
2687
|
const evidenceTotal = runs.reduce((total, run) => total + run.evidence.total, 0);
|
|
956
2688
|
const evidenceAttached = runs.reduce((total, run) => total + run.evidence.attached, 0);
|
|
957
2689
|
const firstAttempts = runs.filter((run) => !run.supersedes);
|
|
958
|
-
const superseded = new Set(runs.flatMap((run) => run.supersedes ? [run.supersedes] : []));
|
|
959
|
-
const effectiveRuns = runs.filter((run) => !superseded.has(run.runId));
|
|
960
|
-
const effectiveChecksTotal = effectiveRuns.reduce((total, run) => total + run.checks.total, 0);
|
|
961
|
-
const effectiveChecksPassed = effectiveRuns.reduce((total, run) => total + run.checks.passed, 0);
|
|
962
|
-
const effectiveOutcomesTotal = effectiveRuns.reduce((total, run) => total + run.outcomes.total, 0);
|
|
963
|
-
const effectiveOutcomesPassed = effectiveRuns.reduce((total, run) => total + run.outcomes.passed, 0);
|
|
964
|
-
const effectiveEvidenceTotal = effectiveRuns.reduce((total, run) => total + run.evidence.total, 0);
|
|
965
|
-
const effectiveEvidenceAttached = effectiveRuns.reduce((total, run) => total + run.evidence.attached, 0);
|
|
966
2690
|
const durations = runs.flatMap((run) => run.durationMs === void 0 ? [] : [run.durationMs]);
|
|
967
2691
|
return {
|
|
968
2692
|
totalRuns: runs.length,
|
|
@@ -973,20 +2697,14 @@ var summarize = (runs) => {
|
|
|
973
2697
|
firstAttemptRuns: firstAttempts.length,
|
|
974
2698
|
humanApprovedRuns: count(runs, (run) => run.humanApproved),
|
|
975
2699
|
authorizedRuns: count(runs, (run) => run.authorized),
|
|
976
|
-
effectiveRunCount: effectiveRuns.length,
|
|
977
|
-
effectiveCompleteRuns: count(effectiveRuns, (run) => run.state === "COMPLETE"),
|
|
978
|
-
effectiveCompletionRate: percentage(count(effectiveRuns, (run) => run.state === "COMPLETE"), effectiveRuns.length),
|
|
979
|
-
effectiveCheckPassRate: percentage(effectiveChecksPassed, effectiveChecksTotal),
|
|
980
|
-
effectiveOutcomePassRate: percentage(effectiveOutcomesPassed, effectiveOutcomesTotal),
|
|
981
|
-
effectiveEvidenceCoverageRate: percentage(effectiveEvidenceAttached, effectiveEvidenceTotal),
|
|
982
2700
|
checkPassRate: percentage(checksPassed, checksTotal),
|
|
983
2701
|
outcomePassRate: percentage(outcomesPassed, outcomesTotal),
|
|
984
2702
|
evidenceCoverageRate: percentage(evidenceAttached, evidenceTotal),
|
|
985
2703
|
firstAttemptApprovalRate: percentage(count(firstAttempts, (run) => run.humanApproved), firstAttempts.length),
|
|
986
2704
|
retryRate: percentage(count(runs, (run) => run.supersedes !== void 0), runs.length),
|
|
987
2705
|
staleRate: percentage(stateCounts.STALE, runs.length),
|
|
988
|
-
averageDurationMs: durations.length ? Math.round(durations.reduce((total,
|
|
989
|
-
medianDurationMs:
|
|
2706
|
+
averageDurationMs: durations.length ? Math.round(durations.reduce((total, duration5) => total + duration5, 0) / durations.length) : null,
|
|
2707
|
+
medianDurationMs: median2(durations)
|
|
990
2708
|
};
|
|
991
2709
|
};
|
|
992
2710
|
var readRuns = (stateDir) => {
|
|
@@ -1013,7 +2731,7 @@ var sha2562 = (value, label) => {
|
|
|
1013
2731
|
if (!/^[a-f0-9]{64}$/.test(result)) return fail(`${label} must be a lowercase SHA-256 digest.`, "INVALID_CONFIG");
|
|
1014
2732
|
return result;
|
|
1015
2733
|
};
|
|
1016
|
-
var
|
|
2734
|
+
var stringList2 = (value, label) => {
|
|
1017
2735
|
if (!Array.isArray(value)) return fail(`${label} must be a non-empty string array.`, "INVALID_CONFIG");
|
|
1018
2736
|
const items = value;
|
|
1019
2737
|
if (!items.length || !items.every((item) => typeof item === "string" && Boolean(item.trim()))) return fail(`${label} must be a non-empty string array.`, "INVALID_CONFIG");
|
|
@@ -1026,73 +2744,16 @@ var nonNegativeNumber = (value, label) => {
|
|
|
1026
2744
|
const result = value;
|
|
1027
2745
|
return result;
|
|
1028
2746
|
};
|
|
1029
|
-
var
|
|
2747
|
+
var nonNegativeInteger3 = (value, label) => {
|
|
1030
2748
|
const result = nonNegativeNumber(value, label);
|
|
1031
2749
|
if (result !== void 0 && !Number.isInteger(result)) return fail(`${label} must be an integer.`, "INVALID_CONFIG");
|
|
1032
2750
|
return result;
|
|
1033
2751
|
};
|
|
1034
|
-
var rate = (value, label) => {
|
|
1035
|
-
const result = nonNegativeNumber(value, label);
|
|
1036
|
-
if (result !== void 0 && result > 1) return fail(`${label} must be between 0 and 1.`, "INVALID_CONFIG");
|
|
1037
|
-
return result;
|
|
1038
|
-
};
|
|
1039
2752
|
var timestamp = (value, label) => {
|
|
1040
2753
|
const result = nonEmptyString(value, label);
|
|
1041
2754
|
if (!Number.isFinite(Date.parse(result))) return fail(`${label} must be a valid timestamp.`, "INVALID_CONFIG");
|
|
1042
2755
|
return result;
|
|
1043
2756
|
};
|
|
1044
|
-
var relativePath = (value, label) => {
|
|
1045
|
-
const result = nonEmptyString(value, label);
|
|
1046
|
-
if (result.startsWith("/") || result.split("/").includes("..")) return fail(`${label} must be a repository-relative path.`, "INVALID_CONFIG");
|
|
1047
|
-
return result;
|
|
1048
|
-
};
|
|
1049
|
-
var taskFile = (value, label) => {
|
|
1050
|
-
if (value === void 0) return void 0;
|
|
1051
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return fail(`${label} must be an object.`, "INVALID_CONFIG");
|
|
1052
|
-
const raw = value;
|
|
1053
|
-
return { path: relativePath(raw["path"], `${label}.path`), sha256: sha2562(raw["sha256"], `${label}.sha256`) ?? fail(`${label}.sha256 is required.`, "INVALID_CONFIG") };
|
|
1054
|
-
};
|
|
1055
|
-
var taskSource = (value, label) => {
|
|
1056
|
-
if (value === void 0) return void 0;
|
|
1057
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return fail(`${label} must be an object.`, "INVALID_CONFIG");
|
|
1058
|
-
const raw = value;
|
|
1059
|
-
return { repository: nonEmptyString(raw["repository"], `${label}.repository`), path: relativePath(raw["path"], `${label}.path`), revision: nonEmptyString(raw["revision"], `${label}.revision`) };
|
|
1060
|
-
};
|
|
1061
|
-
var suiteSource = (value, label) => {
|
|
1062
|
-
if (value === void 0) return void 0;
|
|
1063
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return fail(`${label} must be an object.`, "INVALID_CONFIG");
|
|
1064
|
-
const raw = value;
|
|
1065
|
-
return { repository: nonEmptyString(raw["repository"], `${label}.repository`), revision: nonEmptyString(raw["revision"], `${label}.revision`), taskDefinition: relativePath(raw["taskDefinition"], `${label}.taskDefinition`) };
|
|
1066
|
-
};
|
|
1067
|
-
var taskScope = (value, label) => {
|
|
1068
|
-
if (value === void 0) return void 0;
|
|
1069
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return fail(`${label} must be an object.`, "INVALID_CONFIG");
|
|
1070
|
-
const raw = value;
|
|
1071
|
-
return { read: stringList(raw["read"], `${label}.read`), write: stringList(raw["write"], `${label}.write`) };
|
|
1072
|
-
};
|
|
1073
|
-
var taskSurfaces = (value, label) => {
|
|
1074
|
-
if (value === void 0) return void 0;
|
|
1075
|
-
if (!Array.isArray(value) || !value.length) return fail(`${label} must be a non-empty array.`, "INVALID_CONFIG");
|
|
1076
|
-
const surfaces = value.map((item, index2) => nonEmptyString(item, `${label}[${index2}]`));
|
|
1077
|
-
if (surfaces.some((surface2) => !SURFACE_NAMES.includes(surface2))) fail(`${label} contains an unknown surface.`, "INVALID_CONFIG");
|
|
1078
|
-
if (new Set(surfaces).size !== surfaces.length) fail(`${label} must contain unique surfaces.`, "INVALID_CONFIG");
|
|
1079
|
-
return surfaces;
|
|
1080
|
-
};
|
|
1081
|
-
var benchmarkPolicy = (value) => {
|
|
1082
|
-
if (value === void 0) return void 0;
|
|
1083
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return fail("benchmark.policy must be an object.", "INVALID_CONFIG");
|
|
1084
|
-
const raw = value;
|
|
1085
|
-
const minComparableTasks = nonNegativeInteger(raw["minComparableTasks"], "benchmark.policy.minComparableTasks");
|
|
1086
|
-
const maxDurationRegressionRate = nonNegativeNumber(raw["maxDurationRegressionRate"], "benchmark.policy.maxDurationRegressionRate");
|
|
1087
|
-
const minCompletedRunsPerTask = nonNegativeInteger(raw["minCompletedRunsPerTask"], "benchmark.policy.minCompletedRunsPerTask");
|
|
1088
|
-
const minBaselineSamplesPerTask = nonNegativeInteger(raw["minBaselineSamplesPerTask"] ?? 1, "benchmark.policy.minBaselineSamplesPerTask");
|
|
1089
|
-
if (minComparableTasks === void 0 || minComparableTasks < 1) return fail("benchmark.policy.minComparableTasks must be at least 1.", "INVALID_CONFIG");
|
|
1090
|
-
if (maxDurationRegressionRate === void 0 || maxDurationRegressionRate > 1) return fail("benchmark.policy.maxDurationRegressionRate must be between 0 and 1.", "INVALID_CONFIG");
|
|
1091
|
-
if (minCompletedRunsPerTask === void 0 || minCompletedRunsPerTask < 1) return fail("benchmark.policy.minCompletedRunsPerTask must be at least 1.", "INVALID_CONFIG");
|
|
1092
|
-
if (minBaselineSamplesPerTask === void 0 || minBaselineSamplesPerTask < 1) return fail("benchmark.policy.minBaselineSamplesPerTask must be at least 1.", "INVALID_CONFIG");
|
|
1093
|
-
if (typeof raw["requireZeroEscapedIncomplete"] !== "boolean") return fail("benchmark.policy.requireZeroEscapedIncomplete must be boolean.", "INVALID_CONFIG");
|
|
1094
|
-
return { minComparableTasks, maxDurationRegressionRate, minCompletedRunsPerTask, minBaselineSamplesPerTask, requireZeroEscapedIncomplete: raw["requireZeroEscapedIncomplete"] };
|
|
1095
|
-
};
|
|
1096
2757
|
var validateBenchmarkManifest = (value) => {
|
|
1097
2758
|
if (typeof value !== "object" || value === null || Array.isArray(value)) fail("benchmark manifest must be an object.", "INVALID_CONFIG");
|
|
1098
2759
|
const raw = value;
|
|
@@ -1101,12 +2762,7 @@ var validateBenchmarkManifest = (value) => {
|
|
|
1101
2762
|
const tasks = rawTasks.map((item, index2) => {
|
|
1102
2763
|
if (typeof item !== "object" || item === null || Array.isArray(item)) fail(`benchmark.tasks[${index2}] must be an object.`, "INVALID_CONFIG");
|
|
1103
2764
|
const task = item;
|
|
1104
|
-
|
|
1105
|
-
const prompt = taskFile(task["prompt"], `benchmark.tasks[${index2}].prompt`);
|
|
1106
|
-
const source = taskSource(task["source"], `benchmark.tasks[${index2}].source`);
|
|
1107
|
-
const scope = taskScope(task["scope"], `benchmark.tasks[${index2}].scope`);
|
|
1108
|
-
const surfaces = taskSurfaces(task["surfaces"], `benchmark.tasks[${index2}].surfaces`);
|
|
1109
|
-
return { id: nonEmptyString(task["id"], `benchmark.tasks[${index2}].id`), title: nonEmptyString(task["title"], `benchmark.tasks[${index2}].title`), acceptanceCriteria: stringList(task["acceptanceCriteria"], `benchmark.tasks[${index2}].acceptanceCriteria`), ...surfaces === void 0 ? {} : { surfaces }, ...kind === void 0 ? {} : { kind }, ...prompt === void 0 ? {} : { prompt }, ...source === void 0 ? {} : { source }, ...scope === void 0 ? {} : { scope } };
|
|
2765
|
+
return { id: nonEmptyString(task["id"], `benchmark.tasks[${index2}].id`), title: nonEmptyString(task["title"], `benchmark.tasks[${index2}].title`), acceptanceCriteria: stringList2(task["acceptanceCriteria"], `benchmark.tasks[${index2}].acceptanceCriteria`) };
|
|
1110
2766
|
});
|
|
1111
2767
|
if (new Set(tasks.map((task) => task.id)).size !== tasks.length) fail("benchmark task ids must be unique.", "INVALID_CONFIG");
|
|
1112
2768
|
const taskIds = new Set(tasks.map((task) => task.id));
|
|
@@ -1119,14 +2775,10 @@ var validateBenchmarkManifest = (value) => {
|
|
|
1119
2775
|
const taskId = nonEmptyString(observation["taskId"], `benchmark.observations[${index2}].taskId`);
|
|
1120
2776
|
if (!taskIds.has(taskId)) fail(`benchmark observation references unknown task: ${taskId}.`, "INVALID_CONFIG");
|
|
1121
2777
|
const task = tasks.find((candidate) => candidate.id === taskId) ?? fail(`benchmark observation references unknown task: ${taskId}.`, "INVALID_CONFIG");
|
|
1122
|
-
const attempts =
|
|
2778
|
+
const attempts = nonNegativeInteger3(observation["attempts"], `benchmark.observations[${index2}].attempts`);
|
|
1123
2779
|
const durationMs = nonNegativeNumber(observation["durationMs"], `benchmark.observations[${index2}].durationMs`);
|
|
1124
|
-
const durationSamplesMs = observation["durationSamplesMs"] === void 0 ? void 0 : Array.isArray(observation["durationSamplesMs"]) ? observation["durationSamplesMs"].map((sample, sampleIndex) => nonNegativeNumber(sample, `benchmark.observations[${index2}].durationSamplesMs[${sampleIndex}]`) ?? fail(`benchmark.observations[${index2}].durationSamplesMs must contain numbers.`, "INVALID_CONFIG")) : fail(`benchmark.observations[${index2}].durationSamplesMs must be an array.`, "INVALID_CONFIG");
|
|
1125
|
-
if (durationSamplesMs && !durationSamplesMs.length) fail(`benchmark.observations[${index2}].durationSamplesMs must not be empty.`, "INVALID_CONFIG");
|
|
1126
|
-
const artifactAcceptanceRate2 = rate(observation["artifactAcceptanceRate"], `benchmark.observations[${index2}].artifactAcceptanceRate`);
|
|
1127
|
-
const protocolCompletionRate = rate(observation["protocolCompletionRate"], `benchmark.observations[${index2}].protocolCompletionRate`);
|
|
1128
2780
|
const reviewMinutes2 = nonNegativeNumber(observation["reviewMinutes"], `benchmark.observations[${index2}].reviewMinutes`);
|
|
1129
|
-
const escapedIncomplete =
|
|
2781
|
+
const escapedIncomplete = nonNegativeInteger3(observation["escapedIncomplete"], `benchmark.observations[${index2}].escapedIncomplete`);
|
|
1130
2782
|
const evidenceDigest = sha2562(observation["evidenceDigest"], `benchmark.observations[${index2}].evidenceDigest`);
|
|
1131
2783
|
const rawEvidence = observation["evidence"] === void 0 ? void 0 : Array.isArray(observation["evidence"]) ? observation["evidence"] : fail(`benchmark.observations[${index2}].evidence must be an array.`, "INVALID_CONFIG");
|
|
1132
2784
|
const evidence = rawEvidence?.map((item2, evidenceIndex) => {
|
|
@@ -1139,12 +2791,10 @@ var validateBenchmarkManifest = (value) => {
|
|
|
1139
2791
|
return { criterion, status: evidenceStatus, source: nonEmptyString(entry["source"], `benchmark.observations[${index2}].evidence[${evidenceIndex}].source`) };
|
|
1140
2792
|
});
|
|
1141
2793
|
if (evidence && new Set(evidence.map((entry) => entry.criterion)).size !== evidence.length) fail(`benchmark.observations[${index2}].evidence criteria must be unique.`, "INVALID_CONFIG");
|
|
1142
|
-
return { taskId, mode: "baseline", status, source: nonEmptyString(observation["source"], `benchmark.observations[${index2}].source`), recordedAt: timestamp(observation["recordedAt"], `benchmark.observations[${index2}].recordedAt`), ...attempts === void 0 ? {} : { attempts }, ...durationMs === void 0 ? {} : { durationMs }, ...
|
|
2794
|
+
return { taskId, mode: "baseline", status, source: nonEmptyString(observation["source"], `benchmark.observations[${index2}].source`), recordedAt: timestamp(observation["recordedAt"], `benchmark.observations[${index2}].recordedAt`), ...attempts === void 0 ? {} : { attempts }, ...durationMs === void 0 ? {} : { durationMs }, ...reviewMinutes2 === void 0 ? {} : { reviewMinutes: reviewMinutes2 }, ...escapedIncomplete === void 0 ? {} : { escapedIncomplete }, ...evidence === void 0 ? {} : { evidence }, ...evidenceDigest === void 0 ? {} : { evidenceDigest } };
|
|
1143
2795
|
});
|
|
1144
2796
|
if (new Set(observations.map((observation) => observation.taskId)).size !== observations.length) fail("benchmark allows at most one baseline observation per task.", "INVALID_CONFIG");
|
|
1145
|
-
|
|
1146
|
-
const policy = benchmarkPolicy(raw["policy"]);
|
|
1147
|
-
return { type: "agentskit-harness-benchmark-manifest", schemaVersion: BENCHMARK_SCHEMA_VERSION, suiteId: nonEmptyString(raw["suiteId"], "benchmark.suiteId"), name: nonEmptyString(raw["name"], "benchmark.name"), ...provenance === void 0 ? {} : { provenance }, tasks, observations, ...policy === void 0 ? {} : { policy } };
|
|
2797
|
+
return { type: "agentskit-harness-benchmark-manifest", schemaVersion: BENCHMARK_SCHEMA_VERSION, suiteId: nonEmptyString(raw["suiteId"], "benchmark.suiteId"), name: nonEmptyString(raw["name"], "benchmark.name"), tasks, observations };
|
|
1148
2798
|
};
|
|
1149
2799
|
var loadBenchmarkManifest = (path) => {
|
|
1150
2800
|
try {
|
|
@@ -1170,9 +2820,6 @@ var recordBenchmarkObservation = (path, input) => {
|
|
|
1170
2820
|
recordedAt: input.recordedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1171
2821
|
...input.attempts === void 0 ? {} : { attempts: input.attempts },
|
|
1172
2822
|
...input.durationMs === void 0 ? {} : { durationMs: input.durationMs },
|
|
1173
|
-
...input.durationSamplesMs === void 0 ? {} : { durationSamplesMs: input.durationSamplesMs },
|
|
1174
|
-
...input.artifactAcceptanceRate === void 0 ? {} : { artifactAcceptanceRate: input.artifactAcceptanceRate },
|
|
1175
|
-
...input.protocolCompletionRate === void 0 ? {} : { protocolCompletionRate: input.protocolCompletionRate },
|
|
1176
2823
|
...input.reviewMinutes === void 0 ? {} : { reviewMinutes: input.reviewMinutes },
|
|
1177
2824
|
...input.escapedIncomplete === void 0 ? {} : { escapedIncomplete: input.escapedIncomplete },
|
|
1178
2825
|
...input.evidence === void 0 ? {} : { evidence: input.evidence },
|
|
@@ -1191,111 +2838,39 @@ var recordBenchmarkObservation = (path, input) => {
|
|
|
1191
2838
|
}
|
|
1192
2839
|
return observation;
|
|
1193
2840
|
};
|
|
1194
|
-
var comparisons = (runs, manifest
|
|
2841
|
+
var comparisons = (runs, manifest) => manifest.tasks.map((task) => {
|
|
1195
2842
|
const taskRuns = runs.filter((run) => run.benchmark?.suiteId === manifest.suiteId && run.benchmark.taskId === task.id);
|
|
1196
2843
|
const latest = taskRuns.at(-1);
|
|
1197
2844
|
const baseline = manifest.observations.find((observation) => observation.taskId === task.id);
|
|
1198
2845
|
const coveredCriteria = new Set((baseline?.evidence ?? []).map((entry) => entry.criterion));
|
|
1199
2846
|
const baselineEvidenceCoverageRate = baseline ? percentage(coveredCriteria.size, task.acceptanceCriteria.length) : null;
|
|
1200
2847
|
const baselineEvidenceComplete = baselineEvidenceCoverageRate === 1;
|
|
1201
|
-
const
|
|
1202
|
-
const
|
|
1203
|
-
const
|
|
1204
|
-
const
|
|
1205
|
-
const
|
|
1206
|
-
const
|
|
1207
|
-
|
|
1208
|
-
const completedTaskRuns = taskRuns.filter((run) => run.state === "COMPLETE");
|
|
1209
|
-
const durationSamplesMs = completedTaskRuns.flatMap((run) => run.durationMs === void 0 ? [] : [run.durationMs]);
|
|
1210
|
-
const medianDurationMs = median(durationSamplesMs);
|
|
1211
|
-
const retryCount = count(taskRuns, (run) => run.supersedes !== void 0);
|
|
1212
|
-
const attempts = retryCount + (taskRuns.length ? 1 : 0);
|
|
1213
|
-
const durationRate = comparable ? improvementRate(baselineMedianDurationMs ?? void 0, medianDurationMs ?? void 0) : null;
|
|
1214
|
-
const attemptsRate = comparable ? improvementRate(baseline?.attempts, attempts) : null;
|
|
1215
|
-
const reviewRate = comparable ? improvementRate(baseline?.reviewMinutes, latest?.humanReviewMinutes) : null;
|
|
1216
|
-
const acceptanceSamples = taskRuns.flatMap((run) => run.artifactAcceptanceRate === void 0 ? [] : [run.artifactAcceptanceRate]);
|
|
1217
|
-
const harnessArtifactAcceptanceRate = acceptanceSamples.length ? Number((acceptanceSamples.reduce((total, rate2) => total + rate2, 0) / acceptanceSamples.length).toFixed(4)) : null;
|
|
1218
|
-
const artifactAcceptanceImprovementRate = increaseRate(baseline?.artifactAcceptanceRate, harnessArtifactAcceptanceRate ?? void 0);
|
|
1219
|
-
const artifactAcceptanceDelta = increaseDelta(baseline?.artifactAcceptanceRate, harnessArtifactAcceptanceRate);
|
|
1220
|
-
const completedRuns = completedTaskRuns.length;
|
|
1221
|
-
const harnessProtocolCompletionRate = taskRuns.length ? percentage(completedRuns, taskRuns.length) : null;
|
|
1222
|
-
const protocolCompletionImprovementRate = increaseRate(baseline?.protocolCompletionRate, harnessProtocolCompletionRate ?? void 0);
|
|
1223
|
-
const protocolCompletionDelta = increaseDelta(baseline?.protocolCompletionRate, harnessProtocolCompletionRate);
|
|
1224
|
-
const escapedIncompleteRate = improvementRate(baseline?.escapedIncomplete, latest?.escapedIncomplete);
|
|
1225
|
-
return { taskId: task.id, title: task.title, comparability, comparable, baselineDeliveryComplete, baselineEvidenceCoverageRate, baselineSampleCount: baselineDurationSamples.length, baselineArtifactAcceptanceRate: baseline?.artifactAcceptanceRate ?? null, baselineProtocolCompletionRate: baseline?.protocolCompletionRate ?? null, ...baselineMedianDurationMs === null ? {} : { baselineMedianDurationMs }, improvement: { durationRate, duration: improvementDirection(durationRate), attemptsRate, attempts: improvementDirection(attemptsRate), reviewRate, review: improvementDirection(reviewRate), artifactAcceptanceRate: artifactAcceptanceImprovementRate, artifactAcceptance: increaseDirection(artifactAcceptanceImprovementRate, artifactAcceptanceDelta), artifactAcceptanceDelta, protocolCompletionRate: protocolCompletionImprovementRate, protocolCompletion: increaseDirection(protocolCompletionImprovementRate, protocolCompletionDelta), protocolCompletionDelta, escapedIncompleteRate, escapedIncomplete: improvementDirection(escapedIncompleteRate) }, ...baseline ? { baseline } : {}, harness: { attempts, retryCount, completedRuns, durationSamplesMs, ...medianDurationMs === null ? {} : { medianDurationMs }, ...harnessArtifactAcceptanceRate === null ? {} : { artifactAcceptanceRate: harnessArtifactAcceptanceRate }, artifactAcceptanceSampleCount: acceptanceSamples.length, protocolCompletionRate: harnessProtocolCompletionRate, protocolCompletionSampleCount: taskRuns.length, latestState: latest?.state ?? "NOT_RUN", ...latest ? { latestRunId: latest.runId } : {}, ...latest?.durationMs === void 0 ? {} : { latestDurationMs: latest.durationMs }, checkPassRate: latest ? percentage(latest.checks.passed, latest.checks.total) : null, outcomePassRate: latest ? percentage(latest.outcomes.passed, latest.outcomes.total) : null, evidenceCoverageRate: latest ? percentage(latest.evidence.attached, latest.evidence.total) : null, ...latest?.escapedIncomplete === void 0 ? {} : { escapedIncomplete: latest.escapedIncomplete }, ...latest?.humanReviewMinutes === void 0 ? {} : { humanReviewMinutes: latest.humanReviewMinutes }, humanApproved: latest?.humanApproved ?? false }, confidence: confidence(comparable, completedRuns, policy), ...comparable && baselineMedianDurationMs !== null && medianDurationMs !== null ? { durationDeltaMs: medianDurationMs - baselineMedianDurationMs } : {}, ...comparable && baseline?.attempts !== void 0 ? { attemptDelta: attempts - baseline.attempts } : {}, ...comparable && baseline?.reviewMinutes !== void 0 && latest?.humanReviewMinutes !== void 0 ? { reviewDeltaMinutes: latest.humanReviewMinutes - baseline.reviewMinutes } : {}, ...baseline?.escapedIncomplete !== void 0 && latest?.escapedIncomplete !== void 0 ? { escapedIncompleteDelta: latest.escapedIncomplete - baseline.escapedIncomplete } : {} };
|
|
2848
|
+
const comparable2 = baseline !== void 0 && baseline.status !== "not-run" && baselineEvidenceComplete && latest?.state === "COMPLETE";
|
|
2849
|
+
const comparability = comparable2 ? "comparable" : baseline === void 0 ? "missing-baseline" : baseline.status === "not-run" ? "baseline-not-run" : !baselineEvidenceComplete ? "baseline-evidence-missing" : latest === void 0 ? "harness-not-run" : "harness-not-complete";
|
|
2850
|
+
const durationRate = comparable2 ? improvementRate(baseline?.durationMs, latest?.durationMs) : null;
|
|
2851
|
+
const attemptsRate = comparable2 ? improvementRate(baseline?.attempts, taskRuns.length) : null;
|
|
2852
|
+
const reviewRate = comparable2 ? improvementRate(baseline?.reviewMinutes, latest?.humanReviewMinutes) : null;
|
|
2853
|
+
const escapedIncompleteRate = comparable2 ? improvementRate(baseline?.escapedIncomplete, latest?.escapedIncomplete) : null;
|
|
2854
|
+
return { taskId: task.id, title: task.title, comparability, comparable: comparable2, baselineEvidenceCoverageRate, improvement: { durationRate, duration: improvementDirection(durationRate), attemptsRate, attempts: improvementDirection(attemptsRate), reviewRate, review: improvementDirection(reviewRate), escapedIncompleteRate, escapedIncomplete: improvementDirection(escapedIncompleteRate) }, ...baseline ? { baseline } : {}, harness: { attempts: taskRuns.length, latestState: latest?.state ?? "NOT_RUN", ...latest ? { latestRunId: latest.runId } : {}, ...latest?.durationMs === void 0 ? {} : { latestDurationMs: latest.durationMs }, checkPassRate: latest ? percentage(latest.checks.passed, latest.checks.total) : null, outcomePassRate: latest ? percentage(latest.outcomes.passed, latest.outcomes.total) : null, evidenceCoverageRate: latest ? percentage(latest.evidence.attached, latest.evidence.total) : null, ...latest?.escapedIncomplete === void 0 ? {} : { escapedIncomplete: latest.escapedIncomplete }, ...latest?.humanReviewMinutes === void 0 ? {} : { humanReviewMinutes: latest.humanReviewMinutes }, humanApproved: latest?.humanApproved ?? false }, ...comparable2 && baseline?.durationMs !== void 0 && latest?.durationMs !== void 0 ? { durationDeltaMs: latest.durationMs - baseline.durationMs } : {}, ...comparable2 && baseline?.attempts !== void 0 ? { attemptDelta: taskRuns.length - baseline.attempts } : {}, ...comparable2 && baseline?.reviewMinutes !== void 0 && latest?.humanReviewMinutes !== void 0 ? { reviewDeltaMinutes: latest.humanReviewMinutes - baseline.reviewMinutes } : {}, ...comparable2 && baseline?.escapedIncomplete !== void 0 && latest?.escapedIncomplete !== void 0 ? { escapedIncompleteDelta: latest.escapedIncomplete - baseline.escapedIncomplete } : {} };
|
|
1226
2855
|
});
|
|
1227
2856
|
var benchmarkRuns = (stateDir, manifest) => {
|
|
1228
2857
|
const runs = readRuns(stateDir).map(projectRun).sort((left, right) => left.runId.localeCompare(right.runId));
|
|
1229
|
-
const
|
|
1230
|
-
|
|
1231
|
-
const comparable = reportComparisons.filter((comparison) => comparison.comparable);
|
|
1232
|
-
const durationRegressionTaskIds = comparable.filter((comparison) => (comparison.improvement.durationRate ?? 0) < -policy.maxDurationRegressionRate).map((comparison) => comparison.taskId);
|
|
1233
|
-
const escapedIncompleteTaskIds = comparable.filter((comparison) => comparison.harness.escapedIncomplete !== 0).map((comparison) => comparison.taskId);
|
|
1234
|
-
const reasons = [];
|
|
1235
|
-
if (comparable.length < policy.minComparableTasks) reasons.push(`requires at least ${policy.minComparableTasks} comparable tasks`);
|
|
1236
|
-
const incompleteBaselineTaskIds = reportComparisons.filter((comparison) => comparison.comparability === "baseline-incomplete").map((comparison) => comparison.taskId);
|
|
1237
|
-
if (incompleteBaselineTaskIds.length) reasons.push(`baseline delivery incomplete: ${incompleteBaselineTaskIds.join(", ")}`);
|
|
1238
|
-
const baselineSampleGaps = reportComparisons.filter((comparison) => comparison.baselineSampleCount < policy.minBaselineSamplesPerTask).map((comparison) => `${comparison.taskId} (${comparison.baselineSampleCount}/${policy.minBaselineSamplesPerTask})`);
|
|
1239
|
-
if (baselineSampleGaps.length) reasons.push(`requires ${policy.minBaselineSamplesPerTask} baseline samples per task: ${baselineSampleGaps.join(", ")}`);
|
|
1240
|
-
if (durationRegressionTaskIds.length) reasons.push(`duration regression exceeds ${policy.maxDurationRegressionRate * 100}%: ${durationRegressionTaskIds.join(", ")}`);
|
|
1241
|
-
if (policy.requireZeroEscapedIncomplete && escapedIncompleteTaskIds.length) reasons.push(`escaped incomplete delivery: ${escapedIncompleteTaskIds.join(", ")}`);
|
|
1242
|
-
const confidenceLevel = comparable.length < policy.minComparableTasks ? "insufficient" : comparable.every((comparison) => comparison.confidence === "reliable") ? "reliable" : "directional";
|
|
1243
|
-
const qualityGate = { status: comparable.length < policy.minComparableTasks ? "insufficient-data" : reasons.length ? "failed" : "passed", confidence: confidenceLevel, comparableTaskCount: comparable.length, policy, durationRegressionTaskIds, escapedIncompleteTaskIds, reasons };
|
|
1244
|
-
return { type: "agentskit-harness-benchmark", schemaVersion: BENCHMARK_SCHEMA_VERSION, stateDir, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), runs, summary: summarize(runs), comparisons: reportComparisons, qualityGate, ...manifest ? { manifest: { suiteId: manifest.suiteId, taskCount: manifest.tasks.length, baselineCount: manifest.observations.length, comparableTaskCount: comparable.length } } : {} };
|
|
1245
|
-
};
|
|
1246
|
-
|
|
1247
|
-
// src/coding-benchmark.ts
|
|
1248
|
-
var text2 = (value, label) => {
|
|
1249
|
-
if (typeof value !== "string" || !value.trim()) fail(`${label} must be a non-empty string.`, "INVALID_INPUT");
|
|
1250
|
-
return value.trim();
|
|
1251
|
-
};
|
|
1252
|
-
var numberValue = (value, label, integer = false) => {
|
|
1253
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || integer && !Number.isInteger(value)) fail(`${label} must be a non-negative ${integer ? "integer" : "number"}.`, "INVALID_INPUT");
|
|
1254
|
-
return value;
|
|
2858
|
+
const reportComparisons = manifest ? comparisons(runs, manifest) : [];
|
|
2859
|
+
return { type: "agentskit-harness-benchmark", schemaVersion: BENCHMARK_SCHEMA_VERSION, stateDir, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), runs, summary: summarize(runs), comparisons: reportComparisons, ...manifest ? { manifest: { suiteId: manifest.suiteId, taskCount: manifest.tasks.length, baselineCount: manifest.observations.length, comparableTaskCount: reportComparisons.filter((comparison) => comparison.comparable).length } } : {} };
|
|
1255
2860
|
};
|
|
1256
|
-
var
|
|
1257
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) fail("coding benchmark report must be an object.", "INVALID_INPUT");
|
|
1258
|
-
const raw = value;
|
|
1259
|
-
if (!["edit", "fix-bug", "add-feature", "refactor", "add-test", "review-pr", "free-form"].includes(text2(raw["kind"], "report.kind"))) fail("report.kind is not a supported coding task kind.", "INVALID_INPUT");
|
|
1260
|
-
if (typeof raw["dryRun"] !== "boolean" || typeof raw["isolateWorktrees"] !== "boolean") fail("report.dryRun and report.isolateWorktrees must be booleans.", "INVALID_INPUT");
|
|
1261
|
-
if (!Array.isArray(raw["rows"]) || raw["rows"].length === 0) fail("report.rows must contain at least one provider result.", "INVALID_INPUT");
|
|
1262
|
-
const rows = raw["rows"].map((item, index2) => {
|
|
1263
|
-
if (typeof item !== "object" || item === null || Array.isArray(item)) fail(`report.rows[${index2}] must be an object.`, "INVALID_INPUT");
|
|
1264
|
-
const row = item;
|
|
1265
|
-
const status = text2(row["status"], `report.rows[${index2}].status`);
|
|
1266
|
-
if (!["ok", "partial", "fail", "timeout"].includes(status)) fail(`report.rows[${index2}].status is invalid.`, "INVALID_INPUT");
|
|
1267
|
-
const completenessScore = numberValue(row["completenessScore"], `report.rows[${index2}].completenessScore`);
|
|
1268
|
-
if (completenessScore > 100) fail(`report.rows[${index2}].completenessScore must be between 0 and 100.`, "INVALID_INPUT");
|
|
1269
|
-
const optional = (key) => row[key] === void 0 ? void 0 : numberValue(row[key], `report.rows[${index2}].${key}`);
|
|
1270
|
-
return {
|
|
1271
|
-
providerId: text2(row["providerId"], `report.rows[${index2}].providerId`),
|
|
1272
|
-
status,
|
|
1273
|
-
completenessScore,
|
|
1274
|
-
fileEditCount: numberValue(row["fileEditCount"], `report.rows[${index2}].fileEditCount`, true),
|
|
1275
|
-
summary: text2(row["summary"], `report.rows[${index2}].summary`),
|
|
1276
|
-
...optional("durationMs") === void 0 ? {} : { durationMs: optional("durationMs") },
|
|
1277
|
-
...optional("inputTokens") === void 0 ? {} : { inputTokens: optional("inputTokens") },
|
|
1278
|
-
...optional("outputTokens") === void 0 ? {} : { outputTokens: optional("outputTokens") },
|
|
1279
|
-
...optional("costUsd") === void 0 ? {} : { costUsd: optional("costUsd") },
|
|
1280
|
-
...row["successPassed"] === void 0 ? {} : typeof row["successPassed"] !== "boolean" ? fail(`report.rows[${index2}].successPassed must be a boolean.`, "INVALID_INPUT") : { successPassed: row["successPassed"] }
|
|
1281
|
-
};
|
|
1282
|
-
});
|
|
1283
|
-
if (new Set(rows.map((row) => row.providerId)).size !== rows.length) fail("coding benchmark provider ids must be unique.", "INVALID_INPUT");
|
|
1284
|
-
return { kind: text2(raw["kind"], "report.kind"), prompt: text2(raw["prompt"], "report.prompt"), dryRun: raw["dryRun"], isolateWorktrees: raw["isolateWorktrees"], repoRoot: text2(raw["repoRoot"], "report.repoRoot"), rows };
|
|
1285
|
-
};
|
|
1286
|
-
var required = (value, label) => {
|
|
2861
|
+
var required8 = (value, label) => {
|
|
1287
2862
|
if (typeof value !== "string" || !value.trim()) fail(`${label} is required.`, "INVALID_INPUT");
|
|
1288
2863
|
return value.trim();
|
|
1289
2864
|
};
|
|
1290
|
-
var
|
|
2865
|
+
var duration3 = (value) => {
|
|
1291
2866
|
if (!Number.isFinite(value) || value < 0) fail("Tool durationMs must be a non-negative number.", "INVALID_INPUT");
|
|
1292
2867
|
return value;
|
|
1293
2868
|
};
|
|
1294
2869
|
var createSessionRecorder = ({ stateDir, run, adapter, policy, runtime, sessionId = randomUUID(), resume = false }) => {
|
|
1295
2870
|
if (run.state !== "IMPLEMENTING") fail(`Agent sessions can only start during IMPLEMENTING, not ${run.state}.`, "INVALID_STATE");
|
|
1296
|
-
const id2 =
|
|
1297
|
-
const adapterId =
|
|
1298
|
-
const adapterVersion =
|
|
2871
|
+
const id2 = required8(sessionId, "sessionId");
|
|
2872
|
+
const adapterId = required8(adapter.id, "adapter.id");
|
|
2873
|
+
const adapterVersion = required8(adapter.version, "adapter.version");
|
|
1299
2874
|
if (!policy || typeof policy.evaluate !== "function") fail("policy.evaluate is required.", "INVALID_INPUT");
|
|
1300
2875
|
if (!runtime || typeof runtime.execute !== "function") fail("runtime.execute is required.", "INVALID_INPUT");
|
|
1301
2876
|
if (!Array.isArray(adapter.capabilities) || adapter.capabilities.some((capability) => typeof capability !== "string" || !capability.trim())) fail("adapter.capabilities must contain non-empty strings.", "INVALID_INPUT");
|
|
@@ -1346,18 +2921,18 @@ var createSessionRecorder = ({ stateDir, run, adapter, policy, runtime, sessionI
|
|
|
1346
2921
|
};
|
|
1347
2922
|
const complete = (input) => {
|
|
1348
2923
|
open();
|
|
1349
|
-
const actionId =
|
|
2924
|
+
const actionId = required8(input.actionId, "actionId");
|
|
1350
2925
|
if (!pending.has(actionId)) fail(`Tool action is not pending: ${actionId}.`, "INVALID_STATE");
|
|
1351
|
-
const event = append("tool.completed", { actionId, resultHash:
|
|
2926
|
+
const event = append("tool.completed", { actionId, resultHash: required8(input.resultHash, "resultHash"), durationMs: duration3(input.durationMs), ...input.runtimeEvidence ? { runtimeEvidence: input.runtimeEvidence } : {} });
|
|
1352
2927
|
pending.delete(actionId);
|
|
1353
2928
|
return event;
|
|
1354
2929
|
};
|
|
1355
2930
|
const failAction = (input) => {
|
|
1356
2931
|
open();
|
|
1357
|
-
const actionId =
|
|
2932
|
+
const actionId = required8(input.actionId, "actionId");
|
|
1358
2933
|
if (!pending.has(actionId)) fail(`Tool action is not pending: ${actionId}.`, "INVALID_STATE");
|
|
1359
2934
|
if (typeof input.retryable !== "boolean") fail("retryable must be boolean.", "INVALID_INPUT");
|
|
1360
|
-
const event = append("tool.failed", { actionId, errorCode:
|
|
2935
|
+
const event = append("tool.failed", { actionId, errorCode: required8(input.errorCode, "errorCode"), retryable: input.retryable, durationMs: duration3(input.durationMs), ...input.runtimeEvidence ? { runtimeEvidence: input.runtimeEvidence } : {} });
|
|
1361
2936
|
pending.delete(actionId);
|
|
1362
2937
|
return event;
|
|
1363
2938
|
};
|
|
@@ -1365,24 +2940,24 @@ var createSessionRecorder = ({ stateDir, run, adapter, policy, runtime, sessionI
|
|
|
1365
2940
|
sessionId: id2,
|
|
1366
2941
|
startTurn: (inputHash, turnId = randomUUID()) => {
|
|
1367
2942
|
open();
|
|
1368
|
-
const turn =
|
|
2943
|
+
const turn = required8(turnId, "turnId");
|
|
1369
2944
|
if (turns.has(turn)) fail(`Turn already exists: ${turn}.`, "INVALID_STATE");
|
|
1370
|
-
const event = append("agent.turn.started", { turnId: turn, inputHash:
|
|
2945
|
+
const event = append("agent.turn.started", { turnId: turn, inputHash: required8(inputHash, "inputHash") });
|
|
1371
2946
|
turns.add(turn);
|
|
1372
2947
|
return event;
|
|
1373
2948
|
},
|
|
1374
2949
|
requestTool: (input) => {
|
|
1375
2950
|
open();
|
|
1376
|
-
const turnId =
|
|
2951
|
+
const turnId = required8(input.turnId, "turnId");
|
|
1377
2952
|
if (!turns.has(turnId)) fail(`Turn does not exist: ${turnId}.`, "INVALID_STATE");
|
|
1378
|
-
const actionId =
|
|
2953
|
+
const actionId = required8(input.actionId ?? randomUUID(), "actionId");
|
|
1379
2954
|
if (actions.has(actionId)) fail(`Tool action already exists: ${actionId}.`, "INVALID_STATE");
|
|
1380
|
-
const toolId =
|
|
1381
|
-
const argumentsHash =
|
|
2955
|
+
const toolId = required8(input.toolId, "toolId");
|
|
2956
|
+
const argumentsHash = required8(input.argumentsHash, "argumentsHash");
|
|
1382
2957
|
const decision = policy.evaluate({ actionId, turnId, toolId, argumentsHash });
|
|
1383
2958
|
if (!decision || decision.decision !== "allow" && decision.decision !== "block" && decision.decision !== "approve") fail("Policy decision is invalid.", "HARNESS_ERROR");
|
|
1384
|
-
const policyId =
|
|
1385
|
-
const reason =
|
|
2959
|
+
const policyId = required8(decision.policyId, "policyId");
|
|
2960
|
+
const reason = required8(decision.reason, "policy reason");
|
|
1386
2961
|
append("policy.evaluated", { actionId, turnId, toolId, decision: decision.decision, policyId, reason });
|
|
1387
2962
|
actions.add(actionId);
|
|
1388
2963
|
if (decision.decision === "block") {
|
|
@@ -1400,7 +2975,7 @@ var createSessionRecorder = ({ stateDir, run, adapter, policy, runtime, sessionI
|
|
|
1400
2975
|
},
|
|
1401
2976
|
approveTool: (input) => {
|
|
1402
2977
|
open();
|
|
1403
|
-
const actionId =
|
|
2978
|
+
const actionId = required8(input.actionId, "actionId");
|
|
1404
2979
|
const approval = approvals.get(actionId) ?? fail(`Tool action is not awaiting human approval: ${actionId}.`, "INVALID_STATE");
|
|
1405
2980
|
if (input.actor !== void 0 && input.actor !== "human") fail("Tool approval requires a human actor.", "HUMAN_APPROVAL_REQUIRED");
|
|
1406
2981
|
const decision = input.decision;
|
|
@@ -1414,7 +2989,7 @@ var createSessionRecorder = ({ stateDir, run, adapter, policy, runtime, sessionI
|
|
|
1414
2989
|
},
|
|
1415
2990
|
recoverTool: (input) => {
|
|
1416
2991
|
open();
|
|
1417
|
-
const actionId =
|
|
2992
|
+
const actionId = required8(input.actionId, "actionId");
|
|
1418
2993
|
const action = pending.get(actionId) ?? fail(`Tool action is not pending: ${actionId}.`, "INVALID_STATE");
|
|
1419
2994
|
if (!action.executionStarted) fail(`Tool action does not require recovery: ${actionId}.`, "INVALID_STATE");
|
|
1420
2995
|
if (input.actor !== void 0 && input.actor !== "human") fail("Tool recovery requires a human actor.", "HUMAN_APPROVAL_REQUIRED");
|
|
@@ -1432,7 +3007,7 @@ var createSessionRecorder = ({ stateDir, run, adapter, policy, runtime, sessionI
|
|
|
1432
3007
|
failTool: failAction,
|
|
1433
3008
|
executeTool: async (input) => {
|
|
1434
3009
|
open();
|
|
1435
|
-
const actionId =
|
|
3010
|
+
const actionId = required8(input.actionId, "actionId");
|
|
1436
3011
|
const action = pending.get(actionId) ?? fail(`Tool action is not pending: ${actionId}.`, "INVALID_STATE");
|
|
1437
3012
|
if (action.executionStarted) fail(`Tool action requires human recovery decision: ${actionId}.`, "HUMAN_APPROVAL_REQUIRED");
|
|
1438
3013
|
if (executing.has(actionId)) fail(`Tool action is already executing: ${actionId}.`, "INVALID_STATE");
|
|
@@ -1475,8 +3050,8 @@ var createSessionRecorder = ({ stateDir, run, adapter, policy, runtime, sessionI
|
|
|
1475
3050
|
return recorder;
|
|
1476
3051
|
};
|
|
1477
3052
|
|
|
1478
|
-
// src/policy.ts
|
|
1479
|
-
var
|
|
3053
|
+
// src/kernel/policy.ts
|
|
3054
|
+
var required9 = (value, label) => {
|
|
1480
3055
|
if (typeof value !== "string" || !value.trim()) fail(`${label} is required.`, "INVALID_INPUT");
|
|
1481
3056
|
return value.trim();
|
|
1482
3057
|
};
|
|
@@ -1484,29 +3059,30 @@ var createPolicyGate = ({ rules }) => {
|
|
|
1484
3059
|
if (!Array.isArray(rules)) fail("Policy rules must be an array.", "INVALID_INPUT");
|
|
1485
3060
|
const normalized = rules.map((rule, index2) => {
|
|
1486
3061
|
if (typeof rule !== "object" || rule === null || Array.isArray(rule)) fail(`rules[${index2}] must be an object.`, "INVALID_INPUT");
|
|
1487
|
-
const id2 =
|
|
3062
|
+
const id2 = required9(rule.id, `rules[${index2}].id`);
|
|
1488
3063
|
if (rule.effect !== "allow" && rule.effect !== "block" && rule.effect !== "approve") fail(`rules[${index2}].effect is invalid.`, "INVALID_INPUT");
|
|
1489
3064
|
if (!Array.isArray(rule.toolIds) || !rule.toolIds.length || rule.toolIds.some((toolId) => typeof toolId !== "string" || !toolId.trim())) fail(`rules[${index2}].toolIds must contain non-empty strings.`, "INVALID_INPUT");
|
|
1490
|
-
return { id: id2, effect: rule.effect, toolIds: rule.toolIds.map((toolId) =>
|
|
3065
|
+
return { id: id2, effect: rule.effect, toolIds: rule.toolIds.map((toolId) => required9(toolId, `rules[${index2}].toolIds`)), reason: required9(rule.reason, `rules[${index2}].reason`) };
|
|
1491
3066
|
});
|
|
1492
3067
|
if (new Set(normalized.map((rule) => rule.id)).size !== normalized.length) fail("Policy rules must have unique ids.", "INVALID_INPUT");
|
|
1493
3068
|
return {
|
|
1494
3069
|
evaluate: (request) => {
|
|
1495
3070
|
if (typeof request !== "object" || request === null || Array.isArray(request)) fail("Policy request must be an object.", "INVALID_INPUT");
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
const toolId =
|
|
1499
|
-
|
|
3071
|
+
required9(request.actionId, "request.actionId");
|
|
3072
|
+
required9(request.turnId, "request.turnId");
|
|
3073
|
+
const toolId = required9(request.toolId, "request.toolId");
|
|
3074
|
+
required9(request.argumentsHash, "request.argumentsHash");
|
|
1500
3075
|
const rule = normalized.find((candidate) => candidate.toolIds.includes(toolId));
|
|
1501
3076
|
return rule ? { decision: rule.effect, policyId: rule.id, reason: rule.reason } : { decision: "block", policyId: "default-deny", reason: `No policy rule allows tool: ${toolId}.` };
|
|
1502
3077
|
}
|
|
1503
3078
|
};
|
|
1504
3079
|
};
|
|
1505
|
-
var
|
|
3080
|
+
var createConfiguredToolRuntime = ({ runtime, process: process2, docker }) => runtime.kind === "docker" ? createDockerToolRuntime(docker) : createProcessToolRuntime(process2);
|
|
3081
|
+
var required10 = (value, label) => {
|
|
1506
3082
|
if (typeof value !== "string" || !value.trim()) fail(`${label} is required.`, "INVALID_INPUT");
|
|
1507
3083
|
return value.trim();
|
|
1508
3084
|
};
|
|
1509
|
-
var
|
|
3085
|
+
var duration4 = (value) => {
|
|
1510
3086
|
if (!Number.isFinite(value) || value < 0) fail("Tool durationMs must be a non-negative number.", "INVALID_INPUT");
|
|
1511
3087
|
return value;
|
|
1512
3088
|
};
|
|
@@ -1516,7 +3092,7 @@ var positiveNumber = (value, label) => {
|
|
|
1516
3092
|
return normalized;
|
|
1517
3093
|
};
|
|
1518
3094
|
var absolutePath = (value, label) => {
|
|
1519
|
-
const normalized =
|
|
3095
|
+
const normalized = required10(value, label);
|
|
1520
3096
|
if (!normalized.startsWith("/") || normalized.includes(",")) fail(`${label} must be an absolute path without commas.`, "INVALID_INPUT");
|
|
1521
3097
|
return normalized;
|
|
1522
3098
|
};
|
|
@@ -1534,35 +3110,38 @@ var createToolRuntime = ({ tools, timeoutMs = 3e4 }) => {
|
|
|
1534
3110
|
if (!Number.isInteger(timeoutMs) || timeoutMs < 1) fail("Runtime timeoutMs must be a positive integer.", "INVALID_INPUT");
|
|
1535
3111
|
const normalized = tools.map((tool, index2) => {
|
|
1536
3112
|
if (typeof tool !== "object" || tool === null || Array.isArray(tool)) fail(`tools[${index2}] must be an object.`, "INVALID_INPUT");
|
|
1537
|
-
const toolId =
|
|
3113
|
+
const toolId = required10(tool.toolId, `tools[${index2}].toolId`);
|
|
1538
3114
|
if (typeof tool.execute !== "function") fail(`tools[${index2}].execute is required.`, "INVALID_INPUT");
|
|
1539
3115
|
return { toolId, execute: tool.execute };
|
|
1540
3116
|
});
|
|
1541
3117
|
if (new Set(normalized.map((tool) => tool.toolId)).size !== normalized.length) fail("Runtime tools must have unique ids.", "INVALID_INPUT");
|
|
1542
3118
|
return {
|
|
3119
|
+
assurance: "contract-tested",
|
|
3120
|
+
isolation: "none",
|
|
3121
|
+
telemetry: () => ({ status: "unknown" }),
|
|
1543
3122
|
execute: async (request) => {
|
|
1544
3123
|
const started = Date.now();
|
|
1545
|
-
const actionId =
|
|
1546
|
-
const turnId =
|
|
1547
|
-
const toolId =
|
|
1548
|
-
const argumentsHash =
|
|
3124
|
+
const actionId = required10(request.actionId, "request.actionId");
|
|
3125
|
+
const turnId = required10(request.turnId, "request.turnId");
|
|
3126
|
+
const toolId = required10(request.toolId, "request.toolId");
|
|
3127
|
+
const argumentsHash = required10(request.argumentsHash, "request.argumentsHash");
|
|
1549
3128
|
const tool = normalized.find((candidate) => candidate.toolId === toolId);
|
|
1550
|
-
if (!tool) return { status: "failed", errorCode: "TOOL_NOT_FOUND", retryable: false, durationMs:
|
|
3129
|
+
if (!tool) return { status: "failed", errorCode: "TOOL_NOT_FOUND", retryable: false, durationMs: duration4(Date.now() - started) };
|
|
1551
3130
|
const controller = new AbortController();
|
|
1552
3131
|
let timedOut = false;
|
|
1553
3132
|
let timer;
|
|
1554
3133
|
try {
|
|
1555
|
-
const
|
|
3134
|
+
const timeout2 = new Promise((_, reject) => {
|
|
1556
3135
|
timer = setTimeout(() => {
|
|
1557
3136
|
timedOut = true;
|
|
1558
3137
|
controller.abort();
|
|
1559
3138
|
reject(new Error("Tool execution timed out."));
|
|
1560
3139
|
}, timeoutMs);
|
|
1561
3140
|
});
|
|
1562
|
-
const result = await Promise.race([Promise.resolve(tool.execute({ actionId, turnId, toolId, argumentsHash, arguments: request.arguments, signal: controller.signal })),
|
|
1563
|
-
return { status: "completed", resultHash: hashJson(result === void 0 ? null : result), durationMs:
|
|
3141
|
+
const result = await Promise.race([Promise.resolve(tool.execute({ actionId, turnId, toolId, argumentsHash, arguments: request.arguments, signal: controller.signal })), timeout2]);
|
|
3142
|
+
return { status: "completed", resultHash: hashJson(result === void 0 ? null : result), durationMs: duration4(Date.now() - started) };
|
|
1564
3143
|
} catch {
|
|
1565
|
-
return { status: "failed", errorCode: timedOut ? "TIMEOUT" : "RUNTIME_ERROR", retryable: true, durationMs:
|
|
3144
|
+
return { status: "failed", errorCode: timedOut ? "TIMEOUT" : "RUNTIME_ERROR", retryable: true, durationMs: duration4(Date.now() - started) };
|
|
1566
3145
|
} finally {
|
|
1567
3146
|
if (timer) clearTimeout(timer);
|
|
1568
3147
|
}
|
|
@@ -1575,20 +3154,23 @@ var createProcessToolRuntime = ({ tools, timeoutMs = 3e4, maxOutputBytes = 10485
|
|
|
1575
3154
|
if (!Number.isInteger(maxOutputBytes) || maxOutputBytes < 1) fail("Process runtime maxOutputBytes must be a positive integer.", "INVALID_INPUT");
|
|
1576
3155
|
const normalized = tools.map((tool, index2) => {
|
|
1577
3156
|
if (typeof tool !== "object" || tool === null || Array.isArray(tool)) fail(`tools[${index2}] must be an object.`, "INVALID_INPUT");
|
|
1578
|
-
const toolId =
|
|
1579
|
-
const command =
|
|
3157
|
+
const toolId = required10(tool.toolId, `tools[${index2}].toolId`);
|
|
3158
|
+
const command = required10(tool.command, `tools[${index2}].command`);
|
|
1580
3159
|
if (tool.args !== void 0 && (!Array.isArray(tool.args) || tool.args.some((arg) => typeof arg !== "string"))) fail(`tools[${index2}].args must contain strings.`, "INVALID_INPUT");
|
|
1581
3160
|
if (tool.env !== void 0 && (typeof tool.env !== "object" || tool.env === null || Array.isArray(tool.env) || Object.values(tool.env).some((value) => typeof value !== "string"))) fail(`tools[${index2}].env must contain string values.`, "INVALID_INPUT");
|
|
1582
3161
|
return { toolId, command, args: tool.args ? [...tool.args] : [], ...tool.cwd ? { cwd: tool.cwd } : {}, env: tool.env ? { ...tool.env } : { PATH: process.env["PATH"] ?? "" } };
|
|
1583
3162
|
});
|
|
1584
3163
|
if (new Set(normalized.map((tool) => tool.toolId)).size !== normalized.length) fail("Process runtime tools must have unique ids.", "INVALID_INPUT");
|
|
1585
3164
|
return {
|
|
3165
|
+
assurance: "contract-tested",
|
|
3166
|
+
isolation: "none",
|
|
3167
|
+
telemetry: () => ({ status: "unknown" }),
|
|
1586
3168
|
execute: async (request) => {
|
|
1587
3169
|
const started = Date.now();
|
|
1588
|
-
const actionId =
|
|
1589
|
-
const turnId =
|
|
1590
|
-
const toolId =
|
|
1591
|
-
const argumentsHash =
|
|
3170
|
+
const actionId = required10(request.actionId, "request.actionId");
|
|
3171
|
+
const turnId = required10(request.turnId, "request.turnId");
|
|
3172
|
+
const toolId = required10(request.toolId, "request.toolId");
|
|
3173
|
+
const argumentsHash = required10(request.argumentsHash, "request.argumentsHash");
|
|
1592
3174
|
const tool = normalized.find((candidate) => candidate.toolId === toolId);
|
|
1593
3175
|
if (!tool) return { status: "failed", errorCode: "TOOL_NOT_FOUND", retryable: false, durationMs: Date.now() - started };
|
|
1594
3176
|
let input;
|
|
@@ -1598,7 +3180,7 @@ var createProcessToolRuntime = ({ tools, timeoutMs = 3e4, maxOutputBytes = 10485
|
|
|
1598
3180
|
return { status: "failed", errorCode: "SERIALIZATION_ERROR", retryable: false, durationMs: Date.now() - started };
|
|
1599
3181
|
}
|
|
1600
3182
|
return new Promise((resolve6) => {
|
|
1601
|
-
const child = spawn(tool.command, tool.args, { cwd: tool.cwd, env: tool.env, shell: false, stdio: ["pipe", "pipe", "pipe"] });
|
|
3183
|
+
const child = spawn(tool.command, [...tool.args], { cwd: tool.cwd, env: tool.env, shell: false, stdio: ["pipe", "pipe", "pipe"] });
|
|
1602
3184
|
let stdout = "";
|
|
1603
3185
|
let timedOut = false;
|
|
1604
3186
|
let outputLimit = false;
|
|
@@ -1658,17 +3240,17 @@ var createDockerToolRuntime = ({
|
|
|
1658
3240
|
pull = "never"
|
|
1659
3241
|
}) => {
|
|
1660
3242
|
if (!Array.isArray(tools)) fail("Docker runtime tools must be an array.", "INVALID_INPUT");
|
|
1661
|
-
const command =
|
|
1662
|
-
const memory =
|
|
3243
|
+
const command = required10(dockerCommand, "dockerCommand");
|
|
3244
|
+
const memory = required10(memoryLimit, "memoryLimit");
|
|
1663
3245
|
const cpu = positiveNumber(cpus, "cpus");
|
|
1664
3246
|
if (!Number.isInteger(pidsLimit) || pidsLimit < 1) fail("pidsLimit must be a positive integer.", "INVALID_INPUT");
|
|
1665
|
-
const normalizedUser =
|
|
3247
|
+
const normalizedUser = required10(user, "user");
|
|
1666
3248
|
if (normalizedUser.includes(" ")) fail("user must not contain spaces.", "INVALID_INPUT");
|
|
1667
3249
|
if (pull !== "never" && pull !== "missing" && pull !== "always") fail("pull must be never, missing, or always.", "INVALID_INPUT");
|
|
1668
3250
|
const normalized = tools.map((tool, index2) => {
|
|
1669
3251
|
if (typeof tool !== "object" || tool === null || Array.isArray(tool)) fail(`tools[${index2}] must be an object.`, "INVALID_INPUT");
|
|
1670
|
-
const toolId =
|
|
1671
|
-
const image =
|
|
3252
|
+
const toolId = required10(tool.toolId, `tools[${index2}].toolId`);
|
|
3253
|
+
const image = required10(tool.image, `tools[${index2}].image`);
|
|
1672
3254
|
if (!Array.isArray(tool.command) || tool.command.length === 0 || tool.command.some((part) => typeof part !== "string" || !part.trim())) fail(`tools[${index2}].command must be a non-empty string array.`, "INVALID_INPUT");
|
|
1673
3255
|
if (tool.args !== void 0 && (!Array.isArray(tool.args) || tool.args.some((arg) => typeof arg !== "string"))) fail(`tools[${index2}].args must contain strings.`, "INVALID_INPUT");
|
|
1674
3256
|
const env = dockerEnvironment(tool.env, `tools[${index2}].env`);
|
|
@@ -1720,6 +3302,9 @@ var createDockerToolRuntime = ({
|
|
|
1720
3302
|
if (new Set(normalized.map((tool) => tool.toolId)).size !== normalized.length) fail("Docker runtime tools must have unique ids.", "INVALID_INPUT");
|
|
1721
3303
|
const processRuntime = createProcessToolRuntime({ tools: normalized, timeoutMs, maxOutputBytes });
|
|
1722
3304
|
return {
|
|
3305
|
+
assurance: "runtime-attested",
|
|
3306
|
+
isolation: "sandboxed",
|
|
3307
|
+
telemetry: () => ({ status: "unknown" }),
|
|
1723
3308
|
execute: async (request) => {
|
|
1724
3309
|
const tool = normalized.find((candidate) => candidate.toolId === request.toolId);
|
|
1725
3310
|
if (!tool) return processRuntime.execute(request);
|
|
@@ -1730,7 +3315,7 @@ var createDockerToolRuntime = ({
|
|
|
1730
3315
|
imageDigest = inspected.stdout.trim();
|
|
1731
3316
|
if (!/^sha256:[a-f0-9]{64}$/.test(imageDigest)) throw new Error("Docker image inspection did not return a digest.");
|
|
1732
3317
|
} catch {
|
|
1733
|
-
return { status: "failed", errorCode: "IMAGE_UNAVAILABLE", retryable: true, durationMs:
|
|
3318
|
+
return { status: "failed", errorCode: "IMAGE_UNAVAILABLE", retryable: true, durationMs: duration4(Date.now() - started), runtimeEvidence: tool.evidence };
|
|
1734
3319
|
}
|
|
1735
3320
|
const runtimeEvidence = { ...tool.evidence, imageDigest, profileHash: hashJson({ ...tool.evidence, imageDigest }) };
|
|
1736
3321
|
const result = await processRuntime.execute(request);
|
|
@@ -1738,8 +3323,343 @@ var createDockerToolRuntime = ({
|
|
|
1738
3323
|
}
|
|
1739
3324
|
};
|
|
1740
3325
|
};
|
|
3326
|
+
var required11 = (value, label) => {
|
|
3327
|
+
if (typeof value !== "string" || !value.trim()) fail(`${label} is required.`, "INVALID_INPUT");
|
|
3328
|
+
return value.trim();
|
|
3329
|
+
};
|
|
3330
|
+
var safeKey = (identity) => hashJson(identity);
|
|
3331
|
+
var now3 = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
3332
|
+
var parse = (value, label) => {
|
|
3333
|
+
try {
|
|
3334
|
+
const raw = JSON.parse(value);
|
|
3335
|
+
const identity = {
|
|
3336
|
+
tracker: required11(raw["tracker"], `${label}.tracker`),
|
|
3337
|
+
repository: required11(raw["repository"], `${label}.repository`),
|
|
3338
|
+
issue: required11(raw["issue"], `${label}.issue`),
|
|
3339
|
+
worktree: required11(raw["worktree"], `${label}.worktree`),
|
|
3340
|
+
branch: required11(raw["branch"], `${label}.branch`)
|
|
3341
|
+
};
|
|
3342
|
+
return { ...identity, key: required11(raw["key"], `${label}.key`), leaseId: required11(raw["leaseId"], `${label}.leaseId`), owner: required11(raw["owner"], `${label}.owner`), claimedAt: required11(raw["claimedAt"], `${label}.claimedAt`) };
|
|
3343
|
+
} catch (error) {
|
|
3344
|
+
if (error instanceof SyntaxError) fail(`${label} contains invalid JSON.`, "HARNESS_ERROR");
|
|
3345
|
+
throw error;
|
|
3346
|
+
}
|
|
3347
|
+
};
|
|
3348
|
+
var createDispatchLedger = (stateDir) => {
|
|
3349
|
+
const root = required11(stateDir, "stateDir");
|
|
3350
|
+
const claimsDir = join(root, "coordination", "claims");
|
|
3351
|
+
const ledgerPath = join(root, "coordination", "dispatch-ledger.ndjson");
|
|
3352
|
+
mkdirSync(claimsDir, { recursive: true });
|
|
3353
|
+
const claimPath = (key) => join(claimsDir, `${key}.json`);
|
|
3354
|
+
const append = (record3) => appendFileSync(ledgerPath, `${JSON.stringify(record3)}
|
|
3355
|
+
`, "utf8");
|
|
3356
|
+
const records = () => {
|
|
3357
|
+
if (!existsSync(ledgerPath)) return [];
|
|
3358
|
+
return readFileSync(ledgerPath, "utf8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line, index2) => {
|
|
3359
|
+
try {
|
|
3360
|
+
return JSON.parse(line);
|
|
3361
|
+
} catch {
|
|
3362
|
+
return fail(`Dispatch ledger record ${index2 + 1} is invalid JSON.`, "HARNESS_ERROR");
|
|
3363
|
+
}
|
|
3364
|
+
});
|
|
3365
|
+
};
|
|
3366
|
+
const active = () => {
|
|
3367
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
3368
|
+
for (const record3 of records()) {
|
|
3369
|
+
if (record3.action === "release" || record3.action === "recover") byKey.delete(record3.key);
|
|
3370
|
+
else if (record3.action === "dispatch") byKey.set(record3.key, record3);
|
|
3371
|
+
}
|
|
3372
|
+
return [...byKey.values()];
|
|
3373
|
+
};
|
|
3374
|
+
return {
|
|
3375
|
+
claim: (input) => {
|
|
3376
|
+
const identity = {
|
|
3377
|
+
tracker: required11(input.tracker, "tracker"),
|
|
3378
|
+
repository: required11(input.repository, "repository"),
|
|
3379
|
+
issue: required11(input.issue, "issue"),
|
|
3380
|
+
worktree: required11(input.worktree, "worktree"),
|
|
3381
|
+
branch: required11(input.branch, "branch")
|
|
3382
|
+
};
|
|
3383
|
+
const owner = required11(input.owner, "owner");
|
|
3384
|
+
const key = safeKey(identity);
|
|
3385
|
+
const path = claimPath(key);
|
|
3386
|
+
if (existsSync(path)) return { decision: "already-claimed", lease: parse(readFileSync(path, "utf8"), "claim") };
|
|
3387
|
+
const lease = { ...identity, key, leaseId: randomUUID(), owner, claimedAt: now3() };
|
|
3388
|
+
let fd;
|
|
3389
|
+
try {
|
|
3390
|
+
fd = openSync(path, "wx");
|
|
3391
|
+
} catch (error) {
|
|
3392
|
+
if (error.code === "EEXIST") return { decision: "already-claimed", lease: parse(readFileSync(path, "utf8"), "claim") };
|
|
3393
|
+
throw error;
|
|
3394
|
+
}
|
|
3395
|
+
try {
|
|
3396
|
+
writeFileSync(fd, JSON.stringify(lease), "utf8");
|
|
3397
|
+
} finally {
|
|
3398
|
+
closeSync(fd);
|
|
3399
|
+
}
|
|
3400
|
+
append({ ...lease, action: "dispatch", at: lease.claimedAt });
|
|
3401
|
+
return { decision: "claimed", lease };
|
|
3402
|
+
},
|
|
3403
|
+
recordDispatch: ({ lease, idempotencyKey, commandDigest }) => {
|
|
3404
|
+
const id2 = required11(idempotencyKey, "idempotencyKey");
|
|
3405
|
+
const digest6 = required11(commandDigest, "commandDigest");
|
|
3406
|
+
const existing = records().find((record4) => record4.action === "dispatch" && record4.idempotencyKey === id2);
|
|
3407
|
+
if (existing) return { decision: "duplicate", record: existing };
|
|
3408
|
+
const record3 = { ...lease, action: "dispatch", at: now3(), idempotencyKey: id2, commandDigest: digest6 };
|
|
3409
|
+
append(record3);
|
|
3410
|
+
return { decision: "recorded", record: record3 };
|
|
3411
|
+
},
|
|
3412
|
+
release: (lease, reason = "lease released") => {
|
|
3413
|
+
const path = claimPath(required11(lease.key, "lease.key"));
|
|
3414
|
+
if (!existsSync(path)) fail("Dispatch lease is not active.", "INVALID_STATE");
|
|
3415
|
+
const current = parse(readFileSync(path, "utf8"), "claim");
|
|
3416
|
+
if (current.leaseId !== lease.leaseId) fail("Dispatch lease owner does not match.", "INVALID_STATE");
|
|
3417
|
+
unlinkSync(path);
|
|
3418
|
+
const record3 = { ...current, action: "release", at: now3(), reason: required11(reason, "reason") };
|
|
3419
|
+
append(record3);
|
|
3420
|
+
return record3;
|
|
3421
|
+
},
|
|
3422
|
+
recover: (key, input) => {
|
|
3423
|
+
if (input.actor !== "human") fail("Dispatch lease recovery requires a human actor.", "HUMAN_APPROVAL_REQUIRED");
|
|
3424
|
+
const normalizedKey = required11(key, "key");
|
|
3425
|
+
const maxAgeMs = input.maxAgeMs ?? 3e5;
|
|
3426
|
+
if (!Number.isInteger(maxAgeMs) || maxAgeMs < 0) fail("maxAgeMs must be a non-negative integer.", "INVALID_INPUT");
|
|
3427
|
+
const path = claimPath(normalizedKey);
|
|
3428
|
+
if (!existsSync(path)) fail("Dispatch lease is not active.", "INVALID_STATE");
|
|
3429
|
+
const current = parse(readFileSync(path, "utf8"), "claim");
|
|
3430
|
+
if (Date.now() - Date.parse(current.claimedAt) < maxAgeMs) fail("Dispatch lease is not old enough to recover.", "HARNESS_ERROR");
|
|
3431
|
+
unlinkSync(path);
|
|
3432
|
+
const record3 = { ...current, action: "recover", at: now3(), reason: required11(input.reason, "reason") };
|
|
3433
|
+
append(record3);
|
|
3434
|
+
return record3;
|
|
3435
|
+
},
|
|
3436
|
+
active,
|
|
3437
|
+
records
|
|
3438
|
+
};
|
|
3439
|
+
};
|
|
3440
|
+
var DOC_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".mdx", ".txt", ".adoc", ".rst"]);
|
|
3441
|
+
var TEST_SUFFIXES = [".test.", ".spec.", "__tests__"];
|
|
3442
|
+
var SHELL_META = /[;&|`$()<>\n\r]/;
|
|
3443
|
+
var normalizedPath = (value, label) => {
|
|
3444
|
+
if (typeof value !== "string" || !value.trim()) fail(`${label} must be a non-empty path.`, "INVALID_INPUT");
|
|
3445
|
+
const path = value.trim().replaceAll("\\", "/");
|
|
3446
|
+
if (path.startsWith("/") || path.split("/").includes("..")) fail(`${label} must be repository-relative.`, "INVALID_INPUT");
|
|
3447
|
+
return path;
|
|
3448
|
+
};
|
|
3449
|
+
var validateSafeCommand = (command) => {
|
|
3450
|
+
if (typeof command !== "string" || !command.trim()) fail("command must be a non-empty string.", "INVALID_INPUT");
|
|
3451
|
+
const value = command.trim();
|
|
3452
|
+
if (SHELL_META.test(value)) fail("command contains shell metacharacters; use argv-based execution.", "POLICY_BLOCKED");
|
|
3453
|
+
return { valid: true, command: value };
|
|
3454
|
+
};
|
|
3455
|
+
var isTest = (path) => TEST_SUFFIXES.some((suffix) => path.includes(suffix)) || /(^|\/)(test|tests|__tests__)\//.test(path);
|
|
3456
|
+
var isDoc = (path) => DOC_EXTENSIONS.has(extname(path).toLowerCase());
|
|
3457
|
+
var planFilePreflight = (files, options = {}) => {
|
|
3458
|
+
if (!Array.isArray(files)) fail("files must be an array.", "INVALID_INPUT");
|
|
3459
|
+
const unique2 = [...new Set(files.map((file, index2) => normalizedPath(file.path, `files[${index2}].path`)))].sort();
|
|
3460
|
+
const codeFiles = unique2.filter((path) => !isDoc(path) && !isTest(path));
|
|
3461
|
+
const existingTests = unique2.filter(isTest);
|
|
3462
|
+
const roots = (options.testRoots ?? ["test", "tests", "__tests__"]).map((root, index2) => normalizedPath(root, `testRoots[${index2}]`));
|
|
3463
|
+
const colocated = options.includeTests === false ? [] : codeFiles.flatMap((path) => {
|
|
3464
|
+
const file = basename(path);
|
|
3465
|
+
const directory = dirname(path);
|
|
3466
|
+
const stem = file.includes(".") ? file.slice(0, file.lastIndexOf(".")) : file;
|
|
3467
|
+
return [join(directory, `${stem}.test.ts`), join(directory, `${stem}.spec.ts`)].filter((candidate) => unique2.includes(candidate));
|
|
3468
|
+
});
|
|
3469
|
+
const testFiles = [...new Set([...existingTests, ...colocated, ...unique2.filter((path) => roots.some((root) => path === root || path.startsWith(`${root}/`)))].sort())];
|
|
3470
|
+
const docsOnly = unique2.length > 0 && codeFiles.length === 0 && existingTests.length === 0;
|
|
3471
|
+
return { files: unique2, codeFiles, testFiles, docsOnly, checks: docsOnly ? [] : ["lint", "typecheck", ...testFiles.length ? ["test"] : []] };
|
|
3472
|
+
};
|
|
3473
|
+
|
|
3474
|
+
// src/kernel/block.ts
|
|
3475
|
+
var BLOCK_STATUSES = ["todo", "picked", "development", "validation", "pr-open", "merged", "post-merge", "done", "blocked", "scope-cut"];
|
|
3476
|
+
var text5 = (value, label) => {
|
|
3477
|
+
return typeof value === "string" && value.trim() ? value.trim() : fail(`${label} must be a non-empty string.`, "INVALID_INPUT");
|
|
3478
|
+
};
|
|
3479
|
+
var list = (value, label) => {
|
|
3480
|
+
if (!Array.isArray(value)) return fail(`${label} must be an array of non-empty strings.`, "INVALID_INPUT");
|
|
3481
|
+
if (!value.every((item) => typeof item === "string" && Boolean(item.trim()))) return fail(`${label} must be an array of non-empty strings.`, "INVALID_INPUT");
|
|
3482
|
+
return [...new Set(value.map((item) => item.trim()))];
|
|
3483
|
+
};
|
|
3484
|
+
var validateBlockManifest = (value) => {
|
|
3485
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) fail("block manifest must be an object.", "INVALID_INPUT");
|
|
3486
|
+
const raw = value;
|
|
3487
|
+
if (raw["schemaVersion"] !== 1) fail("block manifest schemaVersion must be 1.", "INVALID_INPUT");
|
|
3488
|
+
const criteria = list(raw["acceptanceCriteria"], "acceptanceCriteria");
|
|
3489
|
+
if (!criteria.length) fail("acceptanceCriteria must not be empty.", "INVALID_INPUT");
|
|
3490
|
+
const dependencies = list(raw["dependencies"] ?? [], "dependencies");
|
|
3491
|
+
const wave = raw["wave"];
|
|
3492
|
+
if (!Number.isInteger(wave) || wave < 1) fail("wave must be a positive integer.", "INVALID_INPUT");
|
|
3493
|
+
const status = raw["status"];
|
|
3494
|
+
if (!BLOCK_STATUSES.includes(status)) fail("status is invalid.", "INVALID_INPUT");
|
|
3495
|
+
const budgetRaw = raw["budget"];
|
|
3496
|
+
let budget;
|
|
3497
|
+
if (budgetRaw !== void 0) {
|
|
3498
|
+
if (typeof budgetRaw !== "object" || budgetRaw === null || Array.isArray(budgetRaw)) fail("budget must be an object.", "INVALID_INPUT");
|
|
3499
|
+
const candidate = budgetRaw;
|
|
3500
|
+
for (const key of ["maxMinutes", "maxAttempts"]) if (candidate[key] !== void 0 && (!Number.isInteger(candidate[key]) || candidate[key] < 1)) fail(`budget.${key} must be a positive integer.`, "INVALID_INPUT");
|
|
3501
|
+
budget = { ...candidate["maxMinutes"] === void 0 ? {} : { maxMinutes: candidate["maxMinutes"] }, ...candidate["maxAttempts"] === void 0 ? {} : { maxAttempts: candidate["maxAttempts"] } };
|
|
3502
|
+
}
|
|
3503
|
+
return { schemaVersion: 1, id: text5(raw["id"], "id"), title: text5(raw["title"], "title"), tracker: text5(raw["tracker"], "tracker"), repository: text5(raw["repository"], "repository"), acceptanceCriteria: criteria, dependencies, wave, status, ...budget ? { budget } : {}, ...raw["humanGates"] === void 0 ? {} : { humanGates: list(raw["humanGates"], "humanGates") }, ...raw["sourceHash"] === void 0 ? {} : { sourceHash: text5(raw["sourceHash"], "sourceHash") } };
|
|
3504
|
+
};
|
|
3505
|
+
var assessBlock = (manifest, completedDependencies = []) => {
|
|
3506
|
+
const value = validateBlockManifest(manifest);
|
|
3507
|
+
const completed = new Set(completedDependencies.map((item) => text5(item, "completedDependencies[]")));
|
|
3508
|
+
const blockers = value.dependencies.filter((dependency) => !completed.has(dependency));
|
|
3509
|
+
const next = blockers.length ? [`Complete dependencies: ${blockers.join(", ")}`] : value.status === "blocked" ? ["Resolve the recorded blocker before dispatch."] : ["Dispatch the block with the frozen acceptance criteria."];
|
|
3510
|
+
return { status: blockers.length || value.status === "blocked" ? "blocked" : "ready", manifestHash: hashJson(value), blockers, next };
|
|
3511
|
+
};
|
|
3512
|
+
var LEARNING_STATUSES = ["proposed", "promoted", "rejected"];
|
|
3513
|
+
var text6 = (value, label) => {
|
|
3514
|
+
return typeof value === "string" && value.trim() ? value.trim() : fail(`${label} must be a non-empty string.`, "INVALID_INPUT");
|
|
3515
|
+
};
|
|
3516
|
+
var category = (heading) => {
|
|
3517
|
+
const value = heading.toLowerCase();
|
|
3518
|
+
if (/went well|success|worked/.test(value)) return "worked";
|
|
3519
|
+
if (/problem|failed|blocker|pain/.test(value)) return "problem";
|
|
3520
|
+
if (/adjust|action|next|improv/.test(value)) return "adjustment";
|
|
3521
|
+
return "other";
|
|
3522
|
+
};
|
|
3523
|
+
var parseRetro = (markdown, source, recordedAt = (/* @__PURE__ */ new Date()).toISOString()) => {
|
|
3524
|
+
const input = text6(markdown, "markdown");
|
|
3525
|
+
const origin = text6(source, "source");
|
|
3526
|
+
if (!Number.isFinite(Date.parse(recordedAt))) fail("recordedAt must be a valid timestamp.", "INVALID_INPUT");
|
|
3527
|
+
const records = [];
|
|
3528
|
+
let current = "other";
|
|
3529
|
+
for (const line of input.split(/\r?\n/)) {
|
|
3530
|
+
const heading = line.match(/^#{1,6}\s+(.+)$/);
|
|
3531
|
+
if (heading) {
|
|
3532
|
+
current = category(heading[1] ?? "");
|
|
3533
|
+
continue;
|
|
3534
|
+
}
|
|
3535
|
+
const item = line.match(/^\s*[-*]\s+(?:\[[ xX]\]\s+)?(.+?)\s*$/);
|
|
3536
|
+
if (!item?.[1]?.trim()) continue;
|
|
3537
|
+
const value = item[1].trim();
|
|
3538
|
+
const id2 = `L-${createHash("sha256").update(`${origin}|${current}|${value}`).digest("hex").slice(0, 12)}`;
|
|
3539
|
+
if (!records.some((record3) => record3.id === id2)) records.push({ id: id2, source: origin, category: current, text: value, status: "proposed", recordedAt });
|
|
3540
|
+
}
|
|
3541
|
+
return records;
|
|
3542
|
+
};
|
|
3543
|
+
var promoteLearnings = (records, input) => {
|
|
3544
|
+
if (input.actor !== "human") fail("Learning promotion requires a human actor.", "HUMAN_APPROVAL_REQUIRED");
|
|
3545
|
+
const ids = new Set(input.ids.map((id2) => text6(id2, "ids[]")));
|
|
3546
|
+
const status = input.status ?? "promoted";
|
|
3547
|
+
const result = records.map((record3) => ids.has(record3.id) ? { ...record3, status } : record3);
|
|
3548
|
+
const unknown = [...ids].filter((id2) => !records.some((record3) => record3.id === id2));
|
|
3549
|
+
if (unknown.length) fail(`Unknown learning IDs: ${unknown.join(", ")}`, "INVALID_INPUT");
|
|
3550
|
+
return result;
|
|
3551
|
+
};
|
|
3552
|
+
|
|
3553
|
+
// src/kernel/status.ts
|
|
3554
|
+
var required12 = (value, label) => {
|
|
3555
|
+
return typeof value === "string" && value.trim() ? value.trim() : fail(`${label} must be a non-empty string.`, "INVALID_INPUT");
|
|
3556
|
+
};
|
|
3557
|
+
var createStatusSnapshot = (input) => {
|
|
3558
|
+
const sourceRevision = required12(input.sourceRevision, "sourceRevision");
|
|
3559
|
+
if (!Number.isFinite(Date.parse(input.generatedAt))) fail("generatedAt must be a valid timestamp.", "INVALID_INPUT");
|
|
3560
|
+
if (!Array.isArray(input.blocks)) fail("blocks must be an array.", "INVALID_INPUT");
|
|
3561
|
+
const blocks = input.blocks.map((block, index2) => {
|
|
3562
|
+
if (typeof block !== "object" || block === null || Array.isArray(block)) fail(`blocks[${index2}] must be an object.`, "INVALID_INPUT");
|
|
3563
|
+
const value = block;
|
|
3564
|
+
if (!(typeof value.id === "string" && value.id.trim())) fail(`blocks[${index2}].id is required.`, "INVALID_INPUT");
|
|
3565
|
+
if (!["todo", "picked", "development", "validation", "pr-open", "merged", "post-merge", "done", "blocked", "scope-cut"].includes(value.status)) fail(`blocks[${index2}].status is invalid.`, "INVALID_INPUT");
|
|
3566
|
+
return { ...value, id: value.id.trim() };
|
|
3567
|
+
}).sort((left, right) => left.id.localeCompare(right.id));
|
|
3568
|
+
if (input.metrics !== void 0 && Object.entries(input.metrics).some(([key, value]) => !key.trim() || typeof value !== "number" || !Number.isFinite(value) || value < 0)) fail("metrics must contain finite non-negative numbers.", "INVALID_INPUT");
|
|
3569
|
+
const body3 = { schemaVersion: 1, generatedAt: input.generatedAt, sourceRevision, blocks, ...input.machine ? { machine: input.machine } : {}, ...input.metrics ? { metrics: input.metrics } : {}, ...input.next ? { next: required12(input.next, "next") } : {} };
|
|
3570
|
+
return { ...body3, digest: hashJson(body3) };
|
|
3571
|
+
};
|
|
3572
|
+
var validateStatusSnapshot = (value) => {
|
|
3573
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) fail("status snapshot must be an object.", "INVALID_INPUT");
|
|
3574
|
+
const raw = value;
|
|
3575
|
+
const snapshot = createStatusSnapshot({ generatedAt: required12(raw.generatedAt, "generatedAt"), sourceRevision: required12(raw.sourceRevision, "sourceRevision"), blocks: raw.blocks, ...raw.machine ? { machine: raw.machine } : {}, ...raw.metrics ? { metrics: raw.metrics } : {}, ...raw.next ? { next: raw.next } : {} });
|
|
3576
|
+
if (raw.schemaVersion !== 1 || raw.digest !== snapshot.digest) fail("status snapshot digest or schemaVersion is invalid.", "HARNESS_ERROR");
|
|
3577
|
+
return snapshot;
|
|
3578
|
+
};
|
|
3579
|
+
|
|
3580
|
+
// src/kernel/model-policy.ts
|
|
3581
|
+
var MODEL_ROLES = ["orchestrator", "reviewer", "builder", "watcher"];
|
|
3582
|
+
var required13 = (value, label) => {
|
|
3583
|
+
if (typeof value !== "string" || !value.trim()) fail(`${label} is required.`, "INVALID_INPUT");
|
|
3584
|
+
return value.trim();
|
|
3585
|
+
};
|
|
3586
|
+
var createModelPolicy = (bindings) => {
|
|
3587
|
+
if (!Array.isArray(bindings) || !bindings.length) fail("bindings must be a non-empty array.", "INVALID_INPUT");
|
|
3588
|
+
const normalized = bindings.map((binding2, index2) => {
|
|
3589
|
+
if (typeof binding2 !== "object" || binding2 === null || Array.isArray(binding2)) fail(`bindings[${index2}] must be an object.`, "INVALID_INPUT");
|
|
3590
|
+
if (!MODEL_ROLES.includes(binding2.role)) fail(`bindings[${index2}].role is invalid.`, "INVALID_INPUT");
|
|
3591
|
+
if (binding2.maxTokens !== void 0 && (!Number.isInteger(binding2.maxTokens) || binding2.maxTokens < 1)) fail(`bindings[${index2}].maxTokens must be a positive integer.`, "INVALID_INPUT");
|
|
3592
|
+
return { role: binding2.role, provider: required13(binding2.provider, `bindings[${index2}].provider`), model: required13(binding2.model, `bindings[${index2}].model`), ...binding2.maxTokens === void 0 ? {} : { maxTokens: binding2.maxTokens } };
|
|
3593
|
+
});
|
|
3594
|
+
if (new Set(normalized.map((binding2) => binding2.role)).size !== normalized.length) fail("Each model role may be bound only once.", "INVALID_INPUT");
|
|
3595
|
+
return { bindings: normalized, digest: hashJson(normalized) };
|
|
3596
|
+
};
|
|
3597
|
+
var modelFor = (policy, role) => policy.bindings.find((binding2) => binding2.role === role) ?? fail(`No model binding exists for role: ${role}.`, "INVALID_STATE");
|
|
3598
|
+
|
|
3599
|
+
// src/adapters/orca.ts
|
|
3600
|
+
var required14 = (value, label) => {
|
|
3601
|
+
if (typeof value !== "string" || !value.trim()) fail(`${label} is required.`, "INVALID_INPUT");
|
|
3602
|
+
return value.trim();
|
|
3603
|
+
};
|
|
3604
|
+
var createOrcaDispatchPlan = (input) => {
|
|
3605
|
+
const repository = required14(input.repository, "repository");
|
|
3606
|
+
const worktree = required14(input.worktree, "worktree");
|
|
3607
|
+
const branch = required14(input.branch, "branch");
|
|
3608
|
+
const baseBranch = required14(input.baseBranch, "baseBranch");
|
|
3609
|
+
const goalFile = required14(input.goalFile, "goalFile");
|
|
3610
|
+
const agent = required14(input.agent ?? "default", "agent");
|
|
3611
|
+
const argv = ["orca", "worktree", "create", "--repo", repository, "--name", worktree, "--base-branch", baseBranch, "--agent", agent, "--prompt-file", goalFile];
|
|
3612
|
+
validateSafeCommand(argv.join(" "));
|
|
3613
|
+
const identity = { repository, worktree, branch, baseBranch, goalFile, agent };
|
|
3614
|
+
return { argv, commandDigest: hashJson(argv), idempotencyKey: hashJson(identity) };
|
|
3615
|
+
};
|
|
3616
|
+
var createOrcaLifecycleProjection = (input) => {
|
|
3617
|
+
const issueRef = required14(input.issueRef, "issueRef");
|
|
3618
|
+
const repository = required14(input.repository, "repository");
|
|
3619
|
+
const worktree = required14(input.worktree, "worktree");
|
|
3620
|
+
const branch = required14(input.branch, "branch");
|
|
3621
|
+
if (!["acquired", "resumed", "conflict", "released"].includes(input.leaseState)) fail("leaseState is invalid.", "INVALID_INPUT");
|
|
3622
|
+
if (input.issueLock !== "held" && input.issueLock !== "missing") fail("issueLock is invalid.", "INVALID_INPUT");
|
|
3623
|
+
const expected = input.expectedRemoteSha?.trim();
|
|
3624
|
+
const observed = input.observedRemoteSha?.trim();
|
|
3625
|
+
const remoteShaConfirmed = Boolean(expected && observed && expected === observed);
|
|
3626
|
+
const cleanupAllowed = input.cleanupRequested === true && remoteShaConfirmed && input.leaseState === "released";
|
|
3627
|
+
const status = input.leaseState === "conflict" || input.issueLock === "missing" ? "blocked" : input.cleanupRequested === true && !remoteShaConfirmed ? "escalated" : input.leaseState === "resumed" ? "resume" : "ready";
|
|
3628
|
+
return { status, leaseState: input.leaseState, worktreeKey: hashJson({ issueRef, repository, worktree, branch }), issueLock: input.issueLock, remoteShaConfirmed, cleanupAllowed, assurance: "contract-tested", telemetry: { status: "measured", durationMs: 0 } };
|
|
3629
|
+
};
|
|
3630
|
+
|
|
3631
|
+
// src/adapters/tracking.ts
|
|
3632
|
+
var required15 = (value, label) => {
|
|
3633
|
+
if (typeof value !== "string" || !value.trim()) fail(`${label} is required.`, "INVALID_INPUT");
|
|
3634
|
+
return value.trim();
|
|
3635
|
+
};
|
|
3636
|
+
var createTrackingTransition = (input) => {
|
|
3637
|
+
const transition2 = { tracker: required15(input.tracker, "tracker"), issue: required15(input.issue, "issue"), ...input.from ? { from: required15(input.from, "from") } : {}, to: required15(input.to, "to"), reason: required15(input.reason, "reason") };
|
|
3638
|
+
return { ...transition2, idempotencyKey: hashJson(transition2) };
|
|
3639
|
+
};
|
|
3640
|
+
var createTrackingAdapter = (id2, handler, options = {}) => {
|
|
3641
|
+
const adapterId = required15(id2, "id");
|
|
3642
|
+
const completed = /* @__PURE__ */ new Set();
|
|
3643
|
+
let writes = 0;
|
|
3644
|
+
return {
|
|
3645
|
+
id: adapterId,
|
|
3646
|
+
assurance: "contract-tested",
|
|
3647
|
+
telemetry: () => ({ status: "measured", externalMutations: writes }),
|
|
3648
|
+
transition: async (input) => {
|
|
3649
|
+
const transition2 = createTrackingTransition(input);
|
|
3650
|
+
if (!completed.has(transition2.idempotencyKey)) {
|
|
3651
|
+
if (!options.dryRun) {
|
|
3652
|
+
await handler(transition2);
|
|
3653
|
+
writes += 1;
|
|
3654
|
+
}
|
|
3655
|
+
completed.add(transition2.idempotencyKey);
|
|
3656
|
+
}
|
|
3657
|
+
return transition2;
|
|
3658
|
+
}
|
|
3659
|
+
};
|
|
3660
|
+
};
|
|
1741
3661
|
var EVIDENCE_BUNDLE_SCHEMA_VERSION = 1;
|
|
1742
|
-
var
|
|
3662
|
+
var body2 = (bundle) => {
|
|
1743
3663
|
const { payloadHash: _payloadHash, signature: _signature, ...unsigned } = bundle;
|
|
1744
3664
|
return unsigned;
|
|
1745
3665
|
};
|
|
@@ -1764,7 +3684,7 @@ var exportEvidenceBundle = async ({ configPath, runId, outputPath, privateKeyPat
|
|
|
1764
3684
|
const loaded = loadConfig(configPath);
|
|
1765
3685
|
const run = requireRun2(runId ? readJson(join(loaded.stateDir, "runs", runId, "run.json")) : loadLatestRun(loaded.stateDir));
|
|
1766
3686
|
const reconciliation = await reconcileRun({ configPath, runId: run.runId });
|
|
1767
|
-
const
|
|
3687
|
+
const digest6 = run.verificationDigest ?? fail("Only a reconciled COMPLETE run can be exported.", "INVALID_STATE");
|
|
1768
3688
|
if (reconciliation.state !== "COMPLETE") fail("Only a reconciled COMPLETE run can be exported.", "INVALID_STATE");
|
|
1769
3689
|
const eventLog = new FileEventStore(loaded.stateDir);
|
|
1770
3690
|
eventLog.read(run.runId);
|
|
@@ -1779,7 +3699,7 @@ var exportEvidenceBundle = async ({ configPath, runId, outputPath, privateKeyPat
|
|
|
1779
3699
|
return bundleFile(loaded.stateDir, path);
|
|
1780
3700
|
});
|
|
1781
3701
|
const privateKey = createPrivateKey(readFileSync(privateKeyPath));
|
|
1782
|
-
const unsigned = { type: "agentskit-harness-evidence-bundle", schemaVersion: EVIDENCE_BUNDLE_SCHEMA_VERSION, runId: run.runId, signerKeyId: keyId, sourceRevision: run.sourceRevision, configHash: run.configHash, contractHash: run.contractHash, verificationDigest:
|
|
3702
|
+
const unsigned = { type: "agentskit-harness-evidence-bundle", schemaVersion: EVIDENCE_BUNDLE_SCHEMA_VERSION, runId: run.runId, signerKeyId: keyId, sourceRevision: run.sourceRevision, configHash: run.configHash, contractHash: run.contractHash, verificationDigest: digest6, eventLog: eventVerification, files };
|
|
1783
3703
|
const payloadHash = sha256(JSON.stringify(unsigned));
|
|
1784
3704
|
const publicKeyPem = createPublicKey(privateKey).export({ type: "spki", format: "pem" }).toString();
|
|
1785
3705
|
const bundle = { ...unsigned, payloadHash, signature: { algorithm: "ed25519", keyId, publicKeyPem, signatureBase64: sign(null, Buffer.from(payloadHash), privateKey).toString("base64") } };
|
|
@@ -1804,7 +3724,7 @@ var verifyEvidenceBundle = (path, { trustedKeys = [] } = {}) => {
|
|
|
1804
3724
|
if (sha256(content) !== file.sha256) fail(`Evidence bundle file hash mismatch: ${file.path}`, "HARNESS_ERROR");
|
|
1805
3725
|
}
|
|
1806
3726
|
if (!paths.has(`runs/${bundle.runId}/run.json`) || !paths.has(`runs/${bundle.runId}/events.ndjson`)) fail("Evidence bundle is missing the run projection or event log.", "HARNESS_ERROR");
|
|
1807
|
-
if (sha256(JSON.stringify(
|
|
3727
|
+
if (sha256(JSON.stringify(body2(bundle))) !== bundle.payloadHash) fail("Evidence bundle payload hash mismatch.", "HARNESS_ERROR");
|
|
1808
3728
|
let valid = false;
|
|
1809
3729
|
try {
|
|
1810
3730
|
valid = verify(null, Buffer.from(bundle.payloadHash), createPublicKey(bundle.signature.publicKeyPem), Buffer.from(bundle.signature.signatureBase64, "base64"));
|
|
@@ -1823,6 +3743,6 @@ var readEvidenceTrustStore = (path) => {
|
|
|
1823
3743
|
});
|
|
1824
3744
|
};
|
|
1825
3745
|
|
|
1826
|
-
export { BENCHMARK_SCHEMA_VERSION, CONTEXT_PROVIDER_SLOT, EVENT_LOG_GENESIS, EVIDENCE_BUNDLE_SCHEMA_VERSION, FileEventStore, HARNESS_EVENT_SCHEMA_VERSION, HARNESS_EVENT_TYPES, HARNESS_PLUGIN_API_VERSION, HarnessError, LEGAL_TRANSITIONS, STATES, approveRun, approvedDecision, assertHuman, authorizeRun, benchmarkRuns, cancelRun, cleanTaskArtifacts, createDocBridgeContextProvider, createDockerToolRuntime, createPluginRegistry, createPluginSlot, createPolicyGate, createProcessToolRuntime, createSessionRecorder, createToolRuntime, exportEvidenceBundle, hashContextSnapshot, hashContextSnapshots, inspectEventLogLock, loadBenchmarkManifest, loadConfig, loadLatestRun, planRun, readContextSnapshots, readEvidenceTrustStore, reconcileRun, recordBenchmarkObservation, recoverEventLogLock, retryRun, startRun, transition, validateBenchmarkManifest, validateConfig, validateContextSnapshot, validateContextSnapshots,
|
|
3746
|
+
export { ARTIFACT_SCHEMA_VERSION, ARTIFACT_TYPES, ASSURANCE_LEVELS, BENCHMARK_SCHEMA_VERSION, BLOCK_STATUSES, CAPABILITY_KINDS, CAPABILITY_MANIFEST_SCHEMA_VERSION, COMPATIBILITY_COMPONENTS, COMPATIBILITY_SCHEMA_VERSION, CONTEXT_PROVIDER_SLOT, EVAL_COMPONENTS, EVAL_LAYERS, EVAL_MANIFEST_SCHEMA_VERSION, EVENT_LOG_GENESIS, EVIDENCE_BUNDLE_SCHEMA_VERSION, FileArtifactStore, FileEventStore, HARNESS_ERROR_CATALOG, HARNESS_ERROR_CODES, HARNESS_EVENT_ENVELOPE_SCHEMA_VERSION, HARNESS_EVENT_SCHEMA_VERSION, HARNESS_EVENT_TYPES, HARNESS_PLUGIN_API_VERSION, HarnessError, IMPROVEMENT_CYCLE_STEPS, LEARNING_STATUSES, LEGAL_TRANSITIONS, MEMORY_SCOPES, MODEL_ROLES, PHASE_DECISIONS, PHASE_EFFECTS, PHASE_EFFECT_ACTIONS, PHASE_MODES, QUALITY_DIMENSIONS, STATES, WIP_STATES, adaptiveConcurrency, approveRun, approvedDecision, artifactDigest, artifactFilePath, artifactIsFresh, artifactMarkdownPath, assertHuman, assessAcceptance, assessAgentEval, assessBlock, assessCompatibility, assessDiscovery, assessImprovementCycle, assessIntegration, assessPilot, assessPreflight, assessProduction, assessQaTransition, assessWip, assessWorktreeCleanup, authorizeRun, benchmarkRuns, cancelRun, classifyFailure, classifyHarnessError, cleanTaskArtifacts, compareOptimization, composePullRequest, createArtifactEnvelope, createCapabilityManifest, createCodingAgentAdapter, createCompatibilityManifest, createConfiguredToolRuntime, createDispatchLedger, createDocBridgeContextProvider, createDockerToolRuntime, createEvalManifest, createHarnessEventEnvelope, createInMemoryMemoryAdapter, createKvMemoryAdapter, createLlmCache, createLlmCacheKey, createMachineMonitor, createModelPolicy, createOrcaDispatchPlan, createOrcaLifecycleProjection, createPhaseArtifact, createPhaseProfile, createPluginRegistry, createPluginSlot, createPolicyGate, createProcessToolRuntime, createPullRequestApproval, createQualityMatrix, createSessionRecorder, createStatusSnapshot, createToolRuntime, createTrackingAdapter, createTrackingTransition, evaluateWatchdog, executePhaseProfile, exportEvidenceBundle, hashContextSnapshot, hashContextSnapshots, inspectEventLogLock, isDiscoveryCurrent, loadBenchmarkManifest, loadConfig, loadLatestRun, modelFor, parseRetro, planFilePreflight, planPhaseProfile, planRun, promoteLearnings, readArtifactFile, readContextSnapshots, readEvidenceTrustStore, reconcileRun, recordBenchmarkObservation, recoverEventLogLock, recoveryDelayMs, renderArtifactMarkdown, resumeStateFromArtifacts, retryRun, runAdversarialReview, runAgentEval, runEvalBattery, runWithRecovery, runWorkflow, sampleMachine, selectRuntime, startRun, summarizeMachine, transition, unknownTelemetry, validateAdapterMetadata, validateArtifactEnvelope, validateBenchmarkManifest, validateBlockManifest, validateCacheableOperation, validateCapabilityManifest, validateCompatibilityManifest, validateConfig, validateContextSnapshot, validateContextSnapshots, validateEvalManifest, validateHarnessErrorClassification, validateHarnessEventEnvelope, validateMemoryRecord, validateOptimizationObservation, validatePhaseTelemetry, validateSafeCommand, validateStatusSnapshot, verifyEvidenceBundle, verifyPullRequestApproval, verifyRun };
|
|
1827
3747
|
//# sourceMappingURL=index.js.map
|
|
1828
3748
|
//# sourceMappingURL=index.js.map
|