@brainbase-labs/cli 0.25.0-eng1209.1 → 0.25.0-eng1209.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -7
- package/dist/index.js +541 -112
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,21 +72,28 @@ synchronously. Schema v1 supports:
|
|
|
72
72
|
- `workspace_assertion`: file existence, absence, SHA-256, or content checks.
|
|
73
73
|
- `sandbox_command`: a bounded argv command rooted in the workspace or hidden
|
|
74
74
|
tests directory. Exit zero passes; any other exit code is a valid failed
|
|
75
|
-
verdict. Schema v1 allows
|
|
76
|
-
read-only assertions
|
|
75
|
+
verdict. Schema v1 allows up to 20 command evaluators and runs them after all
|
|
76
|
+
read-only assertions. Each command receives either the verified live workspace
|
|
77
|
+
for legacy read-only evaluation or its own isolated frozen workspace copy.
|
|
77
78
|
|
|
78
79
|
Valid failed verdicts still produce a successful evaluation phase. Invalid
|
|
79
|
-
specs, digest/path violations, missing environment,
|
|
80
|
-
and output-budget violations fail the phase.
|
|
80
|
+
specs, digest/path violations, missing environment, phase-budget exhaustion,
|
|
81
|
+
and output-budget violations fail the phase. Individual command launch failures,
|
|
82
|
+
timeouts, and forced terminations are recorded as errored evaluator results so
|
|
83
|
+
the remaining evaluators can still run. Results are written atomically
|
|
81
84
|
with mode `0600`, include the raw spec digest and checksummed evidence/log
|
|
82
85
|
input/output references with explicit `staging`, `workspace`, `tests`, or
|
|
83
86
|
`logs` roots, redact declared secret values from command logs, require the
|
|
84
87
|
result path to be `<logs_root>/result.json`, and treat a matching successful
|
|
85
88
|
phase result as authoritative on replay.
|
|
86
89
|
|
|
87
|
-
On any phase-level execution failure, MAS must tear down the task sandbox.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
On any phase-level execution failure, MAS must tear down the task sandbox.
|
|
91
|
+
Failed and timed-out commands clean up the process group and descendants the
|
|
92
|
+
CLI can observe. Successful evaluator commands receive the same cleanup so one
|
|
93
|
+
check cannot contaminate the next. Successful hydration setup commands may
|
|
94
|
+
intentionally leave services running for the agent turn; the runtime lifecycle
|
|
95
|
+
remains their final cleanup boundary. Isolated evaluator workspace copies omit
|
|
96
|
+
`.git` and unsafe absolute or escaping symlinks.
|
|
90
97
|
|
|
91
98
|
## Development
|
|
92
99
|
|
package/dist/index.js
CHANGED
|
@@ -36008,7 +36008,7 @@ function padStart(s, n) {
|
|
|
36008
36008
|
// package.json
|
|
36009
36009
|
var package_default = {
|
|
36010
36010
|
name: "@brainbase-labs/cli",
|
|
36011
|
-
version: "0.25.0-eng1209.
|
|
36011
|
+
version: "0.25.0-eng1209.10",
|
|
36012
36012
|
description: "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
|
|
36013
36013
|
type: "module",
|
|
36014
36014
|
bin: {
|
|
@@ -78485,13 +78485,19 @@ var MAX_TRAJECTORY_BYTES = 100 * 1024 * 1024;
|
|
|
78485
78485
|
var MAX_REMOTE_INPUT_BYTES = 2 * 1024 * 1024 * 1024;
|
|
78486
78486
|
var MAX_ARCHIVE_EXTRACTED_BYTES = 2 * 1024 * 1024 * 1024;
|
|
78487
78487
|
var MAX_ARCHIVE_SCAN_BYTES = 2 * 1024 * 1024 * 1024;
|
|
78488
|
+
var MAX_HYDRATE_COMMANDS = 150;
|
|
78489
|
+
var MAX_PHASE_RESULT_BYTES = 32 * 1024 * 1024;
|
|
78488
78490
|
var MAX_SANDBOX_COMMANDS = 20;
|
|
78489
|
-
var MAX_CRITERIA_PER_EVALUATOR =
|
|
78490
|
-
var MAX_CRITERIA_RESULT_BYTES = 1024 * 1024;
|
|
78491
|
+
var MAX_CRITERIA_PER_EVALUATOR = 200;
|
|
78492
|
+
var MAX_CRITERIA_RESULT_BYTES = 24 * 1024 * 1024;
|
|
78493
|
+
var MAX_CRITERIA_RESULT_PAYLOAD_BYTES = 20 * 1024 * 1024;
|
|
78491
78494
|
var MAX_CRITERION_EXPLANATION_LENGTH = 16384;
|
|
78492
78495
|
var MAX_CRITERION_EVIDENCE_IDS = 100;
|
|
78493
78496
|
var MAX_CRITERION_EVIDENCE_ID_LENGTH = 256;
|
|
78494
78497
|
var MAX_ALLOWED_EVIDENCE_IDS = 1e4;
|
|
78498
|
+
var COMMAND_PROCESS_MARKER_ENV = "BRAINBASE_BENCHMARK_COMMAND_MARKER";
|
|
78499
|
+
var COMMAND_PROCESS_POLL_MS = 10;
|
|
78500
|
+
var COMMAND_PROCESS_CLEANUP_MS = 500;
|
|
78495
78501
|
var RESERVED_WORKSPACE_PATHS = new Set([
|
|
78496
78502
|
".brainbase",
|
|
78497
78503
|
".git",
|
|
@@ -78658,7 +78664,7 @@ var TrajectoryEvidenceSchema = EvidenceFileSchema.extend({
|
|
|
78658
78664
|
var HydrateSpecSchema = BaseSpecSchema.extend({
|
|
78659
78665
|
phase: exports_external.literal("hydrate"),
|
|
78660
78666
|
materials: exports_external.array(MaterialSchema).max(1e4).default([]),
|
|
78661
|
-
setup_commands: exports_external.array(CommandSchema).max(
|
|
78667
|
+
setup_commands: exports_external.array(CommandSchema).max(MAX_HYDRATE_COMMANDS).default([])
|
|
78662
78668
|
}).strict();
|
|
78663
78669
|
var CandidateOutputSchema = exports_external.object({
|
|
78664
78670
|
id: IdSchema,
|
|
@@ -78712,6 +78718,20 @@ var BenchmarkSpecSchema = exports_external.discriminatedUnion("phase", [
|
|
|
78712
78718
|
HydrateSpecSchema,
|
|
78713
78719
|
EvaluateSpecSchema
|
|
78714
78720
|
]).superRefine((value, context) => {
|
|
78721
|
+
if (Object.hasOwn(value.environment, COMMAND_PROCESS_MARKER_ENV)) {
|
|
78722
|
+
context.addIssue({
|
|
78723
|
+
code: exports_external.ZodIssueCode.custom,
|
|
78724
|
+
path: ["environment", COMMAND_PROCESS_MARKER_ENV],
|
|
78725
|
+
message: `${COMMAND_PROCESS_MARKER_ENV} is reserved for command supervision`
|
|
78726
|
+
});
|
|
78727
|
+
}
|
|
78728
|
+
if (value.secret_env.includes(COMMAND_PROCESS_MARKER_ENV)) {
|
|
78729
|
+
context.addIssue({
|
|
78730
|
+
code: exports_external.ZodIssueCode.custom,
|
|
78731
|
+
path: ["secret_env"],
|
|
78732
|
+
message: `${COMMAND_PROCESS_MARKER_ENV} is reserved for command supervision`
|
|
78733
|
+
});
|
|
78734
|
+
}
|
|
78715
78735
|
for (const name of Object.keys(value.environment)) {
|
|
78716
78736
|
if (SENSITIVE_ENV_NAME_RE.test(name)) {
|
|
78717
78737
|
context.addIssue({
|
|
@@ -78785,12 +78805,12 @@ var BenchmarkSpecSchema = exports_external.discriminatedUnion("phase", [
|
|
|
78785
78805
|
{ items: value.candidate_outputs, path: "candidate_outputs" }
|
|
78786
78806
|
];
|
|
78787
78807
|
for (const { items, path: issuePath } of uniqueLists) {
|
|
78788
|
-
const ids = items.map((item) => item.id);
|
|
78808
|
+
const ids = items.map((item) => issuePath === "candidate_outputs" ? item.id.toLowerCase() : item.id);
|
|
78789
78809
|
if (new Set(ids).size !== ids.length) {
|
|
78790
78810
|
context.addIssue({
|
|
78791
78811
|
code: exports_external.ZodIssueCode.custom,
|
|
78792
78812
|
path: [issuePath],
|
|
78793
|
-
message: `${issuePath} ids must be unique`
|
|
78813
|
+
message: `${issuePath} ids must be unique${issuePath === "candidate_outputs" ? " ignoring case" : ""}`
|
|
78794
78814
|
});
|
|
78795
78815
|
}
|
|
78796
78816
|
}
|
|
@@ -78866,6 +78886,8 @@ var BENCHMARK_CAPABILITIES = {
|
|
|
78866
78886
|
],
|
|
78867
78887
|
limits: {
|
|
78868
78888
|
max_secret_bindings: 100,
|
|
78889
|
+
max_hydrate_commands: MAX_HYDRATE_COMMANDS,
|
|
78890
|
+
max_phase_result_bytes: MAX_PHASE_RESULT_BYTES,
|
|
78869
78891
|
max_evaluators: 1000,
|
|
78870
78892
|
max_sandbox_commands: MAX_SANDBOX_COMMANDS,
|
|
78871
78893
|
max_criteria_per_evaluator: MAX_CRITERIA_PER_EVALUATOR
|
|
@@ -78882,6 +78904,19 @@ class BenchmarkPhaseError extends Error {
|
|
|
78882
78904
|
this.name = "BenchmarkPhaseError";
|
|
78883
78905
|
}
|
|
78884
78906
|
}
|
|
78907
|
+
|
|
78908
|
+
class BenchmarkCommandExecutionError extends BenchmarkPhaseError {
|
|
78909
|
+
stdout;
|
|
78910
|
+
stderr;
|
|
78911
|
+
durationMs;
|
|
78912
|
+
constructor(code, message, stdout, stderr, durationMs) {
|
|
78913
|
+
super(code, message);
|
|
78914
|
+
this.stdout = stdout;
|
|
78915
|
+
this.stderr = stderr;
|
|
78916
|
+
this.durationMs = durationMs;
|
|
78917
|
+
this.name = "BenchmarkCommandExecutionError";
|
|
78918
|
+
}
|
|
78919
|
+
}
|
|
78885
78920
|
var ZIP_EOCD_SIGNATURE = 101010256;
|
|
78886
78921
|
var ZIP_CENTRAL_SIGNATURE = 33639248;
|
|
78887
78922
|
var ZIP_LOCAL_SIGNATURE = 67324752;
|
|
@@ -79114,10 +79149,15 @@ async function verifyRecordsUnchanged(records, spec) {
|
|
|
79114
79149
|
const relative = safeRelPath(record3.path);
|
|
79115
79150
|
assertNoSymlinkTraversal(root, relative);
|
|
79116
79151
|
const candidate = path88.resolve(root, relative);
|
|
79117
|
-
if (!isWithin(root, candidate)
|
|
79152
|
+
if (!isWithin(root, candidate)) {
|
|
79153
|
+
throw new BenchmarkPhaseError("evidence_tampered", `evidence was removed during evaluation: ${record3.root}:${record3.path}`);
|
|
79154
|
+
}
|
|
79155
|
+
let stat;
|
|
79156
|
+
try {
|
|
79157
|
+
stat = fs81.lstatSync(candidate);
|
|
79158
|
+
} catch {
|
|
79118
79159
|
throw new BenchmarkPhaseError("evidence_tampered", `evidence was removed during evaluation: ${record3.root}:${record3.path}`);
|
|
79119
79160
|
}
|
|
79120
|
-
const stat = fs81.lstatSync(candidate);
|
|
79121
79161
|
if (record3.kind === "symlink") {
|
|
79122
79162
|
const target = stat.isSymbolicLink() ? fs81.readlinkSync(candidate) : null;
|
|
79123
79163
|
if (target === null || Buffer.byteLength(target) !== record3.size || sha256(target) !== record3.sha256) {
|
|
@@ -79212,6 +79252,15 @@ function removeRemoteHydrationInputs(spec) {
|
|
|
79212
79252
|
removedSources.add(material.source);
|
|
79213
79253
|
}
|
|
79214
79254
|
}
|
|
79255
|
+
function removePrivateHydrationSourcesBeforeSetup(spec, context) {
|
|
79256
|
+
removeRemoteHydrationInputs(spec);
|
|
79257
|
+
for (const temporary of context.temporaryRoots) {
|
|
79258
|
+
fs81.rmSync(temporary, { recursive: true, force: true });
|
|
79259
|
+
}
|
|
79260
|
+
context.temporaryRoots.clear();
|
|
79261
|
+
context.verifiedInputs.clear();
|
|
79262
|
+
context.preparedArchiveFiles.clear();
|
|
79263
|
+
}
|
|
79215
79264
|
async function downloadInputReference(stagingRoot, input, context) {
|
|
79216
79265
|
const cacheKey = inputCacheKey(input);
|
|
79217
79266
|
const cached2 = context.verifiedInputs.get(cacheKey);
|
|
@@ -79893,7 +79942,7 @@ function buildEnvironment(spec, secretNames, additions = {}) {
|
|
|
79893
79942
|
Object.assign(env3, additions);
|
|
79894
79943
|
return env3;
|
|
79895
79944
|
}
|
|
79896
|
-
function redactCommandOutput(data, spec) {
|
|
79945
|
+
function redactCommandOutput(data, spec, additionalSecrets = []) {
|
|
79897
79946
|
let value = data.toString("utf8");
|
|
79898
79947
|
const sensitiveNames = new Set([
|
|
79899
79948
|
...spec.secret_env,
|
|
@@ -79904,6 +79953,10 @@ function redactCommandOutput(data, spec) {
|
|
|
79904
79953
|
if (secret)
|
|
79905
79954
|
value = value.split(secret).join("[REDACTED]");
|
|
79906
79955
|
}
|
|
79956
|
+
for (const secret of additionalSecrets) {
|
|
79957
|
+
if (secret)
|
|
79958
|
+
value = value.split(secret).join("[REDACTED]");
|
|
79959
|
+
}
|
|
79907
79960
|
return Buffer.from(value);
|
|
79908
79961
|
}
|
|
79909
79962
|
function descendantPids(parentPid) {
|
|
@@ -79955,7 +80008,99 @@ function terminate(child) {
|
|
|
79955
80008
|
child.kill("SIGKILL");
|
|
79956
80009
|
}
|
|
79957
80010
|
}
|
|
79958
|
-
|
|
80011
|
+
function markedProcessPids(marker) {
|
|
80012
|
+
const assignment = `${COMMAND_PROCESS_MARKER_ENV}=${marker}`;
|
|
80013
|
+
if (process.platform === "linux") {
|
|
80014
|
+
const matches2 = [];
|
|
80015
|
+
let entries;
|
|
80016
|
+
try {
|
|
80017
|
+
entries = fs81.readdirSync("/proc");
|
|
80018
|
+
} catch {
|
|
80019
|
+
return matches2;
|
|
80020
|
+
}
|
|
80021
|
+
for (const entry of entries) {
|
|
80022
|
+
if (!/^\d+$/.test(entry))
|
|
80023
|
+
continue;
|
|
80024
|
+
const pid = Number(entry);
|
|
80025
|
+
if (pid === process.pid)
|
|
80026
|
+
continue;
|
|
80027
|
+
try {
|
|
80028
|
+
const environment = fs81.readFileSync(path88.join("/proc", entry, "environ"), "utf8");
|
|
80029
|
+
if (environment.split("\x00").includes(assignment))
|
|
80030
|
+
matches2.push(pid);
|
|
80031
|
+
} catch {}
|
|
80032
|
+
}
|
|
80033
|
+
return matches2;
|
|
80034
|
+
}
|
|
80035
|
+
if (process.platform === "darwin") {
|
|
80036
|
+
try {
|
|
80037
|
+
const output = execFileSync2("ps", ["eww", "-axo", "pid=,command="], {
|
|
80038
|
+
encoding: "utf8",
|
|
80039
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
80040
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
80041
|
+
});
|
|
80042
|
+
const matches2 = [];
|
|
80043
|
+
for (const line of output.split(`
|
|
80044
|
+
`)) {
|
|
80045
|
+
const match = line.match(/^\s*(\d+)\s+(.*)$/);
|
|
80046
|
+
if (!match || !match[2].includes(assignment))
|
|
80047
|
+
continue;
|
|
80048
|
+
const pid = Number(match[1]);
|
|
80049
|
+
if (Number.isInteger(pid) && pid !== process.pid)
|
|
80050
|
+
matches2.push(pid);
|
|
80051
|
+
}
|
|
80052
|
+
return matches2;
|
|
80053
|
+
} catch {
|
|
80054
|
+
return [];
|
|
80055
|
+
}
|
|
80056
|
+
}
|
|
80057
|
+
return [];
|
|
80058
|
+
}
|
|
80059
|
+
function processExists(pid) {
|
|
80060
|
+
if (process.platform === "linux") {
|
|
80061
|
+
try {
|
|
80062
|
+
const stat = fs81.readFileSync(path88.join("/proc", String(pid), "stat"), "utf8");
|
|
80063
|
+
const commandEnd = stat.lastIndexOf(")");
|
|
80064
|
+
const state = commandEnd >= 0 ? stat.slice(commandEnd + 2, commandEnd + 3) : "";
|
|
80065
|
+
if (state === "Z" || state === "X")
|
|
80066
|
+
return false;
|
|
80067
|
+
} catch (error2) {
|
|
80068
|
+
if (error2.code === "ENOENT")
|
|
80069
|
+
return false;
|
|
80070
|
+
}
|
|
80071
|
+
}
|
|
80072
|
+
try {
|
|
80073
|
+
process.kill(pid, 0);
|
|
80074
|
+
return true;
|
|
80075
|
+
} catch (error2) {
|
|
80076
|
+
return error2.code !== "ESRCH";
|
|
80077
|
+
}
|
|
80078
|
+
}
|
|
80079
|
+
async function terminateCommandProcesses(child, marker, observedDescendants) {
|
|
80080
|
+
if (child.pid !== undefined) {
|
|
80081
|
+
for (const pid of descendantPids(child.pid))
|
|
80082
|
+
observedDescendants.add(pid);
|
|
80083
|
+
}
|
|
80084
|
+
terminate(child);
|
|
80085
|
+
const deadline = Date.now() + COMMAND_PROCESS_CLEANUP_MS;
|
|
80086
|
+
while (true) {
|
|
80087
|
+
for (const pid of markedProcessPids(marker))
|
|
80088
|
+
observedDescendants.add(pid);
|
|
80089
|
+
const active = [...observedDescendants].filter(processExists);
|
|
80090
|
+
for (const pid of active) {
|
|
80091
|
+
try {
|
|
80092
|
+
process.kill(pid, "SIGKILL");
|
|
80093
|
+
} catch {}
|
|
80094
|
+
}
|
|
80095
|
+
if (active.length === 0)
|
|
80096
|
+
return;
|
|
80097
|
+
if (Date.now() >= deadline) {
|
|
80098
|
+
throw new BenchmarkPhaseError("command_cleanup_failed", "command descendants remained after bounded cleanup");
|
|
80099
|
+
}
|
|
80100
|
+
await new Promise((resolve) => setTimeout(resolve, COMMAND_PROCESS_POLL_MS));
|
|
80101
|
+
}
|
|
80102
|
+
}
|
|
80103
|
+
async function runCommand(command, root, spec, context, options = {}) {
|
|
79959
80104
|
const cwdRel = normalizedRootRelative(command.cwd);
|
|
79960
80105
|
assertNoSymlinkTraversal(root, cwdRel);
|
|
79961
80106
|
const cwd2 = path88.resolve(root, cwdRel);
|
|
@@ -79971,29 +80116,59 @@ async function runCommand(command, root, spec, context, additions = {}) {
|
|
|
79971
80116
|
const remainingMs = context.deadline - Date.now();
|
|
79972
80117
|
if (remainingMs <= 0)
|
|
79973
80118
|
throw new BenchmarkPhaseError("phase_timeout", "phase budget expired");
|
|
79974
|
-
const
|
|
80119
|
+
const reachesPhaseDeadline = command.timeout_ms === undefined || command.timeout_ms >= remainingMs;
|
|
80120
|
+
const timeoutMs2 = reachesPhaseDeadline ? remainingMs : command.timeout_ms;
|
|
79975
80121
|
const started = Date.now();
|
|
80122
|
+
const commandMarker = crypto6.randomBytes(32).toString("hex");
|
|
79976
80123
|
return await new Promise((resolve, reject2) => {
|
|
79977
80124
|
const child = spawn4(command.argv[0], command.argv.slice(1), {
|
|
79978
80125
|
cwd: cwd2,
|
|
79979
|
-
env: buildEnvironment(spec, command.secret_env,
|
|
80126
|
+
env: buildEnvironment(spec, command.secret_env, {
|
|
80127
|
+
...options.additions,
|
|
80128
|
+
[COMMAND_PROCESS_MARKER_ENV]: commandMarker
|
|
80129
|
+
}),
|
|
79980
80130
|
stdio: ["ignore", "pipe", "pipe"],
|
|
79981
80131
|
detached: process.platform !== "win32"
|
|
79982
80132
|
});
|
|
79983
80133
|
const stdout = [];
|
|
79984
80134
|
const stderr = [];
|
|
80135
|
+
const observedDescendants = new Set;
|
|
79985
80136
|
let captured = 0;
|
|
79986
80137
|
let settled = false;
|
|
79987
80138
|
let timer;
|
|
80139
|
+
let observer;
|
|
80140
|
+
let cleanup;
|
|
80141
|
+
const observeDescendants = () => {
|
|
80142
|
+
if (child.pid === undefined)
|
|
80143
|
+
return;
|
|
80144
|
+
for (const pid of descendantPids(child.pid))
|
|
80145
|
+
observedDescendants.add(pid);
|
|
80146
|
+
};
|
|
80147
|
+
const cleanupProcesses = () => {
|
|
80148
|
+
if (observer)
|
|
80149
|
+
clearInterval(observer);
|
|
80150
|
+
cleanup ??= terminateCommandProcesses(child, commandMarker, observedDescendants);
|
|
80151
|
+
return cleanup;
|
|
80152
|
+
};
|
|
80153
|
+
const sanitizedOutput = (chunks) => redactCommandOutput(Buffer.concat(chunks), spec, [commandMarker]);
|
|
80154
|
+
const sanitizedError = (error2) => {
|
|
80155
|
+
if (!(error2 instanceof BenchmarkCommandExecutionError))
|
|
80156
|
+
return error2;
|
|
80157
|
+
return new BenchmarkCommandExecutionError(error2.code, error2.message, sanitizedOutput(stdout), sanitizedOutput(stderr), error2.durationMs);
|
|
80158
|
+
};
|
|
79988
80159
|
const fail = (error2) => {
|
|
79989
80160
|
if (settled)
|
|
79990
80161
|
return;
|
|
79991
80162
|
settled = true;
|
|
79992
80163
|
if (timer)
|
|
79993
80164
|
clearTimeout(timer);
|
|
79994
|
-
|
|
79995
|
-
reject2(error2);
|
|
80165
|
+
cleanupProcesses().then(() => reject2(sanitizedError(error2)), reject2);
|
|
79996
80166
|
};
|
|
80167
|
+
if (process.platform !== "linux") {
|
|
80168
|
+
observeDescendants();
|
|
80169
|
+
observer = setInterval(observeDescendants, COMMAND_PROCESS_POLL_MS);
|
|
80170
|
+
observer.unref();
|
|
80171
|
+
}
|
|
79997
80172
|
const capture = (target, chunk2) => {
|
|
79998
80173
|
if (settled)
|
|
79999
80174
|
return;
|
|
@@ -80005,29 +80180,48 @@ async function runCommand(command, root, spec, context, additions = {}) {
|
|
|
80005
80180
|
}
|
|
80006
80181
|
target.push(chunk2);
|
|
80007
80182
|
};
|
|
80183
|
+
const finish = (code, signal) => {
|
|
80184
|
+
if (observer)
|
|
80185
|
+
clearInterval(observer);
|
|
80186
|
+
if (code === null) {
|
|
80187
|
+
reject2(new BenchmarkCommandExecutionError("command_terminated", `command was terminated by ${signal ?? "an unknown signal"}: ${command.id}`, sanitizedOutput(stdout), sanitizedOutput(stderr), Date.now() - started));
|
|
80188
|
+
return;
|
|
80189
|
+
}
|
|
80190
|
+
resolve({
|
|
80191
|
+
exitCode: code,
|
|
80192
|
+
stdout: sanitizedOutput(stdout),
|
|
80193
|
+
stderr: sanitizedOutput(stderr),
|
|
80194
|
+
durationMs: Date.now() - started,
|
|
80195
|
+
redactions: [commandMarker]
|
|
80196
|
+
});
|
|
80197
|
+
};
|
|
80008
80198
|
child.stdout?.on("data", (chunk2) => capture(stdout, chunk2));
|
|
80009
80199
|
child.stderr?.on("data", (chunk2) => capture(stderr, chunk2));
|
|
80010
80200
|
child.on("error", (error2) => {
|
|
80011
|
-
fail(new
|
|
80201
|
+
fail(new BenchmarkCommandExecutionError("command_start_failed", `failed to start ${command.id}: ${error2.message}`, Buffer.concat(stdout), Buffer.concat(stderr), Date.now() - started));
|
|
80012
80202
|
});
|
|
80013
80203
|
timer = setTimeout(() => {
|
|
80014
|
-
|
|
80204
|
+
if (reachesPhaseDeadline) {
|
|
80205
|
+
fail(new BenchmarkPhaseError("phase_timeout", "phase budget expired"));
|
|
80206
|
+
return;
|
|
80207
|
+
}
|
|
80208
|
+
fail(new BenchmarkCommandExecutionError("command_timeout", `command timed out: ${command.id}`, Buffer.concat(stdout), Buffer.concat(stderr), Date.now() - started));
|
|
80015
80209
|
}, timeoutMs2);
|
|
80016
|
-
child.on("
|
|
80210
|
+
child.on("exit", (code, signal) => {
|
|
80017
80211
|
if (settled)
|
|
80018
80212
|
return;
|
|
80019
80213
|
settled = true;
|
|
80020
80214
|
clearTimeout(timer);
|
|
80021
|
-
if (code === null) {
|
|
80022
|
-
|
|
80023
|
-
|
|
80215
|
+
if (options.descendantCleanup === "always" || code === null || code !== 0) {
|
|
80216
|
+
cleanupProcesses().then(() => finish(code, signal), reject2);
|
|
80217
|
+
} else {
|
|
80218
|
+
if (observer)
|
|
80219
|
+
clearInterval(observer);
|
|
80220
|
+
child.unref();
|
|
80221
|
+
child.stdout?.unref?.();
|
|
80222
|
+
child.stderr?.unref?.();
|
|
80223
|
+
finish(code, signal);
|
|
80024
80224
|
}
|
|
80025
|
-
resolve({
|
|
80026
|
-
exitCode: code,
|
|
80027
|
-
stdout: Buffer.concat(stdout),
|
|
80028
|
-
stderr: Buffer.concat(stderr),
|
|
80029
|
-
durationMs: Date.now() - started
|
|
80030
|
-
});
|
|
80031
80225
|
});
|
|
80032
80226
|
});
|
|
80033
80227
|
}
|
|
@@ -80100,11 +80294,14 @@ async function executeHydrate(spec, context) {
|
|
|
80100
80294
|
throw error2;
|
|
80101
80295
|
}
|
|
80102
80296
|
}
|
|
80297
|
+
removePrivateHydrationSourcesBeforeSetup(spec, context);
|
|
80103
80298
|
for (const command of spec.setup_commands) {
|
|
80104
80299
|
assertBudget(context);
|
|
80105
80300
|
let result2;
|
|
80106
80301
|
try {
|
|
80107
|
-
result2 = await runCommand(command, spec.workspace_root, spec, context
|
|
80302
|
+
result2 = await runCommand(command, spec.workspace_root, spec, context, {
|
|
80303
|
+
descendantCleanup: "failure_only"
|
|
80304
|
+
});
|
|
80108
80305
|
} catch (error2) {
|
|
80109
80306
|
context.steps.push({
|
|
80110
80307
|
id: command.id,
|
|
@@ -80155,7 +80352,6 @@ async function executeHydrate(spec, context) {
|
|
|
80155
80352
|
}
|
|
80156
80353
|
outputs.splice(0, outputs.length, ...finalOutputs);
|
|
80157
80354
|
context.outputs.splice(0, context.outputs.length, ...finalOutputs);
|
|
80158
|
-
removeRemoteHydrationInputs(spec);
|
|
80159
80355
|
assertBudget(context);
|
|
80160
80356
|
return outputs;
|
|
80161
80357
|
}
|
|
@@ -80220,6 +80416,49 @@ async function workspaceManifest(spec, context) {
|
|
|
80220
80416
|
}
|
|
80221
80417
|
return records.sort((a3, b4) => a3.path.localeCompare(b4.path));
|
|
80222
80418
|
}
|
|
80419
|
+
async function verifyWorkspaceManifestUnchanged(expected, spec, context) {
|
|
80420
|
+
await verifyRecordsUnchanged(expected, spec);
|
|
80421
|
+
const actual = await workspaceManifest(spec, context);
|
|
80422
|
+
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
|
|
80423
|
+
throw new BenchmarkPhaseError("evidence_tampered", "candidate workspace changed during read-only evaluation");
|
|
80424
|
+
}
|
|
80425
|
+
}
|
|
80426
|
+
function treeBytes(root, context) {
|
|
80427
|
+
let totalBytes = 0;
|
|
80428
|
+
const stack = [path88.resolve(root)];
|
|
80429
|
+
while (stack.length > 0) {
|
|
80430
|
+
const directory = stack.pop();
|
|
80431
|
+
const entries = fs81.readdirSync(directory, { withFileTypes: true });
|
|
80432
|
+
for (const entry of entries) {
|
|
80433
|
+
assertBudget(context);
|
|
80434
|
+
const candidate = path88.join(directory, entry.name);
|
|
80435
|
+
const stat = fs81.lstatSync(candidate);
|
|
80436
|
+
if (stat.isDirectory()) {
|
|
80437
|
+
stack.push(candidate);
|
|
80438
|
+
continue;
|
|
80439
|
+
}
|
|
80440
|
+
if (stat.isFile()) {
|
|
80441
|
+
totalBytes += stat.size;
|
|
80442
|
+
continue;
|
|
80443
|
+
}
|
|
80444
|
+
if (stat.isSymbolicLink()) {
|
|
80445
|
+
totalBytes += Buffer.byteLength(fs81.readlinkSync(candidate));
|
|
80446
|
+
continue;
|
|
80447
|
+
}
|
|
80448
|
+
throw new BenchmarkPhaseError("unsafe_path", `evaluate output contains an unsupported filesystem entry: ${path88.relative(root, candidate)}`);
|
|
80449
|
+
}
|
|
80450
|
+
}
|
|
80451
|
+
return totalBytes;
|
|
80452
|
+
}
|
|
80453
|
+
function assertEvaluateOutputBudget(spec, context, additionalBytes = 0) {
|
|
80454
|
+
const actual = treeBytes(spec.logs_root, context) + additionalBytes;
|
|
80455
|
+
if (actual > spec.workspace_limits.max_total_bytes) {
|
|
80456
|
+
throw new BenchmarkPhaseError("workspace_limit_exceeded", "evaluate output bytes exceed the workspace byte limit", {
|
|
80457
|
+
actual,
|
|
80458
|
+
max_total_bytes: spec.workspace_limits.max_total_bytes
|
|
80459
|
+
});
|
|
80460
|
+
}
|
|
80461
|
+
}
|
|
80223
80462
|
function candidateGlob(pattern) {
|
|
80224
80463
|
let source = "^";
|
|
80225
80464
|
for (let index = 0;index < pattern.length; index += 1) {
|
|
@@ -80250,6 +80489,8 @@ function manifestDirectories(manifest, context) {
|
|
|
80250
80489
|
const directories = new Set;
|
|
80251
80490
|
for (const entry of manifest) {
|
|
80252
80491
|
assertBudget(context);
|
|
80492
|
+
if (entry.kind === "symlink")
|
|
80493
|
+
continue;
|
|
80253
80494
|
let current = path88.posix.dirname(entry.path);
|
|
80254
80495
|
while (current !== ".") {
|
|
80255
80496
|
assertBudget(context);
|
|
@@ -80267,9 +80508,8 @@ function candidateOutputFiles(output, manifest, context) {
|
|
|
80267
80508
|
assertBudget(context);
|
|
80268
80509
|
if (!matcher.test(entry.path))
|
|
80269
80510
|
continue;
|
|
80270
|
-
if (entry.kind === "symlink")
|
|
80271
|
-
|
|
80272
|
-
}
|
|
80511
|
+
if (entry.kind === "symlink")
|
|
80512
|
+
continue;
|
|
80273
80513
|
matched.push(entry);
|
|
80274
80514
|
}
|
|
80275
80515
|
return {
|
|
@@ -80277,12 +80517,6 @@ function candidateOutputFiles(output, manifest, context) {
|
|
|
80277
80517
|
files: matched
|
|
80278
80518
|
};
|
|
80279
80519
|
}
|
|
80280
|
-
for (const entry of manifest) {
|
|
80281
|
-
assertBudget(context);
|
|
80282
|
-
if (entry.kind === "symlink" && matcher.test(entry.path)) {
|
|
80283
|
-
throw new BenchmarkPhaseError("unsafe_path", `candidate output ${output.id} matches a symlink`);
|
|
80284
|
-
}
|
|
80285
|
-
}
|
|
80286
80520
|
const directories = [];
|
|
80287
80521
|
for (const directory of manifestDirectories(manifest, context)) {
|
|
80288
80522
|
assertBudget(context);
|
|
@@ -80297,9 +80531,8 @@ function candidateOutputFiles(output, manifest, context) {
|
|
|
80297
80531
|
assertBudget(context);
|
|
80298
80532
|
if (!entry.path.startsWith(prefix))
|
|
80299
80533
|
continue;
|
|
80300
|
-
if (entry.kind === "symlink")
|
|
80301
|
-
|
|
80302
|
-
}
|
|
80534
|
+
if (entry.kind === "symlink")
|
|
80535
|
+
continue;
|
|
80303
80536
|
selected.set(entry.path, entry);
|
|
80304
80537
|
}
|
|
80305
80538
|
}
|
|
@@ -80332,7 +80565,7 @@ async function copyCandidateOutput(output, manifest, spec, context) {
|
|
|
80332
80565
|
for (const frozenFile of selected.files) {
|
|
80333
80566
|
assertBudget(context);
|
|
80334
80567
|
const source = path88.resolve(spec.workspace_root, safeRelPath(frozenFile.path));
|
|
80335
|
-
const destination = path88.resolve(spec.logs_root, "candidate-
|
|
80568
|
+
const destination = path88.resolve(spec.logs_root, "candidate-outputs", output.id, safeRelPath(frozenFile.path));
|
|
80336
80569
|
if (fs81.existsSync(destination)) {
|
|
80337
80570
|
throw new BenchmarkPhaseError("destination_conflict", `candidate output destination already exists: ${output.id}/${frozenFile.path}`);
|
|
80338
80571
|
}
|
|
@@ -80345,21 +80578,29 @@ async function copyCandidateOutput(output, manifest, spec, context) {
|
|
|
80345
80578
|
}
|
|
80346
80579
|
return copied;
|
|
80347
80580
|
}
|
|
80348
|
-
async function copyFrozenWorkspace(manifest, spec, context) {
|
|
80349
|
-
for (const entry of manifest) {
|
|
80350
|
-
assertBudget(context);
|
|
80351
|
-
if (entry.kind === "symlink") {
|
|
80352
|
-
throw new BenchmarkPhaseError("unsupported_workspace_symlink", `sandbox evaluator workspace cannot safely reproduce symlink: ${entry.path}`);
|
|
80353
|
-
}
|
|
80354
|
-
}
|
|
80581
|
+
async function copyFrozenWorkspace(manifest, spec, context, sourceRoot = spec.workspace_root, symlinkPolicy = "preserve") {
|
|
80355
80582
|
const destinationRoot = fs81.mkdtempSync(path88.join(os16.tmpdir(), "brainbase-benchmark-workspace-"));
|
|
80356
80583
|
fs81.chmodSync(destinationRoot, 448);
|
|
80357
80584
|
context.temporaryRoots.add(destinationRoot);
|
|
80358
80585
|
for (const frozenFile of manifest) {
|
|
80359
80586
|
assertBudget(context);
|
|
80360
|
-
const source = path88.resolve(
|
|
80587
|
+
const source = path88.resolve(sourceRoot, safeRelPath(frozenFile.path));
|
|
80361
80588
|
const destination = path88.resolve(destinationRoot, safeRelPath(frozenFile.path));
|
|
80362
|
-
|
|
80589
|
+
if (frozenFile.kind === "symlink") {
|
|
80590
|
+
const stat = fs81.lstatSync(source);
|
|
80591
|
+
const target = stat.isSymbolicLink() ? fs81.readlinkSync(source) : null;
|
|
80592
|
+
if (target === null || Buffer.byteLength(target) !== frozenFile.size || sha256(target) !== frozenFile.sha256) {
|
|
80593
|
+
throw new BenchmarkPhaseError("evidence_tampered", `workspace changed after it was frozen: ${frozenFile.path}`);
|
|
80594
|
+
}
|
|
80595
|
+
const resolvedTarget = path88.resolve(path88.dirname(source), target);
|
|
80596
|
+
if (symlinkPolicy === "contained_relative_only" && (path88.isAbsolute(target) || !isWithin(sourceRoot, resolvedTarget))) {
|
|
80597
|
+
continue;
|
|
80598
|
+
}
|
|
80599
|
+
fs81.mkdirSync(path88.dirname(destination), { recursive: true, mode: 448 });
|
|
80600
|
+
fs81.symlinkSync(target, destination);
|
|
80601
|
+
continue;
|
|
80602
|
+
}
|
|
80603
|
+
await atomicCopy(source, destination, frozenFile.mode, sourceRoot);
|
|
80363
80604
|
const copied = await recordFile(destinationRoot, destination, "workspace");
|
|
80364
80605
|
if (copied.sha256 !== frozenFile.sha256 || copied.size !== frozenFile.size || copied.mode !== frozenFile.mode) {
|
|
80365
80606
|
throw new BenchmarkPhaseError("evidence_tampered", `workspace changed after it was frozen: ${frozenFile.path}`);
|
|
@@ -80367,13 +80608,52 @@ async function copyFrozenWorkspace(manifest, spec, context) {
|
|
|
80367
80608
|
}
|
|
80368
80609
|
return destinationRoot;
|
|
80369
80610
|
}
|
|
80370
|
-
function
|
|
80611
|
+
async function copyEvaluatorTests(spec, evaluator, context) {
|
|
80612
|
+
const destinationRoot = fs81.mkdtempSync(path88.join(os16.tmpdir(), "brainbase-benchmark-tests-"));
|
|
80613
|
+
fs81.chmodSync(destinationRoot, 448);
|
|
80614
|
+
context.temporaryRoots.add(destinationRoot);
|
|
80615
|
+
const sourceRoot = evaluatorTestsPath(evaluator, spec.tests_root);
|
|
80616
|
+
const relativeRoot = evaluator.tests_path ? normalizedRootRelative(evaluator.tests_path) : ".";
|
|
80617
|
+
const destinationStart = relativeRoot === "." ? destinationRoot : path88.resolve(destinationRoot, relativeRoot);
|
|
80618
|
+
fs81.mkdirSync(destinationStart, { recursive: true, mode: 448 });
|
|
80619
|
+
const stack = [{ source: sourceRoot, destination: destinationStart }];
|
|
80620
|
+
while (stack.length > 0) {
|
|
80621
|
+
assertBudget(context);
|
|
80622
|
+
const current = stack.pop();
|
|
80623
|
+
const entries = fs81.readdirSync(current.source, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
|
|
80624
|
+
for (const entry of entries) {
|
|
80625
|
+
assertBudget(context);
|
|
80626
|
+
const source = path88.join(current.source, entry.name);
|
|
80627
|
+
const destination = path88.join(current.destination, entry.name);
|
|
80628
|
+
const stat = fs81.lstatSync(source);
|
|
80629
|
+
if (stat.isSymbolicLink()) {
|
|
80630
|
+
throw new BenchmarkPhaseError("unsafe_path", `evaluator reference tree contains a symlink: ${path88.relative(spec.tests_root, source)}`);
|
|
80631
|
+
}
|
|
80632
|
+
if (stat.isDirectory()) {
|
|
80633
|
+
fs81.mkdirSync(destination, { recursive: true, mode: stat.mode & 511 });
|
|
80634
|
+
stack.push({ source, destination });
|
|
80635
|
+
continue;
|
|
80636
|
+
}
|
|
80637
|
+
if (!stat.isFile()) {
|
|
80638
|
+
throw new BenchmarkPhaseError("unsafe_path", `evaluator reference tree contains an unsupported filesystem entry: ${path88.relative(spec.tests_root, source)}`);
|
|
80639
|
+
}
|
|
80640
|
+
await atomicCopy(source, destination, stat.mode & 511, spec.tests_root);
|
|
80641
|
+
const sourceRecord = await recordFile(spec.tests_root, source, "tests");
|
|
80642
|
+
const copiedRecord = await recordFile(destinationRoot, destination, "tests");
|
|
80643
|
+
if (copiedRecord.path !== sourceRecord.path || copiedRecord.sha256 !== sourceRecord.sha256 || copiedRecord.size !== sourceRecord.size || copiedRecord.mode !== sourceRecord.mode) {
|
|
80644
|
+
throw new BenchmarkPhaseError("evidence_tampered", `evaluator reference changed while it was copied: ${sourceRecord.path}`);
|
|
80645
|
+
}
|
|
80646
|
+
}
|
|
80647
|
+
}
|
|
80648
|
+
return destinationRoot;
|
|
80649
|
+
}
|
|
80650
|
+
function evaluatorTestsPath(evaluator, testsRoot) {
|
|
80371
80651
|
if (!evaluator.tests_path)
|
|
80372
|
-
return
|
|
80652
|
+
return testsRoot;
|
|
80373
80653
|
const relative = normalizedRootRelative(evaluator.tests_path);
|
|
80374
|
-
assertNoSymlinkTraversal(
|
|
80375
|
-
const candidate = path88.resolve(
|
|
80376
|
-
if (!isWithin(
|
|
80654
|
+
assertNoSymlinkTraversal(testsRoot, relative);
|
|
80655
|
+
const candidate = path88.resolve(testsRoot, relative);
|
|
80656
|
+
if (!isWithin(testsRoot, candidate) || !fs81.existsSync(candidate)) {
|
|
80377
80657
|
throw new BenchmarkPhaseError("invalid_tests_path", `evaluator tests_path does not exist: ${evaluator.tests_path}`);
|
|
80378
80658
|
}
|
|
80379
80659
|
const stat = fs81.lstatSync(candidate);
|
|
@@ -80398,8 +80678,16 @@ var CriterionResultSchema = exports_external.object({
|
|
|
80398
80678
|
});
|
|
80399
80679
|
var CriteriaResultFileSchema = exports_external.object({
|
|
80400
80680
|
criteria: exports_external.array(CriterionResultSchema).max(MAX_CRITERIA_PER_EVALUATOR)
|
|
80401
|
-
}).strict()
|
|
80402
|
-
|
|
80681
|
+
}).strict().superRefine((value, context) => {
|
|
80682
|
+
if (Buffer.byteLength(JSON.stringify(value)) > MAX_CRITERIA_RESULT_PAYLOAD_BYTES) {
|
|
80683
|
+
context.addIssue({
|
|
80684
|
+
code: exports_external.ZodIssueCode.custom,
|
|
80685
|
+
path: ["criteria"],
|
|
80686
|
+
message: "criterion result exceeds the 20 MiB aggregate payload limit"
|
|
80687
|
+
});
|
|
80688
|
+
}
|
|
80689
|
+
});
|
|
80690
|
+
function readCriterionResults(resultPath, criterionKeys, allowedEvidenceIds, spec, additionalSecrets = []) {
|
|
80403
80691
|
let opened;
|
|
80404
80692
|
try {
|
|
80405
80693
|
opened = openRegularFileNoFollow(resultPath, "criterion result", path88.dirname(resultPath));
|
|
@@ -80411,7 +80699,7 @@ function readCriterionResults(resultPath, criterionKeys, allowedEvidenceIds) {
|
|
|
80411
80699
|
}
|
|
80412
80700
|
try {
|
|
80413
80701
|
if (opened.stat.size > MAX_CRITERIA_RESULT_BYTES) {
|
|
80414
|
-
throw new BenchmarkPhaseError("invalid_criterion_result", "criterion result exceeds the
|
|
80702
|
+
throw new BenchmarkPhaseError("invalid_criterion_result", "criterion result exceeds the 24 MiB file limit");
|
|
80415
80703
|
}
|
|
80416
80704
|
let raw;
|
|
80417
80705
|
try {
|
|
@@ -80443,7 +80731,13 @@ function readCriterionResults(resultPath, criterionKeys, allowedEvidenceIds) {
|
|
|
80443
80731
|
if (unknownEvidenceIds.length > 0) {
|
|
80444
80732
|
throw new BenchmarkPhaseError("invalid_criterion_result", "criterion result cites evidence outside the evaluator plan", { unknown_evidence_ids: unknownEvidenceIds });
|
|
80445
80733
|
}
|
|
80446
|
-
return criterionKeys.map((key2) =>
|
|
80734
|
+
return criterionKeys.map((key2) => {
|
|
80735
|
+
const criterion = byKey.get(key2);
|
|
80736
|
+
return {
|
|
80737
|
+
...criterion,
|
|
80738
|
+
explanation: redactCommandOutput(Buffer.from(criterion.explanation), spec, additionalSecrets).toString("utf8")
|
|
80739
|
+
};
|
|
80740
|
+
});
|
|
80447
80741
|
} finally {
|
|
80448
80742
|
fs81.closeSync(opened.fd);
|
|
80449
80743
|
}
|
|
@@ -80466,7 +80760,7 @@ function eventType(event) {
|
|
|
80466
80760
|
}
|
|
80467
80761
|
return;
|
|
80468
80762
|
}
|
|
80469
|
-
async function evaluateOne(evaluator, spec, manifest, finalOutput, trajectory, frozenEvidence, context) {
|
|
80763
|
+
async function evaluateOne(evaluator, spec, manifest, frozenWorkspaceRoot, finalOutput, trajectory, frozenEvidence, context) {
|
|
80470
80764
|
const started = Date.now();
|
|
80471
80765
|
const base2 = {
|
|
80472
80766
|
id: evaluator.id,
|
|
@@ -80501,16 +80795,33 @@ async function evaluateOne(evaluator, spec, manifest, finalOutput, trajectory, f
|
|
|
80501
80795
|
cwd: ".",
|
|
80502
80796
|
secret_env: []
|
|
80503
80797
|
}, spec.tests_root, spec, context, {
|
|
80504
|
-
|
|
80505
|
-
|
|
80506
|
-
|
|
80798
|
+
additions: {
|
|
80799
|
+
BB_REGEX_INPUT: frozenEvidence.finalOutputPath,
|
|
80800
|
+
BB_REGEX_PATTERN_B64: Buffer.from(assertion.pattern).toString("base64"),
|
|
80801
|
+
BB_REGEX_FLAGS: assertion.flags
|
|
80802
|
+
},
|
|
80803
|
+
descendantCleanup: "always"
|
|
80507
80804
|
});
|
|
80508
80805
|
if (regexResult.exitCode === 2) {
|
|
80509
80806
|
throw new BenchmarkPhaseError("invalid_evaluator", `invalid regex in evaluator: ${evaluator.id}`);
|
|
80510
80807
|
}
|
|
80511
80808
|
verdict = regexResult.exitCode === 0;
|
|
80512
80809
|
}
|
|
80513
|
-
return {
|
|
80810
|
+
return {
|
|
80811
|
+
...base2,
|
|
80812
|
+
status: verdict ? "passed" : "failed",
|
|
80813
|
+
verdict,
|
|
80814
|
+
duration_ms: Date.now() - started,
|
|
80815
|
+
details: {
|
|
80816
|
+
operator: assertion.operator,
|
|
80817
|
+
actual_size_bytes: Buffer.byteLength(finalOutput),
|
|
80818
|
+
...assertion.operator === "exact" ? {
|
|
80819
|
+
expected_size_bytes: Buffer.byteLength(assertion.expected),
|
|
80820
|
+
actual_sha256: sha256(finalOutput),
|
|
80821
|
+
expected_sha256: sha256(assertion.expected)
|
|
80822
|
+
} : {}
|
|
80823
|
+
}
|
|
80824
|
+
};
|
|
80514
80825
|
}
|
|
80515
80826
|
if (evaluator.type === "trajectory_assertion") {
|
|
80516
80827
|
const count = trajectory.filter((event) => eventType(event) === evaluator.event_type).length;
|
|
@@ -80525,8 +80836,8 @@ async function evaluateOne(evaluator, spec, manifest, finalOutput, trajectory, f
|
|
|
80525
80836
|
}
|
|
80526
80837
|
if (evaluator.type === "workspace_assertion") {
|
|
80527
80838
|
const relative = workspaceRel(evaluator.path);
|
|
80528
|
-
assertNoSymlinkTraversal(
|
|
80529
|
-
const candidate = path88.resolve(
|
|
80839
|
+
assertNoSymlinkTraversal(frozenWorkspaceRoot, relative);
|
|
80840
|
+
const candidate = path88.resolve(frozenWorkspaceRoot, relative);
|
|
80530
80841
|
let stat = null;
|
|
80531
80842
|
try {
|
|
80532
80843
|
stat = fs81.lstatSync(candidate);
|
|
@@ -80540,15 +80851,17 @@ async function evaluateOne(evaluator, spec, manifest, finalOutput, trajectory, f
|
|
|
80540
80851
|
}
|
|
80541
80852
|
const exists2 = stat !== null;
|
|
80542
80853
|
let verdict = false;
|
|
80854
|
+
let actualSha256;
|
|
80543
80855
|
if (evaluator.assertion.operator === "exists")
|
|
80544
80856
|
verdict = exists2;
|
|
80545
80857
|
if (evaluator.assertion.operator === "not_exists")
|
|
80546
80858
|
verdict = !exists2;
|
|
80547
80859
|
if (evaluator.assertion.operator === "sha256") {
|
|
80548
80860
|
if (stat?.isFile()) {
|
|
80549
|
-
const opened = openRegularFileNoFollow(candidate, `workspace assertion ${relative}`,
|
|
80861
|
+
const opened = openRegularFileNoFollow(candidate, `workspace assertion ${relative}`, frozenWorkspaceRoot);
|
|
80550
80862
|
try {
|
|
80551
|
-
|
|
80863
|
+
actualSha256 = await sha256OfDescriptor(opened.fd);
|
|
80864
|
+
verdict = actualSha256 === evaluator.assertion.expected;
|
|
80552
80865
|
} finally {
|
|
80553
80866
|
fs81.closeSync(opened.fd);
|
|
80554
80867
|
}
|
|
@@ -80556,7 +80869,7 @@ async function evaluateOne(evaluator, spec, manifest, finalOutput, trajectory, f
|
|
|
80556
80869
|
}
|
|
80557
80870
|
if (evaluator.assertion.operator === "contains") {
|
|
80558
80871
|
if (stat?.isFile()) {
|
|
80559
|
-
const opened = openRegularFileNoFollow(candidate, `workspace assertion ${relative}`,
|
|
80872
|
+
const opened = openRegularFileNoFollow(candidate, `workspace assertion ${relative}`, frozenWorkspaceRoot);
|
|
80560
80873
|
try {
|
|
80561
80874
|
verdict = readDescriptor(opened.fd).toString("utf8").includes(evaluator.assertion.expected);
|
|
80562
80875
|
} finally {
|
|
@@ -80564,15 +80877,36 @@ async function evaluateOne(evaluator, spec, manifest, finalOutput, trajectory, f
|
|
|
80564
80877
|
}
|
|
80565
80878
|
}
|
|
80566
80879
|
}
|
|
80567
|
-
return {
|
|
80880
|
+
return {
|
|
80881
|
+
...base2,
|
|
80882
|
+
status: verdict ? "passed" : "failed",
|
|
80883
|
+
verdict,
|
|
80884
|
+
duration_ms: Date.now() - started,
|
|
80885
|
+
details: {
|
|
80886
|
+
operator: evaluator.assertion.operator,
|
|
80887
|
+
path: relative,
|
|
80888
|
+
exists: exists2,
|
|
80889
|
+
...stat ? {
|
|
80890
|
+
actual_kind: stat.isFile() ? "file" : stat.isDirectory() ? "directory" : "other",
|
|
80891
|
+
actual_size_bytes: stat.size
|
|
80892
|
+
} : {},
|
|
80893
|
+
...actualSha256 ? { actual_sha256: actualSha256 } : {}
|
|
80894
|
+
}
|
|
80895
|
+
};
|
|
80568
80896
|
}
|
|
80569
80897
|
let isolatedWorkspace;
|
|
80898
|
+
let isolatedTestsRoot;
|
|
80570
80899
|
let privateResultRoot;
|
|
80900
|
+
const usesLiveWorkspace = evaluator.workspace_mode === "read_only";
|
|
80571
80901
|
try {
|
|
80572
|
-
const evaluatorWorkspace = await copyFrozenWorkspace(manifest, spec, context);
|
|
80573
|
-
|
|
80574
|
-
|
|
80575
|
-
|
|
80902
|
+
const evaluatorWorkspace = usesLiveWorkspace ? spec.workspace_root : await copyFrozenWorkspace(manifest, spec, context, frozenWorkspaceRoot, "contained_relative_only");
|
|
80903
|
+
if (!usesLiveWorkspace)
|
|
80904
|
+
isolatedWorkspace = evaluatorWorkspace;
|
|
80905
|
+
else
|
|
80906
|
+
await verifyWorkspaceManifestUnchanged(manifest, spec, context);
|
|
80907
|
+
isolatedTestsRoot = await copyEvaluatorTests(spec, evaluator, context);
|
|
80908
|
+
const testsPath = evaluatorTestsPath(evaluator, isolatedTestsRoot);
|
|
80909
|
+
const commandRoot = evaluator.root === "workspace" ? evaluatorWorkspace : isolatedTestsRoot;
|
|
80576
80910
|
const command = { id: evaluator.id, ...evaluator.command };
|
|
80577
80911
|
const environment = {
|
|
80578
80912
|
BRAINBASE_BENCHMARK_WORKSPACE: evaluatorWorkspace,
|
|
@@ -80589,12 +80923,15 @@ async function evaluateOne(evaluator, spec, manifest, finalOutput, trajectory, f
|
|
|
80589
80923
|
criterionResultPath = path88.join(privateResultRoot, "result.json");
|
|
80590
80924
|
environment.BRAINBASE_BENCHMARK_CRITERIA_RESULT = criterionResultPath;
|
|
80591
80925
|
}
|
|
80592
|
-
const result2 = await runCommand(command, commandRoot, spec, context,
|
|
80926
|
+
const result2 = await runCommand(command, commandRoot, spec, context, {
|
|
80927
|
+
additions: environment,
|
|
80928
|
+
descendantCleanup: "always"
|
|
80929
|
+
});
|
|
80593
80930
|
const stdout = await writeLog(spec.logs_root, `${evaluator.id}.stdout.log`, result2.stdout, spec);
|
|
80594
80931
|
const stderr = await writeLog(spec.logs_root, `${evaluator.id}.stderr.log`, result2.stderr, spec);
|
|
80595
80932
|
let criterionResults;
|
|
80596
80933
|
try {
|
|
80597
|
-
criterionResults = evaluator.criterion_keys && criterionResultPath ? readCriterionResults(criterionResultPath, evaluator.criterion_keys, evaluator.allowed_evidence_ids ?? []) : undefined;
|
|
80934
|
+
criterionResults = evaluator.criterion_keys && criterionResultPath ? readCriterionResults(criterionResultPath, evaluator.criterion_keys, evaluator.allowed_evidence_ids ?? [], spec, result2.redactions) : undefined;
|
|
80598
80935
|
} catch (error2) {
|
|
80599
80936
|
const normalized = stableError(error2);
|
|
80600
80937
|
return {
|
|
@@ -80623,11 +80960,17 @@ async function evaluateOne(evaluator, spec, manifest, finalOutput, trajectory, f
|
|
|
80623
80960
|
...criterionResults ? { criterion_results: criterionResults } : {}
|
|
80624
80961
|
};
|
|
80625
80962
|
} finally {
|
|
80626
|
-
|
|
80627
|
-
if (
|
|
80628
|
-
|
|
80629
|
-
|
|
80630
|
-
|
|
80963
|
+
try {
|
|
80964
|
+
if (usesLiveWorkspace) {
|
|
80965
|
+
await verifyWorkspaceManifestUnchanged(manifest, spec, context);
|
|
80966
|
+
}
|
|
80967
|
+
} finally {
|
|
80968
|
+
for (const temporary of [privateResultRoot, isolatedTestsRoot, isolatedWorkspace]) {
|
|
80969
|
+
if (!temporary)
|
|
80970
|
+
continue;
|
|
80971
|
+
fs81.rmSync(temporary, { recursive: true, force: true });
|
|
80972
|
+
context.temporaryRoots.delete(temporary);
|
|
80973
|
+
}
|
|
80631
80974
|
}
|
|
80632
80975
|
}
|
|
80633
80976
|
}
|
|
@@ -80672,6 +81015,7 @@ async function executeEvaluate(spec, context) {
|
|
|
80672
81015
|
];
|
|
80673
81016
|
outputs.push(...frozenEvidenceRecords);
|
|
80674
81017
|
context.outputs.push(...frozenEvidenceRecords);
|
|
81018
|
+
assertEvaluateOutputBudget(spec, context);
|
|
80675
81019
|
assertBudget(context);
|
|
80676
81020
|
const manifest = await workspaceManifest(spec, context);
|
|
80677
81021
|
const manifestPath2 = path88.join(spec.logs_root, "candidate-workspace-manifest.json");
|
|
@@ -80684,6 +81028,7 @@ async function executeEvaluate(spec, context) {
|
|
|
80684
81028
|
const manifestRecord = await recordFile(spec.logs_root, manifestPath2, "logs");
|
|
80685
81029
|
outputs.push(manifestRecord);
|
|
80686
81030
|
context.outputs.push(manifestRecord);
|
|
81031
|
+
assertEvaluateOutputBudget(spec, context);
|
|
80687
81032
|
for (const artifactRelInput of spec.candidate_artifacts) {
|
|
80688
81033
|
const artifactRel = workspaceRel(artifactRelInput);
|
|
80689
81034
|
assertNoSymlinkTraversal(spec.workspace_root, artifactRel);
|
|
@@ -80701,11 +81046,13 @@ async function executeEvaluate(spec, context) {
|
|
|
80701
81046
|
outputs.push(artifact);
|
|
80702
81047
|
context.outputs.push(artifact);
|
|
80703
81048
|
}
|
|
81049
|
+
assertEvaluateOutputBudget(spec, context);
|
|
80704
81050
|
for (const candidateOutput of spec.candidate_outputs) {
|
|
80705
81051
|
const copied = await copyCandidateOutput(candidateOutput, manifest, spec, context);
|
|
80706
81052
|
outputs.push(...copied);
|
|
80707
81053
|
context.outputs.push(...copied);
|
|
80708
81054
|
}
|
|
81055
|
+
assertEvaluateOutputBudget(spec, context);
|
|
80709
81056
|
if (spec.capture_workspace_archive) {
|
|
80710
81057
|
const regularFiles = manifest.filter((entry) => entry.kind !== "symlink").map((entry) => entry.path);
|
|
80711
81058
|
const archive = path88.join(spec.logs_root, "candidate-workspace.tar.gz");
|
|
@@ -80719,23 +81066,23 @@ async function executeEvaluate(spec, context) {
|
|
|
80719
81066
|
const archiveRecord = await recordFile(spec.logs_root, archive, "logs");
|
|
80720
81067
|
outputs.push(archiveRecord);
|
|
80721
81068
|
context.outputs.push(archiveRecord);
|
|
81069
|
+
assertEvaluateOutputBudget(spec, context);
|
|
80722
81070
|
}
|
|
80723
81071
|
await verifyRecordsUnchanged(manifest, spec);
|
|
81072
|
+
const frozenWorkspaceRoot = await copyFrozenWorkspace(manifest, spec, context, spec.workspace_root);
|
|
80724
81073
|
for (const reference of spec.references) {
|
|
80725
81074
|
const referenceOutputs = await copyMaterial(reference, spec.staging_root, spec.tests_root, "tests", false, context);
|
|
80726
81075
|
outputs.push(...referenceOutputs);
|
|
80727
81076
|
context.outputs.push(...referenceOutputs);
|
|
80728
81077
|
}
|
|
80729
|
-
const frozenOutputCount = context.outputs.length;
|
|
80730
81078
|
const evaluators = [];
|
|
80731
81079
|
const evaluatorOrder = new Map(spec.evaluators.map((evaluator, index) => [evaluator.id, index]));
|
|
80732
81080
|
const executionOrder = [...spec.evaluators].sort((left, right) => Number(left.type === "sandbox_command") - Number(right.type === "sandbox_command"));
|
|
80733
81081
|
for (const evaluator of executionOrder) {
|
|
80734
81082
|
assertBudget(context);
|
|
80735
81083
|
const started = Date.now();
|
|
80736
|
-
let requiredResultError;
|
|
80737
81084
|
try {
|
|
80738
|
-
const evaluated = await evaluateOne(evaluator, spec, manifest, finalOutput.buffer.toString("utf8"), trajectory, { finalOutputPath, trajectoryPath }, context);
|
|
81085
|
+
const evaluated = await evaluateOne(evaluator, spec, manifest, frozenWorkspaceRoot, finalOutput.buffer.toString("utf8"), trajectory, { finalOutputPath, trajectoryPath }, context);
|
|
80739
81086
|
assertBudget(context);
|
|
80740
81087
|
evaluators.push(evaluated);
|
|
80741
81088
|
context.evaluators.push(evaluated);
|
|
@@ -80747,13 +81094,14 @@ async function executeEvaluate(spec, context) {
|
|
|
80747
81094
|
outputs.push(evaluated.stderr);
|
|
80748
81095
|
context.outputs.push(evaluated.stderr);
|
|
80749
81096
|
}
|
|
80750
|
-
if (evaluated.status === "errored" && evaluator.required) {
|
|
80751
|
-
const errorCode = typeof evaluated.details?.error_code === "string" ? evaluated.details.error_code : "evaluator_execution_failed";
|
|
80752
|
-
const errorMessage2 = typeof evaluated.details?.error_message === "string" ? evaluated.details.error_message : "required evaluator execution failed";
|
|
80753
|
-
requiredResultError = new BenchmarkPhaseError(errorCode, errorMessage2);
|
|
80754
|
-
}
|
|
80755
81097
|
} catch (error2) {
|
|
80756
81098
|
const normalized = stableError(error2);
|
|
81099
|
+
let stdout;
|
|
81100
|
+
let stderr;
|
|
81101
|
+
if (error2 instanceof BenchmarkCommandExecutionError) {
|
|
81102
|
+
stdout = await writeLog(spec.logs_root, `${evaluator.id}.stdout.log`, error2.stdout, spec);
|
|
81103
|
+
stderr = await writeLog(spec.logs_root, `${evaluator.id}.stderr.log`, error2.stderr, spec);
|
|
81104
|
+
}
|
|
80757
81105
|
const errored = {
|
|
80758
81106
|
id: evaluator.id,
|
|
80759
81107
|
type: evaluator.type,
|
|
@@ -80763,26 +81111,38 @@ async function executeEvaluate(spec, context) {
|
|
|
80763
81111
|
verdict: null,
|
|
80764
81112
|
engine: "brainbase-cli",
|
|
80765
81113
|
engine_version: VERSION,
|
|
80766
|
-
duration_ms: Date.now() - started,
|
|
81114
|
+
duration_ms: error2 instanceof BenchmarkCommandExecutionError ? error2.durationMs : Date.now() - started,
|
|
80767
81115
|
details: {
|
|
80768
81116
|
error_code: normalized?.code ?? "phase_failed",
|
|
80769
81117
|
error_message: normalized?.message ?? "evaluator execution failed"
|
|
80770
|
-
}
|
|
81118
|
+
},
|
|
81119
|
+
...stdout ? { stdout } : {},
|
|
81120
|
+
...stderr ? { stderr } : {}
|
|
80771
81121
|
};
|
|
80772
81122
|
evaluators.push(errored);
|
|
80773
81123
|
context.evaluators.push(errored);
|
|
80774
|
-
if (
|
|
81124
|
+
if (stdout) {
|
|
81125
|
+
outputs.push(stdout);
|
|
81126
|
+
context.outputs.push(stdout);
|
|
81127
|
+
}
|
|
81128
|
+
if (stderr) {
|
|
81129
|
+
outputs.push(stderr);
|
|
81130
|
+
context.outputs.push(stderr);
|
|
81131
|
+
}
|
|
81132
|
+
if (!(error2 instanceof BenchmarkCommandExecutionError))
|
|
80775
81133
|
throw error2;
|
|
80776
81134
|
assertBudget(context);
|
|
80777
81135
|
}
|
|
80778
|
-
if (
|
|
80779
|
-
|
|
81136
|
+
if (evaluator.type === "sandbox_command") {
|
|
81137
|
+
assertEvaluateOutputBudget(spec, context);
|
|
81138
|
+
}
|
|
80780
81139
|
}
|
|
80781
|
-
await verifyRecordsUnchanged([...context.inputs, ...context.outputs
|
|
81140
|
+
await verifyRecordsUnchanged([...context.inputs, ...context.outputs], spec);
|
|
80782
81141
|
if (!verifyOwnedDirectory(spec.tests_root, "tests", spec) || !verifyOwnedDirectory(spec.logs_root, "logs", spec)) {
|
|
80783
81142
|
throw new BenchmarkPhaseError("evidence_tampered", "benchmark evaluator changed an owned phase directory marker");
|
|
80784
81143
|
}
|
|
80785
81144
|
assertBudget(context);
|
|
81145
|
+
assertEvaluateOutputBudget(spec, context);
|
|
80786
81146
|
evaluators.sort((left, right) => evaluatorOrder.get(left.id) - evaluatorOrder.get(right.id));
|
|
80787
81147
|
return { outputs, evaluators };
|
|
80788
81148
|
}
|
|
@@ -80809,6 +81169,67 @@ function stableError(error2) {
|
|
|
80809
81169
|
message: error2 instanceof Error ? error2.message : "unknown benchmark phase failure"
|
|
80810
81170
|
};
|
|
80811
81171
|
}
|
|
81172
|
+
function compactOversizedResult(result2) {
|
|
81173
|
+
const actualBytes = Buffer.byteLength(`${JSON.stringify(result2, null, 2)}
|
|
81174
|
+
`);
|
|
81175
|
+
if (actualBytes <= MAX_PHASE_RESULT_BYTES)
|
|
81176
|
+
return result2;
|
|
81177
|
+
return {
|
|
81178
|
+
schema_version: result2.schema_version,
|
|
81179
|
+
cli_version: result2.cli_version,
|
|
81180
|
+
phase: result2.phase,
|
|
81181
|
+
attempt_id: result2.attempt_id,
|
|
81182
|
+
phase_id: result2.phase_id,
|
|
81183
|
+
spec_digest: result2.spec_digest,
|
|
81184
|
+
status: "failed",
|
|
81185
|
+
started_at: result2.started_at,
|
|
81186
|
+
completed_at: result2.completed_at,
|
|
81187
|
+
duration_ms: result2.duration_ms,
|
|
81188
|
+
steps: [],
|
|
81189
|
+
inputs: [],
|
|
81190
|
+
outputs: [],
|
|
81191
|
+
error: {
|
|
81192
|
+
code: "result_too_large",
|
|
81193
|
+
message: "benchmark phase result exceeds the 32 MiB contract limit",
|
|
81194
|
+
details: {
|
|
81195
|
+
actual_bytes: actualBytes,
|
|
81196
|
+
max_bytes: MAX_PHASE_RESULT_BYTES,
|
|
81197
|
+
step_count: result2.steps.length,
|
|
81198
|
+
input_count: result2.inputs.length,
|
|
81199
|
+
output_count: result2.outputs.length,
|
|
81200
|
+
evaluator_count: result2.evaluators?.length ?? 0
|
|
81201
|
+
}
|
|
81202
|
+
}
|
|
81203
|
+
};
|
|
81204
|
+
}
|
|
81205
|
+
function readCachedPhaseResult(resultPath) {
|
|
81206
|
+
let opened;
|
|
81207
|
+
try {
|
|
81208
|
+
opened = openRegularFileNoFollow(resultPath, "cached benchmark phase result");
|
|
81209
|
+
} catch {
|
|
81210
|
+
return;
|
|
81211
|
+
}
|
|
81212
|
+
try {
|
|
81213
|
+
if (opened.stat.size > MAX_PHASE_RESULT_BYTES)
|
|
81214
|
+
return;
|
|
81215
|
+
const chunks = [];
|
|
81216
|
+
let offset = 0;
|
|
81217
|
+
while (offset <= MAX_PHASE_RESULT_BYTES) {
|
|
81218
|
+
const buffer = Buffer.alloc(Math.min(64 * 1024, MAX_PHASE_RESULT_BYTES + 1 - offset));
|
|
81219
|
+
const bytesRead = fs81.readSync(opened.fd, buffer, 0, buffer.length, offset);
|
|
81220
|
+
if (bytesRead === 0) {
|
|
81221
|
+
return JSON.parse(Buffer.concat(chunks, offset).toString("utf8"));
|
|
81222
|
+
}
|
|
81223
|
+
chunks.push(buffer.subarray(0, bytesRead));
|
|
81224
|
+
offset += bytesRead;
|
|
81225
|
+
}
|
|
81226
|
+
return;
|
|
81227
|
+
} catch {
|
|
81228
|
+
return;
|
|
81229
|
+
} finally {
|
|
81230
|
+
fs81.closeSync(opened.fd);
|
|
81231
|
+
}
|
|
81232
|
+
}
|
|
80812
81233
|
function rawIdentity(value) {
|
|
80813
81234
|
if (!value || typeof value !== "object") {
|
|
80814
81235
|
return { phase: "unknown", attemptId: null, phaseId: null };
|
|
@@ -80881,13 +81302,9 @@ function prepareBenchmarkPhaseInvocation(specPathInput, resultPathInput, expecte
|
|
|
80881
81302
|
timeout_ms: spec.budget.timeout_ms
|
|
80882
81303
|
};
|
|
80883
81304
|
let cachedResult;
|
|
80884
|
-
|
|
80885
|
-
|
|
80886
|
-
|
|
80887
|
-
if (cached2.schema_version === SCHEMA_VERSION && cached2.phase === spec.phase && cached2.attempt_id === spec.attempt_id && cached2.phase_id === spec.phase_id && cached2.spec_digest === digest && cached2.status === "succeeded") {
|
|
80888
|
-
cachedResult = cached2;
|
|
80889
|
-
}
|
|
80890
|
-
} catch {}
|
|
81305
|
+
const cached2 = readCachedPhaseResult(resultPath);
|
|
81306
|
+
if (cached2?.schema_version === SCHEMA_VERSION && cached2.phase === spec.phase && cached2.attempt_id === spec.attempt_id && cached2.phase_id === spec.phase_id && cached2.spec_digest === digest && cached2.status === "succeeded") {
|
|
81307
|
+
cachedResult = cached2;
|
|
80891
81308
|
}
|
|
80892
81309
|
if (cachedResult) {
|
|
80893
81310
|
if (spec.phase === "hydrate") {
|
|
@@ -80904,7 +81321,7 @@ function prepareBenchmarkPhaseInvocation(specPathInput, resultPathInput, expecte
|
|
|
80904
81321
|
} catch (error2) {
|
|
80905
81322
|
return {
|
|
80906
81323
|
ok: false,
|
|
80907
|
-
result: {
|
|
81324
|
+
result: compactOversizedResult({
|
|
80908
81325
|
schema_version: SCHEMA_VERSION,
|
|
80909
81326
|
cli_version: VERSION,
|
|
80910
81327
|
phase: identity2.phase === "unknown" ? expectedPhase : identity2.phase,
|
|
@@ -80919,7 +81336,7 @@ function prepareBenchmarkPhaseInvocation(specPathInput, resultPathInput, expecte
|
|
|
80919
81336
|
inputs: [],
|
|
80920
81337
|
outputs: [],
|
|
80921
81338
|
error: stableError(error2)
|
|
80922
|
-
}
|
|
81339
|
+
})
|
|
80923
81340
|
};
|
|
80924
81341
|
}
|
|
80925
81342
|
}
|
|
@@ -80968,6 +81385,7 @@ async function runBenchmarkPhase(specPathInput, resultPathInput, expectedPhase,
|
|
|
80968
81385
|
let status = "failed";
|
|
80969
81386
|
let error2;
|
|
80970
81387
|
let context;
|
|
81388
|
+
let validatedSpec;
|
|
80971
81389
|
let resultPathValidated = false;
|
|
80972
81390
|
try {
|
|
80973
81391
|
let bytes;
|
|
@@ -80987,6 +81405,7 @@ async function runBenchmarkPhase(specPathInput, resultPathInput, expectedPhase,
|
|
|
80987
81405
|
}
|
|
80988
81406
|
identity2 = rawIdentity(raw);
|
|
80989
81407
|
const spec = BenchmarkSpecSchema.parse(raw);
|
|
81408
|
+
validatedSpec = spec;
|
|
80990
81409
|
if (expectedPhase && spec.phase !== expectedPhase) {
|
|
80991
81410
|
throw new BenchmarkPhaseError("phase_mismatch", `the ${expectedPhase} command cannot execute a ${spec.phase} spec`);
|
|
80992
81411
|
}
|
|
@@ -80997,13 +81416,9 @@ async function runBenchmarkPhase(specPathInput, resultPathInput, expectedPhase,
|
|
|
80997
81416
|
}
|
|
80998
81417
|
resultPathValidated = true;
|
|
80999
81418
|
let cachedResult;
|
|
81000
|
-
|
|
81001
|
-
|
|
81002
|
-
|
|
81003
|
-
if (cached2.schema_version === SCHEMA_VERSION && cached2.phase === spec.phase && cached2.attempt_id === spec.attempt_id && cached2.phase_id === spec.phase_id && cached2.spec_digest === digest && cached2.status === "succeeded") {
|
|
81004
|
-
cachedResult = cached2;
|
|
81005
|
-
}
|
|
81006
|
-
} catch {}
|
|
81419
|
+
const cached2 = readCachedPhaseResult(resultPath);
|
|
81420
|
+
if (cached2?.schema_version === SCHEMA_VERSION && cached2.phase === spec.phase && cached2.attempt_id === spec.attempt_id && cached2.phase_id === spec.phase_id && cached2.spec_digest === digest && cached2.status === "succeeded") {
|
|
81421
|
+
cachedResult = cached2;
|
|
81007
81422
|
}
|
|
81008
81423
|
if (cachedResult) {
|
|
81009
81424
|
if (spec.phase === "hydrate") {
|
|
@@ -81046,7 +81461,7 @@ async function runBenchmarkPhase(specPathInput, resultPathInput, expectedPhase,
|
|
|
81046
81461
|
fs81.rmSync(temporary, { recursive: true, force: true });
|
|
81047
81462
|
}
|
|
81048
81463
|
}
|
|
81049
|
-
|
|
81464
|
+
let result2 = {
|
|
81050
81465
|
schema_version: SCHEMA_VERSION,
|
|
81051
81466
|
cli_version: VERSION,
|
|
81052
81467
|
phase: identity2.phase === "unknown" && expectedPhase ? expectedPhase : identity2.phase,
|
|
@@ -81064,8 +81479,22 @@ async function runBenchmarkPhase(specPathInput, resultPathInput, expectedPhase,
|
|
|
81064
81479
|
...error2 ? { error: error2 } : {}
|
|
81065
81480
|
};
|
|
81066
81481
|
if (!resultPathValidated || !context?.logsOwned) {
|
|
81482
|
+
result2 = compactOversizedResult(result2);
|
|
81067
81483
|
return { exitCode: status === "succeeded" ? 0 : 1, result: result2 };
|
|
81068
81484
|
}
|
|
81485
|
+
if (status === "succeeded" && validatedSpec?.phase === "evaluate") {
|
|
81486
|
+
try {
|
|
81487
|
+
const resultBytes = Buffer.byteLength(`${JSON.stringify(result2, null, 2)}
|
|
81488
|
+
`);
|
|
81489
|
+
assertEvaluateOutputBudget(validatedSpec, context, resultBytes);
|
|
81490
|
+
} catch (budgetError) {
|
|
81491
|
+
status = "failed";
|
|
81492
|
+
result2.status = "failed";
|
|
81493
|
+
result2.error = stableError(budgetError);
|
|
81494
|
+
}
|
|
81495
|
+
}
|
|
81496
|
+
result2 = compactOversizedResult(result2);
|
|
81497
|
+
status = result2.status;
|
|
81069
81498
|
try {
|
|
81070
81499
|
writeJsonAtomic(resultPath, result2);
|
|
81071
81500
|
} catch (writeError) {
|