engineering-behavior-observatory 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +151 -0
- package/contracts/codex-app-server-0.150.1/manifest.json +25 -0
- package/contracts/codex-app-server-0.150.1/schema/InitializeParams.json +84 -0
- package/contracts/codex-app-server-0.150.1/schema/JSONRPCMessage.json +137 -0
- package/contracts/codex-app-server-0.150.1/schema/v2/ThreadReadParams.json +17 -0
- package/contracts/codex-app-server-0.150.1/schema/v2/ThreadTokenUsageUpdatedNotification.json +82 -0
- package/contracts/codex-app-server-0.150.1/schema/v2/TurnInterruptParams.json +17 -0
- package/contracts/codex-app-server-0.150.1/types/AskForApproval.ts +5 -0
- package/contracts/codex-app-server-0.150.1/types/ClientNotification.ts +5 -0
- package/contracts/codex-app-server-0.150.1/types/SandboxMode.ts +5 -0
- package/contracts/codex-app-server-0.150.1/types/ThreadReadParams.ts +9 -0
- package/contracts/codex-app-server-0.150.1/types/TokenUsageBreakdown.ts +5 -0
- package/contracts/codex-app-server-0.150.1/types/TurnInterruptParams.ts +5 -0
- package/contracts/codex-app-server-0.153.4/manifest.json +27 -0
- package/contracts/codex-app-server-0.153.4/schema/JSONRPCMessage.json +137 -0
- package/contracts/codex-app-server-0.153.4/schema/v1/InitializeParams.json +84 -0
- package/contracts/codex-app-server-0.153.4/schema/v2/ThreadReadParams.json +17 -0
- package/contracts/codex-app-server-0.153.4/schema/v2/ThreadStartParams.json +515 -0
- package/contracts/codex-app-server-0.153.4/schema/v2/ThreadTokenUsageUpdatedNotification.json +82 -0
- package/contracts/codex-app-server-0.153.4/schema/v2/TurnInterruptParams.json +17 -0
- package/contracts/codex-app-server-0.153.4/schema/v2/TurnStartParams.json +921 -0
- package/contracts/codex-app-server-0.153.4/types/AskForApproval.ts +5 -0
- package/contracts/codex-app-server-0.153.4/types/ClientNotification.ts +5 -0
- package/contracts/codex-app-server-0.153.4/types/SandboxMode.ts +5 -0
- package/contracts/codex-app-server-0.153.4/types/ThreadReadParams.ts +12 -0
- package/contracts/codex-app-server-0.153.4/types/TokenUsageBreakdown.ts +5 -0
- package/contracts/codex-app-server-0.153.4/types/TurnInterruptParams.ts +5 -0
- package/contracts/openhands-agent-server-v1.44.1.json +65 -0
- package/contracts/openhands-agent-server-v1.46.0.json +46 -0
- package/dist/contracts/codex-app-server-0.153.4/types/AskForApproval.d.ts +9 -0
- package/dist/contracts/codex-app-server-0.153.4/types/AskForApproval.js +2 -0
- package/dist/contracts/codex-app-server-0.153.4/types/ClientNotification.d.ts +3 -0
- package/dist/contracts/codex-app-server-0.153.4/types/ClientNotification.js +2 -0
- package/dist/contracts/codex-app-server-0.153.4/types/SandboxMode.d.ts +1 -0
- package/dist/contracts/codex-app-server-0.153.4/types/SandboxMode.js +2 -0
- package/dist/contracts/codex-app-server-0.153.4/types/ThreadReadParams.d.ts +10 -0
- package/dist/contracts/codex-app-server-0.153.4/types/ThreadReadParams.js +2 -0
- package/dist/contracts/codex-app-server-0.153.4/types/TokenUsageBreakdown.d.ts +8 -0
- package/dist/contracts/codex-app-server-0.153.4/types/TokenUsageBreakdown.js +2 -0
- package/dist/contracts/codex-app-server-0.153.4/types/TurnInterruptParams.d.ts +4 -0
- package/dist/contracts/codex-app-server-0.153.4/types/TurnInterruptParams.js +2 -0
- package/dist/src/agent-sdk-normalizer.d.ts +22 -0
- package/dist/src/agent-sdk-normalizer.js +995 -0
- package/dist/src/agent-sdk-run.d.ts +31 -0
- package/dist/src/agent-sdk-run.js +242 -0
- package/dist/src/agent-sdk-runner.d.ts +96 -0
- package/dist/src/agent-sdk-runner.js +407 -0
- package/dist/src/agent-sdk.d.ts +294 -0
- package/dist/src/agent-sdk.js +644 -0
- package/dist/src/aggregation.d.ts +183 -0
- package/dist/src/aggregation.js +513 -0
- package/dist/src/artifacts.d.ts +36 -0
- package/dist/src/artifacts.js +1372 -0
- package/dist/src/atlas-grafana.d.ts +377 -0
- package/dist/src/atlas-grafana.js +47 -0
- package/dist/src/atlas-html.d.ts +2 -0
- package/dist/src/atlas-html.js +34 -0
- package/dist/src/atlas.d.ts +130 -0
- package/dist/src/atlas.js +235 -0
- package/dist/src/behavior-assertions.d.ts +95 -0
- package/dist/src/behavior-assertions.js +154 -0
- package/dist/src/cli.d.ts +5 -0
- package/dist/src/cli.js +957 -0
- package/dist/src/codex-judge.d.ts +44 -0
- package/dist/src/codex-judge.js +214 -0
- package/dist/src/codex-run.d.ts +114 -0
- package/dist/src/codex-run.js +519 -0
- package/dist/src/codex.d.ts +187 -0
- package/dist/src/codex.js +1028 -0
- package/dist/src/contracts.d.ts +155 -0
- package/dist/src/contracts.js +866 -0
- package/dist/src/corpus.d.ts +67 -0
- package/dist/src/corpus.js +540 -0
- package/dist/src/cursor-sdk-runner.d.ts +81 -0
- package/dist/src/cursor-sdk-runner.js +362 -0
- package/dist/src/cursor-sdk.d.ts +81 -0
- package/dist/src/cursor-sdk.js +1078 -0
- package/dist/src/deepseek-adapter.d.ts +152 -0
- package/dist/src/deepseek-adapter.js +777 -0
- package/dist/src/exports.d.ts +69 -0
- package/dist/src/exports.js +966 -0
- package/dist/src/human-calibration.d.ts +167 -0
- package/dist/src/human-calibration.js +618 -0
- package/dist/src/index.d.ts +71 -0
- package/dist/src/index.js +38 -0
- package/dist/src/lifecycle.d.ts +196 -0
- package/dist/src/lifecycle.js +1889 -0
- package/dist/src/normalization-integrity.d.ts +128 -0
- package/dist/src/normalization-integrity.js +429 -0
- package/dist/src/openhands-run.d.ts +36 -0
- package/dist/src/openhands-run.js +415 -0
- package/dist/src/openhands.d.ts +130 -0
- package/dist/src/openhands.js +859 -0
- package/dist/src/pi.d.ts +203 -0
- package/dist/src/pi.js +1345 -0
- package/dist/src/process-protocol.d.ts +245 -0
- package/dist/src/process-protocol.js +1261 -0
- package/dist/src/retained-evidence.d.ts +12 -0
- package/dist/src/retained-evidence.js +201 -0
- package/dist/src/run-bundles.d.ts +193 -0
- package/dist/src/run-bundles.js +993 -0
- package/dist/src/scheduler.d.ts +109 -0
- package/dist/src/scheduler.js +1080 -0
- package/dist/src/semantic-judge.d.ts +194 -0
- package/dist/src/semantic-judge.js +875 -0
- package/dist/src/structural-observations.d.ts +124 -0
- package/dist/src/structural-observations.js +671 -0
- package/dist/src/task-packets.d.ts +115 -0
- package/dist/src/task-packets.js +683 -0
- package/dist/src/uniform-events.d.ts +120 -0
- package/dist/src/uniform-events.js +158 -0
- package/dist/src/verifiers.d.ts +116 -0
- package/dist/src/verifiers.js +819 -0
- package/dist/src/workspaces.d.ts +56 -0
- package/dist/src/workspaces.js +1314 -0
- package/docs/README.md +60 -0
- package/docs/development/README.md +71 -0
- package/docs/development/documentation-sync.md +58 -0
- package/docs/development/extension-contracts.md +307 -0
- package/docs/evaluation/README.md +48 -0
- package/docs/evaluation/aggregation.md +112 -0
- package/docs/evaluation/behavior-assertions.md +45 -0
- package/docs/evaluation/human-calibration.md +170 -0
- package/docs/evaluation/normalization-integrity.md +80 -0
- package/docs/evaluation/semantic-judge.md +188 -0
- package/docs/evaluation/structural-observations.md +84 -0
- package/docs/evaluation/uniform-events.md +131 -0
- package/docs/guides/atlas.md +144 -0
- package/docs/guides/evidence-and-sharing.md +100 -0
- package/docs/guides/operator-guide.md +424 -0
- package/docs/guides/quickstart.md +152 -0
- package/docs/guides/telemetry.md +66 -0
- package/docs/harnesses/README.md +31 -0
- package/docs/harnesses/claude-agent-sdk.md +61 -0
- package/docs/harnesses/codex-harness.md +174 -0
- package/docs/harnesses/cursor-sdk.md +149 -0
- package/docs/harnesses/deepseek-harness.md +134 -0
- package/docs/harnesses/openhands-agent-server.md +153 -0
- package/docs/harnesses/pi-sdk.md +66 -0
- package/docs/reference/README.md +19 -0
- package/docs/reference/agent-sdk-operational-runner.md +118 -0
- package/docs/reference/cli.md +114 -0
- package/docs/reference/contracts.md +222 -0
- package/docs/reference/run-bundle-contract.md +354 -0
- package/docs/reference/run-lifecycle.md +54 -0
- package/examples/cursor-sdk/README.md +11 -0
- package/examples/cursor-sdk/capture-profile.json +1 -0
- package/examples/cursor-sdk/harness.json +1 -0
- package/examples/cursor-sdk/model.json +1 -0
- package/examples/cursor-sdk/native-limits.json +1 -0
- package/examples/cursor-sdk/native-tool-policy.json +1 -0
- package/ontology/behavior-categories.v1.json +46 -0
- package/package.json +66 -0
- package/release/0.1.0/KNOWN_LIMITATIONS.md +35 -0
- package/release/0.1.0/README.md +46 -0
- package/release/0.1.0/reproducibility.json +61 -0
- package/release/0.2.0/KNOWN_LIMITATIONS.md +40 -0
- package/release/0.2.0/README.md +42 -0
- package/release/0.2.0/reproducibility.json +73 -0
- package/release/0.2.1/KNOWN_LIMITATIONS.md +18 -0
- package/release/0.2.1/README.md +63 -0
- package/release/0.2.1/reproducibility.json +73 -0
- package/release/README.md +13 -0
- package/schemas/aggregation.v1.json +293 -0
- package/schemas/behavior-assertions.v1.json +220 -0
- package/schemas/deepseek-runtime-composition.v1.schema.json +122 -0
- package/schemas/experiment.v1.schema.json +188 -0
- package/schemas/human-calibration.v1.json +336 -0
- package/schemas/normalization-integrity.v1.json +302 -0
- package/schemas/run-bundles/v1.json +1040 -0
- package/schemas/run-queue.v1.schema.json +206 -0
- package/schemas/semantic-judge.v1.json +380 -0
- package/schemas/structural-observations.v1.json +131 -0
- package/schemas/task-packet-freeze.v1.schema.json +106 -0
- package/schemas/task-packet.v1.schema.json +234 -0
- package/schemas/uniform-events/v1.json +289 -0
- package/scripts/atlas-grafana.sh +18 -0
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,957 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { existsSync, mkdirSync, mkdtempSync, realpathSync, renameSync, rmSync } from "node:fs";
|
|
4
|
+
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
import { assertNoDuplicateJsonKeys, canonicalizeMetadata, digestMetadata, validateArtifact, validateExportManifest, validateRunManifestEvidence, writeMetadataAtomically } from "./artifacts.js";
|
|
7
|
+
import { aggregateEvaluation } from "./aggregation.js";
|
|
8
|
+
import { serveAtlas, writeAtlas } from "./atlas.js";
|
|
9
|
+
import { buildCorpusIndex, packPortableExport, queryCorpusIndex, readCorpusIndex, unpackPortableExport, validateCorpusIndex, writeCorpusIndex, } from "./corpus.js";
|
|
10
|
+
import { runAgentSdkQueueEntry } from "./agent-sdk-runner.js";
|
|
11
|
+
import { validateRetainedBehaviorAssertion } from "./behavior-assertions.js";
|
|
12
|
+
import { runCodexQueueEntry } from "./codex-run.js";
|
|
13
|
+
import { runCursorSdkQueueEntry } from "./cursor-sdk-runner.js";
|
|
14
|
+
import { runPiQueueEntry } from "./pi.js";
|
|
15
|
+
import { createPortableRunBundleExport } from "./exports.js";
|
|
16
|
+
import { assertCalibrationDestination, importReviewDecision, selectReviewSample, summarizeCalibration, revalidateReviewSample, validateReviewHistory, writeReviewPacket, } from "./human-calibration.js";
|
|
17
|
+
import { assessComparisonEligibility, assessLegacyComparisonEligibility } from "./normalization-integrity.js";
|
|
18
|
+
import { runAgentSdkSemanticJudge, } from "./semantic-judge.js";
|
|
19
|
+
import { createRetainedStructuralObservationSet } from "./structural-observations.js";
|
|
20
|
+
import { admitTaskPacket, formatErrors, freezeTaskPacket, inspectTaskPacket, statusTaskPacket, } from "./task-packets.js";
|
|
21
|
+
import { compileRunQueue, inspectRunQueue, readBoundedFile, readRunQueue, validateRunQueue, writeRunQueue, } from "./scheduler.js";
|
|
22
|
+
const usage = `Usage: ebo [--help] | validate <artifact.json>... | task-packet <command> ...
|
|
23
|
+
ebo matrix compile <experiment.json> <bundle-root> <queue.json> [--freeze-locator <task-id>=<path>]
|
|
24
|
+
ebo queue inspect <queue.json>
|
|
25
|
+
ebo queue validate <queue.json> [experiment.json] [--bundle-root <bundle-root>]
|
|
26
|
+
ebo agent-sdk run <bundle-root> <queue.json> <run-id> <output-root> [--workspace-root <path>]
|
|
27
|
+
ebo codex run <bundle-root> <queue.json> <run-id> <output-root> [--workspace-root <path>]
|
|
28
|
+
ebo cursor run <bundle-root> <queue.json> <run-id> <output-root> [--workspace-root <path>]
|
|
29
|
+
ebo pi run <bundle-root> <queue.json> <run-id> <output-root> [--workspace-root <path>]
|
|
30
|
+
ebo export create <run-bundle-root> <policy.json> <export-root>
|
|
31
|
+
ebo corpus build <corpus-root> <index.jsonl>
|
|
32
|
+
ebo corpus query <index.jsonl> [--kind|--run|--attempt|--task|--model|--harness|--assessment-mode|--terminal|--failure-class|--verifier-status|--capture|--export-status|--sharing-class <value>]
|
|
33
|
+
ebo corpus validate <corpus-root> <index.jsonl>
|
|
34
|
+
ebo corpus pack <export-root> <policy.json> <archive.tar.gz>
|
|
35
|
+
ebo corpus unpack <archive.tar.gz> <destination-root>
|
|
36
|
+
ebo comparison check <request.json>
|
|
37
|
+
ebo aggregate build <request.json> <output.json>
|
|
38
|
+
ebo atlas build <request.json> <output-root> [--share] [--filter <name=value>]
|
|
39
|
+
ebo atlas serve <request.json> [--port <port>]
|
|
40
|
+
ebo observations create <run-bundle-root> <output.json>
|
|
41
|
+
ebo observations corpus <corpus-root> <index.jsonl> <output-root> [corpus query flags]
|
|
42
|
+
ebo assertions validate <run-bundle-root> <assertion.json> [review.json]
|
|
43
|
+
ebo judge run <run-bundle-root> <observations.json> <request.json> <output-root>
|
|
44
|
+
ebo calibration sample <sources.json> <criteria.json> <selection.json>
|
|
45
|
+
ebo calibration packet <selection.json> <output-root>
|
|
46
|
+
ebo calibration inspect <packet.json> <assertion-id> [event-id]
|
|
47
|
+
ebo calibration binding <selection.json> <assertion-id> [history.json]
|
|
48
|
+
ebo calibration <import|adjudicate> <selection.json> <history.json> <decision.json>
|
|
49
|
+
ebo calibration summarize <selection.json> <history.json> <summary.json>
|
|
50
|
+
|
|
51
|
+
Engineering Behavior Observatory
|
|
52
|
+
`;
|
|
53
|
+
const taskPacketUsage = `Usage: ebo task-packet <validate|admit|freeze|status> <bundle-root> <packet.json> [freeze-record.json]
|
|
54
|
+
`;
|
|
55
|
+
export function main(args = process.argv.slice(2), write = (message) => {
|
|
56
|
+
process.stdout.write(message);
|
|
57
|
+
}, dependencies = {}) {
|
|
58
|
+
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
59
|
+
write(usage);
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
if (args[0] === "task-packet") {
|
|
63
|
+
return runTaskPacketCommand(args.slice(1), write);
|
|
64
|
+
}
|
|
65
|
+
if ((args[0] === "matrix" && args[1] === "compile") || args[0] === "compile") {
|
|
66
|
+
return compileQueue(args[0] === "matrix" ? args.slice(2) : args.slice(1), write);
|
|
67
|
+
}
|
|
68
|
+
if (args[0] === "queue" && args[1] === "inspect") {
|
|
69
|
+
if (args[2] === undefined) {
|
|
70
|
+
write("Usage: ebo queue inspect <queue.json>\n");
|
|
71
|
+
return 1;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const inspection = inspectRunQueue(readRunQueue(args[2]));
|
|
75
|
+
write(`Run queue ${args[2]}: ${inspection.entryCount} entr${inspection.entryCount === 1 ? "y" : "ies"}; seed=${inspection.seed}; strategy=${inspection.strategy}.\n`);
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
write(`${errorMessage(error)}\n`);
|
|
80
|
+
return 1;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (args[0] === "queue" && args[1] === "validate") {
|
|
84
|
+
return validateQueue(args.slice(2), write);
|
|
85
|
+
}
|
|
86
|
+
if (args[0] === "agent-sdk" && args[1] === "run") {
|
|
87
|
+
return runAgentSdkCommand(args.slice(2), write);
|
|
88
|
+
}
|
|
89
|
+
if (args[0] === "codex" && args[1] === "run") {
|
|
90
|
+
return runCodexCommand(args.slice(2), write);
|
|
91
|
+
}
|
|
92
|
+
if (args[0] === "cursor" && args[1] === "run") {
|
|
93
|
+
return runCursorSdkCommand(args.slice(2), write);
|
|
94
|
+
}
|
|
95
|
+
if (args[0] === "pi" && args[1] === "run") {
|
|
96
|
+
return runPiCommand(args.slice(2), write);
|
|
97
|
+
}
|
|
98
|
+
if (args[0] === "export" && args[1] === "create") {
|
|
99
|
+
return createExportCommand(args.slice(2), write);
|
|
100
|
+
}
|
|
101
|
+
if (args[0] === "corpus") {
|
|
102
|
+
return runCorpusCommand(args.slice(1), write);
|
|
103
|
+
}
|
|
104
|
+
if (args[0] === "comparison" && args[1] === "check") {
|
|
105
|
+
if (args[2] === undefined || args.length !== 3) {
|
|
106
|
+
write("Usage: ebo comparison check <request.json>\n");
|
|
107
|
+
return 1;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const request = readJson(args[2]);
|
|
111
|
+
const report = request.schemaVersion === "ebo.comparison-request/v1"
|
|
112
|
+
? assessLegacyComparisonEligibility(request)
|
|
113
|
+
: assessComparisonEligibility(request);
|
|
114
|
+
write(`${canonicalizeMetadata(report)}\n`);
|
|
115
|
+
return report.status === "unsupported" ? 1 : 0;
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
write(`${errorMessage(error)}\n`);
|
|
119
|
+
return 1;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (args[0] === "aggregate" && args[1] === "build") {
|
|
123
|
+
return runAggregationCommand(args.slice(2), write);
|
|
124
|
+
}
|
|
125
|
+
if (args[0] === "atlas") {
|
|
126
|
+
return (async () => {
|
|
127
|
+
try {
|
|
128
|
+
if (args[1] === "serve" && args[2] && (args.length === 3 || (args.length === 5 && args[3] === "--port"))) {
|
|
129
|
+
const server = await serveAtlas(args[2], args[4] === undefined ? 13011 : Number(args[4]));
|
|
130
|
+
const address = server.address();
|
|
131
|
+
write(`Atlas listening at http://127.0.0.1:${typeof address === "object" && address ? address.port : 13011}\n`);
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
if (args[1] !== "build" || !args[2] || !args[3])
|
|
135
|
+
throw new Error("Usage: ebo atlas build <request.json> <output-root> [--share] [--filter <name=value>] | atlas serve <request.json> [--port <port>]");
|
|
136
|
+
const filters = {};
|
|
137
|
+
let share = false;
|
|
138
|
+
for (let i = 4; i < args.length; i++) {
|
|
139
|
+
if (args[i] === "--share") {
|
|
140
|
+
share = true;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (args[i] !== "--filter" || !args[i + 1]?.includes("="))
|
|
144
|
+
throw new Error("Unknown Atlas argument.");
|
|
145
|
+
const pair = args[++i];
|
|
146
|
+
const offset = pair.indexOf("=");
|
|
147
|
+
filters[pair.slice(0, offset)] = pair.slice(offset + 1);
|
|
148
|
+
}
|
|
149
|
+
const view = await writeAtlas(args[2], args[3], filters, share);
|
|
150
|
+
write(`Built Atlas: ${view.report.sourcePopulation.selectedAttempts} selected attempts; ${view.mode}; ${view.cohortDigest}\n`);
|
|
151
|
+
return 0;
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
write(`${errorMessage(error)}\n`);
|
|
155
|
+
return 1;
|
|
156
|
+
}
|
|
157
|
+
})();
|
|
158
|
+
}
|
|
159
|
+
if (args[0] === "observations") {
|
|
160
|
+
return runObservationsCommand(args.slice(1), write);
|
|
161
|
+
}
|
|
162
|
+
if (args[0] === "assertions" && args[1] === "validate") {
|
|
163
|
+
const bundleRoot = args[2];
|
|
164
|
+
const assertionPath = args[3];
|
|
165
|
+
const reviewPath = args[4];
|
|
166
|
+
if (bundleRoot === undefined || assertionPath === undefined || args.length > 5) {
|
|
167
|
+
write("Usage: ebo assertions validate <run-bundle-root> <assertion.json> [review.json]\n");
|
|
168
|
+
return 1;
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const assertion = readJson(assertionPath);
|
|
172
|
+
const review = reviewPath === undefined ? undefined : readJson(reviewPath);
|
|
173
|
+
return validateRetainedBehaviorAssertion(bundleRoot, assertion, review).then((citations) => {
|
|
174
|
+
write(`Validated behavior assertion "${assertion.id}" (${citations.length} citation(s); review=${review?.state ?? "unreviewed"}).\n`);
|
|
175
|
+
return 0;
|
|
176
|
+
}, (error) => {
|
|
177
|
+
write(`${errorMessage(error)}\n`);
|
|
178
|
+
return 1;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
write(`${errorMessage(error)}\n`);
|
|
183
|
+
return 1;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (args[0] === "judge" && args[1] === "run") {
|
|
187
|
+
const bundleRoot = args[2];
|
|
188
|
+
const observationsPath = args[3];
|
|
189
|
+
const requestPath = args[4];
|
|
190
|
+
const outputRoot = args[5];
|
|
191
|
+
if (bundleRoot === undefined || observationsPath === undefined || requestPath === undefined
|
|
192
|
+
|| outputRoot === undefined || args.length !== 6) {
|
|
193
|
+
write("Usage: ebo judge run <run-bundle-root> <observations.json> <request.json> <output-root>\n");
|
|
194
|
+
return 1;
|
|
195
|
+
}
|
|
196
|
+
const controller = new AbortController();
|
|
197
|
+
const abort = () => controller.abort();
|
|
198
|
+
const cleanup = () => {
|
|
199
|
+
process.off("SIGINT", abort);
|
|
200
|
+
process.off("SIGTERM", abort);
|
|
201
|
+
};
|
|
202
|
+
process.on("SIGINT", abort);
|
|
203
|
+
process.on("SIGTERM", abort);
|
|
204
|
+
try {
|
|
205
|
+
return runAgentSdkSemanticJudge({
|
|
206
|
+
bundleRoot,
|
|
207
|
+
observations: readJson(observationsPath),
|
|
208
|
+
request: readJson(requestPath),
|
|
209
|
+
outputRoot,
|
|
210
|
+
signal: controller.signal,
|
|
211
|
+
...(dependencies.semanticJudgeBackend === undefined ? {} : { backend: dependencies.semanticJudgeBackend }),
|
|
212
|
+
}).then((record) => {
|
|
213
|
+
write(`${canonicalizeMetadata(record)}\n`);
|
|
214
|
+
return record.status === "proposed" ? 0 : 1;
|
|
215
|
+
}, (error) => {
|
|
216
|
+
write(`${errorMessage(error)}\n`);
|
|
217
|
+
return 1;
|
|
218
|
+
}).finally(cleanup);
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
cleanup();
|
|
222
|
+
write(`${errorMessage(error)}\n`);
|
|
223
|
+
return 1;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (args[0] === "calibration") {
|
|
227
|
+
return runCalibrationCommand(args.slice(1), write);
|
|
228
|
+
}
|
|
229
|
+
if (args[0] === "validate") {
|
|
230
|
+
if (args.length === 1) {
|
|
231
|
+
write("Usage: ebo validate <artifact.json>...\n");
|
|
232
|
+
return 1;
|
|
233
|
+
}
|
|
234
|
+
const artifacts = args.slice(1).map((artifact) => {
|
|
235
|
+
try {
|
|
236
|
+
return { artifact, document: readJson(artifact) };
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
return {
|
|
240
|
+
artifact,
|
|
241
|
+
error: { artifact, schemaVersion: "unknown", field: "/", message: error instanceof Error ? error.message : "Unable to read artifact." },
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
const errors = artifacts.flatMap(({ artifact, document, error }) => {
|
|
246
|
+
if (error !== undefined)
|
|
247
|
+
return [error];
|
|
248
|
+
if (document !== undefined && typeof document === "object" && document !== null
|
|
249
|
+
&& document.schemaVersion === "ebo.run-queue/v1") {
|
|
250
|
+
return validateRunQueue(document, undefined, artifact);
|
|
251
|
+
}
|
|
252
|
+
const validation = validateArtifact(artifact, document);
|
|
253
|
+
return document !== undefined && typeof document === "object" && document !== null
|
|
254
|
+
&& document.schemaVersion === "run-manifest/v1"
|
|
255
|
+
? [...validation, ...validateRunManifestEvidence(artifact, document, dirname(artifact))]
|
|
256
|
+
: validation;
|
|
257
|
+
});
|
|
258
|
+
for (const { artifact, document } of artifacts) {
|
|
259
|
+
if (document === undefined || typeof document !== "object" || document === null
|
|
260
|
+
|| document.schemaVersion !== "export-manifest/v1")
|
|
261
|
+
continue;
|
|
262
|
+
const bundleId = document.bundleId;
|
|
263
|
+
const containingEntry = artifacts.find(({ document: candidate }) => candidate !== undefined && typeof candidate === "object" && candidate !== null
|
|
264
|
+
&& candidate.schemaVersion === "run-manifest/v1"
|
|
265
|
+
&& candidate.bundleId === bundleId);
|
|
266
|
+
errors.push(...validateExportManifest(artifact, document, containingEntry?.document, containingEntry === undefined ? undefined : dirname(containingEntry.artifact)));
|
|
267
|
+
}
|
|
268
|
+
if (errors.length === 0) {
|
|
269
|
+
write(`Validated ${args.length - 1} artifact(s).\n`);
|
|
270
|
+
return 0;
|
|
271
|
+
}
|
|
272
|
+
for (const error of errors) {
|
|
273
|
+
write(`${error.artifact} [${error.schemaVersion}] ${error.field}: ${error.message}\n`);
|
|
274
|
+
}
|
|
275
|
+
return 1;
|
|
276
|
+
}
|
|
277
|
+
process.stderr.write(`Unknown argument: ${args[0]}\n`);
|
|
278
|
+
return 1;
|
|
279
|
+
}
|
|
280
|
+
async function runAggregationCommand(args, write) {
|
|
281
|
+
const [requestPath, outputPath] = args;
|
|
282
|
+
if (requestPath === undefined || outputPath === undefined || args.length !== 2) {
|
|
283
|
+
write("Usage: ebo aggregate build <request.json> <output.json>\n");
|
|
284
|
+
return 1;
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
const request = readJson(requestPath);
|
|
288
|
+
const errors = validateArtifact(requestPath, request);
|
|
289
|
+
if (errors.length > 0)
|
|
290
|
+
throw new Error(errors.map(({ field, message }) => `${field}: ${message}`).join("\n"));
|
|
291
|
+
const base = dirname(resolve(requestPath));
|
|
292
|
+
const source = request.sources;
|
|
293
|
+
const corpusRoot = resolve(base, source.corpusRoot);
|
|
294
|
+
const corpusIndexPath = resolve(base, source.corpusIndex);
|
|
295
|
+
const corpusEntries = readCorpusIndex(corpusIndexPath);
|
|
296
|
+
const corpusIssues = validateCorpusIndex(corpusRoot, corpusEntries);
|
|
297
|
+
if (corpusIssues.length > 0)
|
|
298
|
+
throw new Error(`Corpus index is not current: ${corpusIssues[0].manifestPath} ${corpusIssues[0].message}`);
|
|
299
|
+
const resolveJson = (path, label) => {
|
|
300
|
+
const resolved = resolve(base, path);
|
|
301
|
+
const value = readJson(resolved);
|
|
302
|
+
const validation = validateArtifact(resolved, value);
|
|
303
|
+
if (validation.length > 0)
|
|
304
|
+
throw new Error(`${label} ${validation[0].field}: ${validation[0].message}`);
|
|
305
|
+
return value;
|
|
306
|
+
};
|
|
307
|
+
const destination = resolve(outputPath);
|
|
308
|
+
prepareDerivedParent(corpusRoot, destination);
|
|
309
|
+
const report = await aggregateEvaluation({
|
|
310
|
+
lineage: {
|
|
311
|
+
requestDigest: `sha256:${digestMetadata(request).value}`,
|
|
312
|
+
corpusIndexDigest: `sha256:${digestMetadata(corpusEntries).value}`,
|
|
313
|
+
},
|
|
314
|
+
corpusEntries,
|
|
315
|
+
observationSets: source.observationSets.map(({ bundleRoot, path }) => ({
|
|
316
|
+
bundleRoot: resolve(base, bundleRoot),
|
|
317
|
+
document: resolveJson(path, "Structural observation set"),
|
|
318
|
+
})),
|
|
319
|
+
assertions: source.assertions.map(({ bundleRoot, path }) => ({
|
|
320
|
+
bundleRoot: resolve(base, bundleRoot),
|
|
321
|
+
document: resolveJson(path, "Behavior assertion"),
|
|
322
|
+
})),
|
|
323
|
+
calibrations: source.calibrations.map(({ selection, history }) => ({
|
|
324
|
+
selection: resolveJson(selection, "Review sample"),
|
|
325
|
+
history: resolveJson(history, "Review history"),
|
|
326
|
+
})),
|
|
327
|
+
comparisons: request.comparisons.map(({ eligibilityGates, ...comparison }) => ({
|
|
328
|
+
...comparison,
|
|
329
|
+
eligibility: eligibilityGates.map(({ request: comparisonRequest, report }) => ({
|
|
330
|
+
request: resolveJson(comparisonRequest, "Comparison request"),
|
|
331
|
+
report: resolveJson(report, "Comparison report"),
|
|
332
|
+
})),
|
|
333
|
+
})),
|
|
334
|
+
}, request);
|
|
335
|
+
await writeMetadataAtomically(dirname(destination), basename(destination), report, undefined, { overwrite: false });
|
|
336
|
+
write(`Built ${report.groups.length} aggregate group(s) and ${report.comparisons.length} matched comparison(s).\n`);
|
|
337
|
+
return 0;
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
write(`${errorMessage(error)}\n`);
|
|
341
|
+
return 1;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
async function runCalibrationCommand(args, write) {
|
|
345
|
+
const [command, first, second, third] = args;
|
|
346
|
+
try {
|
|
347
|
+
if (command === "sample" && first !== undefined && second !== undefined && third !== undefined && args.length === 4) {
|
|
348
|
+
const sources = readJson(first);
|
|
349
|
+
assertCalibrationDestination(sources.sources.map(({ bundleRoot }) => bundleRoot), third);
|
|
350
|
+
const sample = await selectReviewSample(sources, readJson(second));
|
|
351
|
+
await writeMetadataAtomically(dirname(resolve(third)), basename(third), sample, undefined, { overwrite: false });
|
|
352
|
+
write(`Selected ${sample.candidates.length} of ${sample.population.eligibleAssertionIds.length} eligible behavior assertions.\n`);
|
|
353
|
+
return 0;
|
|
354
|
+
}
|
|
355
|
+
if (command === "packet" && first !== undefined && second !== undefined && args.length === 3) {
|
|
356
|
+
const sample = readJson(first);
|
|
357
|
+
await writeReviewPacket(sample, second);
|
|
358
|
+
write(`Created local review packet for ${sample.candidates.length} assertion(s).\n`);
|
|
359
|
+
return 0;
|
|
360
|
+
}
|
|
361
|
+
if (command === "inspect" && first !== undefined && second !== undefined && args.length >= 3 && args.length <= 4) {
|
|
362
|
+
const packet = readJson(first);
|
|
363
|
+
const errors = validateArtifact(first, packet);
|
|
364
|
+
if (errors.length > 0)
|
|
365
|
+
throw new Error(errors.map(({ field, message }) => `${field}: ${message}`).join("\n"));
|
|
366
|
+
const items = packet.items ?? [];
|
|
367
|
+
const matching = items.filter(({ assertionBinding }) => matchesAssertionSelector(assertionBinding, second));
|
|
368
|
+
if (matching.length !== 1)
|
|
369
|
+
throw new Error(matching.length === 0
|
|
370
|
+
? `Review packet has no assertion "${second}".` : `Review packet assertion "${second}" is ambiguous; append @<digest>.`);
|
|
371
|
+
const item = matching[0];
|
|
372
|
+
const value = third === undefined ? item : item.citations?.find(({ eventId }) => eventId === third);
|
|
373
|
+
if (value === undefined)
|
|
374
|
+
throw new Error(`Review assertion "${second}" has no citation "${third}".`);
|
|
375
|
+
write(`${canonicalizeMetadata(value)}\n`);
|
|
376
|
+
return 0;
|
|
377
|
+
}
|
|
378
|
+
if (command === "binding" && first !== undefined && second !== undefined && args.length >= 3 && args.length <= 4) {
|
|
379
|
+
const selection = readJson(first);
|
|
380
|
+
await revalidateReviewSample(selection);
|
|
381
|
+
const matching = selection.candidates.filter(({ assertion }) => matchesAssertionSelector(assertion, second));
|
|
382
|
+
if (matching.length !== 1)
|
|
383
|
+
throw new Error(matching.length === 0
|
|
384
|
+
? `Review sample has no assertion "${second}".` : `Review sample assertion "${second}" is ambiguous; append @<digest>.`);
|
|
385
|
+
const candidate = matching[0];
|
|
386
|
+
const history = third === undefined ? undefined : readJson(third);
|
|
387
|
+
if (history !== undefined)
|
|
388
|
+
validateReviewHistory(selection, history);
|
|
389
|
+
write(`${canonicalizeMetadata({
|
|
390
|
+
assertion: candidate.assertion,
|
|
391
|
+
previousHistory: history === undefined || history.decisions.length === 0 ? null : {
|
|
392
|
+
schemaVersion: history.schemaVersion,
|
|
393
|
+
digest: `sha256:${digestMetadata(history).value}`,
|
|
394
|
+
},
|
|
395
|
+
})}\n`);
|
|
396
|
+
return 0;
|
|
397
|
+
}
|
|
398
|
+
if ((command === "import" || command === "adjudicate") && first !== undefined && second !== undefined && third !== undefined && args.length === 4) {
|
|
399
|
+
const decision = readJson(third);
|
|
400
|
+
const expectedKind = command === "adjudicate" ? "adjudication" : "review";
|
|
401
|
+
if (decision.kind !== expectedKind)
|
|
402
|
+
throw new Error(`The calibration ${command} command requires a ${expectedKind} decision.`);
|
|
403
|
+
const result = await importReviewDecision(readJson(first), second, decision);
|
|
404
|
+
write(`${result.appended ? "Appended" : "Already imported"} human ${command === "adjudicate" ? "adjudication" : "review"} decision.\n`);
|
|
405
|
+
return 0;
|
|
406
|
+
}
|
|
407
|
+
if (command === "summarize" && first !== undefined && second !== undefined && third !== undefined && args.length === 4) {
|
|
408
|
+
const selection = readJson(first);
|
|
409
|
+
assertCalibrationDestination(selection.sources.sources.map(({ bundleRoot }) => bundleRoot), third);
|
|
410
|
+
const summary = await summarizeCalibration(selection, readJson(second));
|
|
411
|
+
await writeMetadataAtomically(dirname(resolve(third)), basename(third), summary, undefined, { overwrite: false });
|
|
412
|
+
write(`Summarized ${summary.totals.selectedAssertions} selected assertion(s); confirmed=${summary.totals.confirmedEligibleAssertions}, unresolved=${summary.totals.unresolvedAssertions}.\n`);
|
|
413
|
+
return 0;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
catch (error) {
|
|
417
|
+
write(`${errorMessage(error)}\n`);
|
|
418
|
+
return 1;
|
|
419
|
+
}
|
|
420
|
+
write("Usage: ebo calibration <sample|packet|inspect|binding|import|adjudicate|summarize> ...\n");
|
|
421
|
+
return 1;
|
|
422
|
+
}
|
|
423
|
+
function matchesAssertionSelector(assertion, selector) {
|
|
424
|
+
if (assertion === undefined)
|
|
425
|
+
return false;
|
|
426
|
+
const separator = selector.lastIndexOf("@sha256:");
|
|
427
|
+
return separator < 0 ? assertion.id === selector
|
|
428
|
+
: assertion.id === selector.slice(0, separator) && assertion.digest === selector.slice(separator + 1);
|
|
429
|
+
}
|
|
430
|
+
function runCorpusCommand(args, write) {
|
|
431
|
+
const [command, first, second, third] = args;
|
|
432
|
+
try {
|
|
433
|
+
if (command === "build" && first !== undefined && second !== undefined && args.length === 3) {
|
|
434
|
+
const entries = buildCorpusIndex(first);
|
|
435
|
+
writeCorpusIndex(second, entries);
|
|
436
|
+
write(`Built ${entries.length} corpus index entr${entries.length === 1 ? "y" : "ies"}.\n`);
|
|
437
|
+
return 0;
|
|
438
|
+
}
|
|
439
|
+
if (command === "query" && first !== undefined) {
|
|
440
|
+
const query = parseCorpusQuery(args.slice(2));
|
|
441
|
+
for (const entry of queryCorpusIndex(readCorpusIndex(first), query))
|
|
442
|
+
write(`${canonicalizeMetadata(entry)}\n`);
|
|
443
|
+
return 0;
|
|
444
|
+
}
|
|
445
|
+
if (command === "validate" && first !== undefined && second !== undefined && args.length === 3) {
|
|
446
|
+
const issues = validateCorpusIndex(first, readCorpusIndex(second));
|
|
447
|
+
for (const issue of issues)
|
|
448
|
+
write(`${issue.manifestPath} [${issue.kind}]: ${issue.message}\n`);
|
|
449
|
+
if (issues.length > 0)
|
|
450
|
+
return 1;
|
|
451
|
+
write("Corpus index is current and all indexed evidence is reachable.\n");
|
|
452
|
+
return 0;
|
|
453
|
+
}
|
|
454
|
+
if (command === "pack" && first !== undefined && second !== undefined && third !== undefined && args.length === 4) {
|
|
455
|
+
return packPortableExport(first, third, readJson(second)).then((digest) => {
|
|
456
|
+
write(`Packed portable export (${digest}).\n`);
|
|
457
|
+
return 0;
|
|
458
|
+
}, (error) => {
|
|
459
|
+
write(`${errorMessage(error)}\n`);
|
|
460
|
+
return 1;
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
if (command === "unpack" && first !== undefined && second !== undefined && args.length === 3) {
|
|
464
|
+
const manifest = unpackPortableExport(first, second);
|
|
465
|
+
write(`Unpacked portable export ${manifest.bundleId} (${manifest.artifacts.length} artifacts).\n`);
|
|
466
|
+
return 0;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
write(`${errorMessage(error)}\n`);
|
|
471
|
+
return 1;
|
|
472
|
+
}
|
|
473
|
+
write("Usage: ebo corpus <build|query|validate|pack|unpack> ...\n");
|
|
474
|
+
return 1;
|
|
475
|
+
}
|
|
476
|
+
function parseCorpusQuery(args) {
|
|
477
|
+
const fields = {
|
|
478
|
+
"--kind": "manifestKind",
|
|
479
|
+
"--run": "runId",
|
|
480
|
+
"--trial": "runId",
|
|
481
|
+
"--attempt": "attemptId",
|
|
482
|
+
"--task": "taskId",
|
|
483
|
+
"--model": "modelId",
|
|
484
|
+
"--harness": "harnessId",
|
|
485
|
+
"--assessment-mode": "assessmentMode",
|
|
486
|
+
"--terminal": "terminalState",
|
|
487
|
+
"--failure-class": "failureClass",
|
|
488
|
+
"--verifier-status": "verifierStatus",
|
|
489
|
+
"--capture": "captureQualification",
|
|
490
|
+
"--export-status": "exportStatus",
|
|
491
|
+
"--sharing-class": "sharingClass",
|
|
492
|
+
};
|
|
493
|
+
const query = {};
|
|
494
|
+
for (let index = 0; index < args.length; index += 2) {
|
|
495
|
+
const field = fields[args[index]];
|
|
496
|
+
const value = args[index + 1];
|
|
497
|
+
if (field === undefined || value === undefined)
|
|
498
|
+
throw new Error("Corpus query filters require a supported flag and value.");
|
|
499
|
+
query[field] = value;
|
|
500
|
+
}
|
|
501
|
+
return query;
|
|
502
|
+
}
|
|
503
|
+
async function runObservationsCommand(args, write) {
|
|
504
|
+
const [command, first, second, third] = args;
|
|
505
|
+
try {
|
|
506
|
+
if (command === "create" && first !== undefined && second !== undefined && args.length === 3) {
|
|
507
|
+
assertDerivedDestination(first, second);
|
|
508
|
+
const report = await createRetainedStructuralObservationSet(first);
|
|
509
|
+
await writeObservationReport(second, report, first);
|
|
510
|
+
write(`Created ${report.observations.length} structural observations for attempt ${report.attemptId}.\n`);
|
|
511
|
+
return 0;
|
|
512
|
+
}
|
|
513
|
+
if (command === "corpus" && first !== undefined && second !== undefined && third !== undefined) {
|
|
514
|
+
assertDerivedDestination(first, third);
|
|
515
|
+
const index = readCorpusIndex(second);
|
|
516
|
+
const issues = validateCorpusIndex(first, index);
|
|
517
|
+
if (issues.length > 0)
|
|
518
|
+
throw new Error(`Corpus index is not current: ${issues[0].manifestPath} ${issues[0].message}`);
|
|
519
|
+
const selected = queryCorpusIndex(index, { ...parseCorpusQuery(args.slice(4)), manifestKind: "run" });
|
|
520
|
+
if (selected.length === 0)
|
|
521
|
+
throw new Error("Corpus selection matched no retained run bundles.");
|
|
522
|
+
const outputRoot = resolve(third);
|
|
523
|
+
const outputParent = prepareDerivedParent(first, outputRoot);
|
|
524
|
+
if (existsSync(outputRoot))
|
|
525
|
+
throw new Error("Structural observation corpus destination already exists.");
|
|
526
|
+
const stagingRoot = mkdtempSync(join(outputParent, ".ebo-observations-"));
|
|
527
|
+
let published = false;
|
|
528
|
+
try {
|
|
529
|
+
for (const entry of selected) {
|
|
530
|
+
if (entry.runId === undefined || entry.attemptId === undefined || entry.issues.length > 0)
|
|
531
|
+
throw new Error(`Corpus entry ${entry.manifestPath} is not observation-ready.`);
|
|
532
|
+
const bundleRoot = dirname(join(resolve(first), ...entry.manifestPath.split("/")));
|
|
533
|
+
const report = await createRetainedStructuralObservationSet(bundleRoot);
|
|
534
|
+
await writeObservationReport(join(stagingRoot, observationFileName(entry.runId, entry.attemptId)), report, first);
|
|
535
|
+
}
|
|
536
|
+
renameSync(stagingRoot, outputRoot);
|
|
537
|
+
published = true;
|
|
538
|
+
}
|
|
539
|
+
finally {
|
|
540
|
+
if (!published)
|
|
541
|
+
rmSync(stagingRoot, { recursive: true, force: true });
|
|
542
|
+
}
|
|
543
|
+
write(`Created structural observations for ${selected.length} corpus run bundle(s).\n`);
|
|
544
|
+
return 0;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
catch (error) {
|
|
548
|
+
write(`${errorMessage(error)}\n`);
|
|
549
|
+
return 1;
|
|
550
|
+
}
|
|
551
|
+
write("Usage: ebo observations <create|corpus> ...\n");
|
|
552
|
+
return 1;
|
|
553
|
+
}
|
|
554
|
+
async function writeObservationReport(path, report, sourceRoot) {
|
|
555
|
+
const destination = resolve(path);
|
|
556
|
+
prepareDerivedParent(sourceRoot, destination);
|
|
557
|
+
await writeMetadataAtomically(dirname(destination), basename(destination), report, undefined, { overwrite: false });
|
|
558
|
+
}
|
|
559
|
+
function prepareDerivedParent(sourceRoot, destination) {
|
|
560
|
+
const source = realpathSync(sourceRoot);
|
|
561
|
+
const parent = dirname(resolve(destination));
|
|
562
|
+
let existing = parent;
|
|
563
|
+
while (!existsSync(existing)) {
|
|
564
|
+
const next = dirname(existing);
|
|
565
|
+
if (next === existing)
|
|
566
|
+
break;
|
|
567
|
+
existing = next;
|
|
568
|
+
}
|
|
569
|
+
assertOutside(source, realpathSync(existing));
|
|
570
|
+
mkdirSync(parent, { recursive: true, mode: 0o700 });
|
|
571
|
+
const resolved = realpathSync(parent);
|
|
572
|
+
assertOutside(source, resolved);
|
|
573
|
+
return resolved;
|
|
574
|
+
}
|
|
575
|
+
function assertDerivedDestination(sourceRoot, destination) {
|
|
576
|
+
const source = resolve(sourceRoot);
|
|
577
|
+
const output = resolve(destination);
|
|
578
|
+
assertOutside(source, output);
|
|
579
|
+
}
|
|
580
|
+
function assertOutside(source, output) {
|
|
581
|
+
const locator = relative(source, output);
|
|
582
|
+
if (locator === "" || locator !== ".." && !locator.startsWith(`..${sep}`)) {
|
|
583
|
+
throw new Error("Structural observations must be written outside immutable source evidence.");
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
function observationFileName(runId, attemptId) {
|
|
587
|
+
return `sha256-${createHash("sha256").update(JSON.stringify([runId, attemptId])).digest("hex")}.json`;
|
|
588
|
+
}
|
|
589
|
+
async function runAgentSdkCommand(args, write) {
|
|
590
|
+
const agentSdkRunUsage = "Usage: ebo agent-sdk run <bundle-root> <queue.json> <run-id> <output-root> [--workspace-root <path>]\n";
|
|
591
|
+
const positional = [];
|
|
592
|
+
let workspaceRoot;
|
|
593
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
594
|
+
if (args[index] === "--workspace-root") {
|
|
595
|
+
const value = args[++index];
|
|
596
|
+
if (value === undefined || value.startsWith("--")) {
|
|
597
|
+
write(agentSdkRunUsage);
|
|
598
|
+
return 1;
|
|
599
|
+
}
|
|
600
|
+
workspaceRoot = value;
|
|
601
|
+
}
|
|
602
|
+
else
|
|
603
|
+
positional.push(args[index]);
|
|
604
|
+
}
|
|
605
|
+
const [bundleRoot, queuePath, runId, outputRoot] = positional;
|
|
606
|
+
if (bundleRoot === undefined || queuePath === undefined || runId === undefined
|
|
607
|
+
|| outputRoot === undefined || positional.length !== 4) {
|
|
608
|
+
write(agentSdkRunUsage);
|
|
609
|
+
return 1;
|
|
610
|
+
}
|
|
611
|
+
try {
|
|
612
|
+
const summary = await runAgentSdkQueueEntry({
|
|
613
|
+
bundleRoot,
|
|
614
|
+
queuePath,
|
|
615
|
+
runId,
|
|
616
|
+
outputRoot,
|
|
617
|
+
...(workspaceRoot === undefined ? {} : { workspaceRoot }),
|
|
618
|
+
});
|
|
619
|
+
write(`${canonicalizeMetadata(summary)}\n`);
|
|
620
|
+
return 0;
|
|
621
|
+
}
|
|
622
|
+
catch (error) {
|
|
623
|
+
write(`${errorMessage(error)}\n`);
|
|
624
|
+
return 1;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
async function runCodexCommand(args, write) {
|
|
628
|
+
const commandUsage = "Usage: ebo codex run <bundle-root> <queue.json> <run-id> <output-root> [--workspace-root <path>]\n";
|
|
629
|
+
const positional = [];
|
|
630
|
+
let workspaceRoot;
|
|
631
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
632
|
+
if (args[index] === "--workspace-root") {
|
|
633
|
+
const value = args[++index];
|
|
634
|
+
if (value === undefined || value.startsWith("--")) {
|
|
635
|
+
write(commandUsage);
|
|
636
|
+
return 1;
|
|
637
|
+
}
|
|
638
|
+
workspaceRoot = value;
|
|
639
|
+
}
|
|
640
|
+
else
|
|
641
|
+
positional.push(args[index]);
|
|
642
|
+
}
|
|
643
|
+
const [bundleRoot, queuePath, runId, outputRoot] = positional;
|
|
644
|
+
if (bundleRoot === undefined || queuePath === undefined || runId === undefined
|
|
645
|
+
|| outputRoot === undefined || positional.length !== 4) {
|
|
646
|
+
write(commandUsage);
|
|
647
|
+
return 1;
|
|
648
|
+
}
|
|
649
|
+
const controller = new AbortController();
|
|
650
|
+
const abort = () => controller.abort();
|
|
651
|
+
process.on("SIGINT", abort);
|
|
652
|
+
process.on("SIGTERM", abort);
|
|
653
|
+
try {
|
|
654
|
+
const summary = await runCodexQueueEntry({
|
|
655
|
+
bundleRoot,
|
|
656
|
+
queuePath,
|
|
657
|
+
runId,
|
|
658
|
+
outputRoot,
|
|
659
|
+
signal: controller.signal,
|
|
660
|
+
...(workspaceRoot === undefined ? {} : { workspaceRoot }),
|
|
661
|
+
});
|
|
662
|
+
write(`${canonicalizeMetadata(summary)}\n`);
|
|
663
|
+
return 0;
|
|
664
|
+
}
|
|
665
|
+
catch (error) {
|
|
666
|
+
write(`${errorMessage(error)}\n`);
|
|
667
|
+
return 1;
|
|
668
|
+
}
|
|
669
|
+
finally {
|
|
670
|
+
process.off("SIGINT", abort);
|
|
671
|
+
process.off("SIGTERM", abort);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
async function runCursorSdkCommand(args, write) {
|
|
675
|
+
const commandUsage = "Usage: ebo cursor run <bundle-root> <queue.json> <run-id> <output-root> [--workspace-root <path>]\n";
|
|
676
|
+
const positional = [];
|
|
677
|
+
let workspaceRoot;
|
|
678
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
679
|
+
if (args[index] === "--workspace-root") {
|
|
680
|
+
const value = args[++index];
|
|
681
|
+
if (value === undefined || value.startsWith("--")) {
|
|
682
|
+
write(commandUsage);
|
|
683
|
+
return 1;
|
|
684
|
+
}
|
|
685
|
+
workspaceRoot = value;
|
|
686
|
+
}
|
|
687
|
+
else
|
|
688
|
+
positional.push(args[index]);
|
|
689
|
+
}
|
|
690
|
+
const [bundleRoot, queuePath, runId, outputRoot] = positional;
|
|
691
|
+
if (bundleRoot === undefined || queuePath === undefined || runId === undefined
|
|
692
|
+
|| outputRoot === undefined || positional.length !== 4) {
|
|
693
|
+
write(commandUsage);
|
|
694
|
+
return 1;
|
|
695
|
+
}
|
|
696
|
+
const controller = new AbortController();
|
|
697
|
+
const abort = () => controller.abort();
|
|
698
|
+
process.on("SIGINT", abort);
|
|
699
|
+
process.on("SIGTERM", abort);
|
|
700
|
+
try {
|
|
701
|
+
const summary = await runCursorSdkQueueEntry({
|
|
702
|
+
bundleRoot,
|
|
703
|
+
queuePath,
|
|
704
|
+
runId,
|
|
705
|
+
outputRoot,
|
|
706
|
+
signal: controller.signal,
|
|
707
|
+
...(workspaceRoot === undefined ? {} : { workspaceRoot }),
|
|
708
|
+
});
|
|
709
|
+
write(`${canonicalizeMetadata(summary)}\n`);
|
|
710
|
+
return 0;
|
|
711
|
+
}
|
|
712
|
+
catch (error) {
|
|
713
|
+
write(`${errorMessage(error)}\n`);
|
|
714
|
+
return 1;
|
|
715
|
+
}
|
|
716
|
+
finally {
|
|
717
|
+
process.off("SIGINT", abort);
|
|
718
|
+
process.off("SIGTERM", abort);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
async function runPiCommand(args, write) {
|
|
722
|
+
const commandUsage = "Usage: ebo pi run <bundle-root> <queue.json> <run-id> <output-root> [--workspace-root <path>]\n";
|
|
723
|
+
const positional = [];
|
|
724
|
+
let workspaceRoot;
|
|
725
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
726
|
+
if (args[index] === "--workspace-root") {
|
|
727
|
+
const value = args[++index];
|
|
728
|
+
if (value === undefined || value.startsWith("--")) {
|
|
729
|
+
write(commandUsage);
|
|
730
|
+
return 1;
|
|
731
|
+
}
|
|
732
|
+
workspaceRoot = value;
|
|
733
|
+
}
|
|
734
|
+
else
|
|
735
|
+
positional.push(args[index]);
|
|
736
|
+
}
|
|
737
|
+
const [bundleRoot, queuePath, runId, outputRoot] = positional;
|
|
738
|
+
if (bundleRoot === undefined || queuePath === undefined || runId === undefined
|
|
739
|
+
|| outputRoot === undefined || positional.length !== 4) {
|
|
740
|
+
write(commandUsage);
|
|
741
|
+
return 1;
|
|
742
|
+
}
|
|
743
|
+
const controller = new AbortController();
|
|
744
|
+
const abort = () => controller.abort();
|
|
745
|
+
process.on("SIGINT", abort);
|
|
746
|
+
process.on("SIGTERM", abort);
|
|
747
|
+
try {
|
|
748
|
+
const summary = await runPiQueueEntry({
|
|
749
|
+
bundleRoot,
|
|
750
|
+
queuePath,
|
|
751
|
+
runId,
|
|
752
|
+
outputRoot,
|
|
753
|
+
signal: controller.signal,
|
|
754
|
+
...(workspaceRoot === undefined ? {} : { workspaceRoot }),
|
|
755
|
+
});
|
|
756
|
+
write(`${canonicalizeMetadata(summary)}\n`);
|
|
757
|
+
return 0;
|
|
758
|
+
}
|
|
759
|
+
catch (error) {
|
|
760
|
+
write(`${errorMessage(error)}\n`);
|
|
761
|
+
return 1;
|
|
762
|
+
}
|
|
763
|
+
finally {
|
|
764
|
+
process.off("SIGINT", abort);
|
|
765
|
+
process.off("SIGTERM", abort);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
async function createExportCommand(args, write) {
|
|
769
|
+
const [sourceRoot, policyPath, exportRoot] = args;
|
|
770
|
+
if (sourceRoot === undefined || policyPath === undefined || exportRoot === undefined || args.length !== 3) {
|
|
771
|
+
write("Usage: ebo export create <run-bundle-root> <policy.json> <export-root>\n");
|
|
772
|
+
return 1;
|
|
773
|
+
}
|
|
774
|
+
try {
|
|
775
|
+
const manifest = await createPortableRunBundleExport({
|
|
776
|
+
sourceRoot,
|
|
777
|
+
destinationRoot: exportRoot,
|
|
778
|
+
policy: readJson(policyPath),
|
|
779
|
+
});
|
|
780
|
+
write(`Created ${manifest.sharingClass} portable export (${manifest.status}) for bundle ${manifest.bundleId} at ${exportRoot}.\n`);
|
|
781
|
+
return 0;
|
|
782
|
+
}
|
|
783
|
+
catch (error) {
|
|
784
|
+
write(`${errorMessage(error)}\n`);
|
|
785
|
+
return 1;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
function compileQueue(args, write) {
|
|
789
|
+
const positional = [];
|
|
790
|
+
let bundleRoot;
|
|
791
|
+
let outputPath;
|
|
792
|
+
const freezeLocators = {};
|
|
793
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
794
|
+
const argument = args[index];
|
|
795
|
+
if (argument === "--bundle-root") {
|
|
796
|
+
const value = args[++index];
|
|
797
|
+
if (value === undefined || value.startsWith("--")) {
|
|
798
|
+
write("Usage: ebo matrix compile <experiment.json> <bundle-root> <queue.json> [--freeze-locator <task-id>=<path>]\n");
|
|
799
|
+
return 1;
|
|
800
|
+
}
|
|
801
|
+
bundleRoot = value;
|
|
802
|
+
}
|
|
803
|
+
else if (argument === "--output") {
|
|
804
|
+
outputPath = args[++index];
|
|
805
|
+
}
|
|
806
|
+
else if (argument === "--freeze-locator") {
|
|
807
|
+
const value = args[++index];
|
|
808
|
+
const separator = value?.indexOf("=") ?? -1;
|
|
809
|
+
if (separator <= 0 || separator === value.length - 1) {
|
|
810
|
+
write("Usage: ebo matrix compile <experiment.json> <bundle-root> <queue.json> [--freeze-locator <task-id>=<path>]\n");
|
|
811
|
+
return 1;
|
|
812
|
+
}
|
|
813
|
+
freezeLocators[value.slice(0, separator)] = value.slice(separator + 1);
|
|
814
|
+
}
|
|
815
|
+
else {
|
|
816
|
+
positional.push(argument);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
const experimentPath = positional.shift();
|
|
820
|
+
if (experimentPath === undefined) {
|
|
821
|
+
write("Usage: ebo matrix compile <experiment.json> <bundle-root> <queue.json> [--freeze-locator <task-id>=<path>]\n");
|
|
822
|
+
return 1;
|
|
823
|
+
}
|
|
824
|
+
if (bundleRoot === undefined && positional.length === 2)
|
|
825
|
+
bundleRoot = positional.shift();
|
|
826
|
+
if (outputPath === undefined && positional.length === 1)
|
|
827
|
+
outputPath = positional.shift();
|
|
828
|
+
if (bundleRoot === undefined)
|
|
829
|
+
bundleRoot = dirname(experimentPath);
|
|
830
|
+
if (outputPath === undefined) {
|
|
831
|
+
write("Usage: ebo matrix compile <experiment.json> <bundle-root> <queue.json> [--freeze-locator <task-id>=<path>]\n");
|
|
832
|
+
return 1;
|
|
833
|
+
}
|
|
834
|
+
try {
|
|
835
|
+
const experiment = readJson(experimentPath);
|
|
836
|
+
const queue = compileRunQueue(experiment, {
|
|
837
|
+
bundleRoot,
|
|
838
|
+
...(Object.keys(freezeLocators).length === 0 ? {} : { freezeLocators }),
|
|
839
|
+
});
|
|
840
|
+
writeRunQueue(outputPath, queue);
|
|
841
|
+
write(`Compiled ${queue.entries.length} run entr${queue.entries.length === 1 ? "y" : "ies"} into ${outputPath}. Seed: ${queue.seed}.\n`);
|
|
842
|
+
return 0;
|
|
843
|
+
}
|
|
844
|
+
catch (error) {
|
|
845
|
+
write(`${errorMessage(error)}\n`);
|
|
846
|
+
return 1;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
function validateQueue(args, write) {
|
|
850
|
+
const positional = [];
|
|
851
|
+
let bundleRoot;
|
|
852
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
853
|
+
if (args[index] === "--bundle-root") {
|
|
854
|
+
const value = args[++index];
|
|
855
|
+
if (value === undefined || value.startsWith("--")) {
|
|
856
|
+
write("Usage: ebo queue validate <queue.json> [experiment.json] [--bundle-root <bundle-root>]\n");
|
|
857
|
+
return 1;
|
|
858
|
+
}
|
|
859
|
+
bundleRoot = value;
|
|
860
|
+
}
|
|
861
|
+
else
|
|
862
|
+
positional.push(args[index]);
|
|
863
|
+
}
|
|
864
|
+
const queuePath = positional[0];
|
|
865
|
+
if (queuePath === undefined || positional.length > 2) {
|
|
866
|
+
write("Usage: ebo queue validate <queue.json> [experiment.json] [--bundle-root <bundle-root>]\n");
|
|
867
|
+
return 1;
|
|
868
|
+
}
|
|
869
|
+
try {
|
|
870
|
+
const experiment = positional[1] === undefined ? undefined : readJson(positional[1]);
|
|
871
|
+
const queue = readRunQueue(queuePath, experiment, bundleRoot === undefined ? {} : { bundleRoot });
|
|
872
|
+
const entryCount = queue.entries.length;
|
|
873
|
+
write(`Validated run queue ${queuePath} (${entryCount} entr${entryCount === 1 ? "y" : "ies"}).\n`);
|
|
874
|
+
return 0;
|
|
875
|
+
}
|
|
876
|
+
catch (error) {
|
|
877
|
+
write(`${errorMessage(error)}\n`);
|
|
878
|
+
return 1;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
function readJson(path) {
|
|
882
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(readBoundedFile(path));
|
|
883
|
+
assertNoDuplicateJsonKeys(text);
|
|
884
|
+
return JSON.parse(text);
|
|
885
|
+
}
|
|
886
|
+
function errorMessage(error) {
|
|
887
|
+
return error instanceof Error ? error.message : "Unable to process run queue.";
|
|
888
|
+
}
|
|
889
|
+
function runTaskPacketCommand(args, write) {
|
|
890
|
+
const [command, bundleRoot, packetLocator, freezeLocator] = args;
|
|
891
|
+
if (command === undefined || bundleRoot === undefined || packetLocator === undefined
|
|
892
|
+
|| !["validate", "admit", "freeze", "status"].includes(command)) {
|
|
893
|
+
write(taskPacketUsage);
|
|
894
|
+
return 1;
|
|
895
|
+
}
|
|
896
|
+
try {
|
|
897
|
+
if (command === "validate") {
|
|
898
|
+
const inspection = inspectTaskPacket(bundleRoot, packetLocator);
|
|
899
|
+
if (inspection.errors.length > 0) {
|
|
900
|
+
write(`${formatErrors(inspection.errors)}\n`);
|
|
901
|
+
return 1;
|
|
902
|
+
}
|
|
903
|
+
write(`Validated task packet "${inspection.packet.id}".\n`);
|
|
904
|
+
return 0;
|
|
905
|
+
}
|
|
906
|
+
if (command === "admit") {
|
|
907
|
+
const inspection = admitTaskPacket(bundleRoot, packetLocator);
|
|
908
|
+
if (inspection.errors.length > 0) {
|
|
909
|
+
write(`${formatErrors(inspection.errors)}\n`);
|
|
910
|
+
return 1;
|
|
911
|
+
}
|
|
912
|
+
write(`Admitted task packet "${inspection.packet.id}".\n`);
|
|
913
|
+
return 0;
|
|
914
|
+
}
|
|
915
|
+
if (command === "freeze") {
|
|
916
|
+
const record = freezeTaskPacket(bundleRoot, packetLocator, freezeLocator);
|
|
917
|
+
write(`Frozen task packet "${record.packetId}" (aggregate ${record.aggregateDigest.algorithm}:${record.aggregateDigest.value}).\n`);
|
|
918
|
+
return 0;
|
|
919
|
+
}
|
|
920
|
+
const status = statusTaskPacket(bundleRoot, packetLocator, freezeLocator);
|
|
921
|
+
write(`Task packet status: ${status.status}\n`);
|
|
922
|
+
if (status.packetId !== null)
|
|
923
|
+
write(`Packet: ${status.packetId}\n`);
|
|
924
|
+
if (status.packetDigest !== null)
|
|
925
|
+
write(`Packet digest: ${status.packetDigest.algorithm}:${status.packetDigest.value}\n`);
|
|
926
|
+
if (status.aggregateDigest !== null)
|
|
927
|
+
write(`Aggregate digest: ${status.aggregateDigest.algorithm}:${status.aggregateDigest.value}\n`);
|
|
928
|
+
for (const mismatch of status.mismatches)
|
|
929
|
+
write(`Mismatch: ${mismatch}\n`);
|
|
930
|
+
if (status.errors.length > 0)
|
|
931
|
+
write(`${formatErrors(status.errors)}\n`);
|
|
932
|
+
return status.status === "frozen" ? 0 : 1;
|
|
933
|
+
}
|
|
934
|
+
catch (error) {
|
|
935
|
+
write(`${error instanceof Error ? error.message : "Task packet command failed."}\n`);
|
|
936
|
+
return 1;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
function isDirectExecution(entryPath = process.argv[1]) {
|
|
940
|
+
if (entryPath === undefined) {
|
|
941
|
+
return false;
|
|
942
|
+
}
|
|
943
|
+
try {
|
|
944
|
+
return import.meta.url === pathToFileURL(realpathSync(entryPath)).href;
|
|
945
|
+
}
|
|
946
|
+
catch {
|
|
947
|
+
return false;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
if (isDirectExecution()) {
|
|
951
|
+
Promise.resolve(main()).then((exitCode) => {
|
|
952
|
+
process.exitCode = exitCode;
|
|
953
|
+
}, (error) => {
|
|
954
|
+
process.stderr.write(`${errorMessage(error)}\n`);
|
|
955
|
+
process.exitCode = 1;
|
|
956
|
+
});
|
|
957
|
+
}
|