@brainbase-labs/cli 0.25.0-eng1209.1 → 0.25.0-eng1209.11

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.
Files changed (3) hide show
  1. package/README.md +14 -7
  2. package/dist/index.js +572 -112
  3. 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 one command evaluator and runs it after all
76
- read-only assertions so evaluator mutations cannot change candidate verdicts.
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, launch failures, timeouts,
80
- and output-budget violations fail the phase. Results are written atomically
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. The
88
- CLI kills the command process group and descendants it can observe, but the
89
- runtime lifecycle remains the final cleanup boundary for daemonized processes.
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.1",
36011
+ version: "0.25.0-eng1209.11",
36012
36012
  description: "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
36013
36013
  type: "module",
36014
36014
  bin: {
@@ -78485,13 +78485,20 @@ 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 = 100;
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;
78501
+ var JUDGE_ERROR_PREFIX = "BRAINBASE_BENCHMARK_JUDGE_ERROR_V1:";
78495
78502
  var RESERVED_WORKSPACE_PATHS = new Set([
78496
78503
  ".brainbase",
78497
78504
  ".git",
@@ -78658,7 +78665,7 @@ var TrajectoryEvidenceSchema = EvidenceFileSchema.extend({
78658
78665
  var HydrateSpecSchema = BaseSpecSchema.extend({
78659
78666
  phase: exports_external.literal("hydrate"),
78660
78667
  materials: exports_external.array(MaterialSchema).max(1e4).default([]),
78661
- setup_commands: exports_external.array(CommandSchema).max(128).default([])
78668
+ setup_commands: exports_external.array(CommandSchema).max(MAX_HYDRATE_COMMANDS).default([])
78662
78669
  }).strict();
78663
78670
  var CandidateOutputSchema = exports_external.object({
78664
78671
  id: IdSchema,
@@ -78712,6 +78719,20 @@ var BenchmarkSpecSchema = exports_external.discriminatedUnion("phase", [
78712
78719
  HydrateSpecSchema,
78713
78720
  EvaluateSpecSchema
78714
78721
  ]).superRefine((value, context) => {
78722
+ if (Object.hasOwn(value.environment, COMMAND_PROCESS_MARKER_ENV)) {
78723
+ context.addIssue({
78724
+ code: exports_external.ZodIssueCode.custom,
78725
+ path: ["environment", COMMAND_PROCESS_MARKER_ENV],
78726
+ message: `${COMMAND_PROCESS_MARKER_ENV} is reserved for command supervision`
78727
+ });
78728
+ }
78729
+ if (value.secret_env.includes(COMMAND_PROCESS_MARKER_ENV)) {
78730
+ context.addIssue({
78731
+ code: exports_external.ZodIssueCode.custom,
78732
+ path: ["secret_env"],
78733
+ message: `${COMMAND_PROCESS_MARKER_ENV} is reserved for command supervision`
78734
+ });
78735
+ }
78715
78736
  for (const name of Object.keys(value.environment)) {
78716
78737
  if (SENSITIVE_ENV_NAME_RE.test(name)) {
78717
78738
  context.addIssue({
@@ -78785,12 +78806,12 @@ var BenchmarkSpecSchema = exports_external.discriminatedUnion("phase", [
78785
78806
  { items: value.candidate_outputs, path: "candidate_outputs" }
78786
78807
  ];
78787
78808
  for (const { items, path: issuePath } of uniqueLists) {
78788
- const ids = items.map((item) => item.id);
78809
+ const ids = items.map((item) => issuePath === "candidate_outputs" ? item.id.toLowerCase() : item.id);
78789
78810
  if (new Set(ids).size !== ids.length) {
78790
78811
  context.addIssue({
78791
78812
  code: exports_external.ZodIssueCode.custom,
78792
78813
  path: [issuePath],
78793
- message: `${issuePath} ids must be unique`
78814
+ message: `${issuePath} ids must be unique${issuePath === "candidate_outputs" ? " ignoring case" : ""}`
78794
78815
  });
78795
78816
  }
78796
78817
  }
@@ -78853,6 +78874,7 @@ var BENCHMARK_CAPABILITIES = {
78853
78874
  "remote_input_references_v1",
78854
78875
  "archive_file_materials_v1",
78855
78876
  "structured_criterion_results_v1",
78877
+ "structured_judge_errors_v1",
78856
78878
  "multiple_sandbox_commands_v1",
78857
78879
  "sandbox_command_workspace_modes_v1",
78858
78880
  "candidate_outputs_v1",
@@ -78866,6 +78888,8 @@ var BENCHMARK_CAPABILITIES = {
78866
78888
  ],
78867
78889
  limits: {
78868
78890
  max_secret_bindings: 100,
78891
+ max_hydrate_commands: MAX_HYDRATE_COMMANDS,
78892
+ max_phase_result_bytes: MAX_PHASE_RESULT_BYTES,
78869
78893
  max_evaluators: 1000,
78870
78894
  max_sandbox_commands: MAX_SANDBOX_COMMANDS,
78871
78895
  max_criteria_per_evaluator: MAX_CRITERIA_PER_EVALUATOR
@@ -78882,6 +78906,19 @@ class BenchmarkPhaseError extends Error {
78882
78906
  this.name = "BenchmarkPhaseError";
78883
78907
  }
78884
78908
  }
78909
+
78910
+ class BenchmarkCommandExecutionError extends BenchmarkPhaseError {
78911
+ stdout;
78912
+ stderr;
78913
+ durationMs;
78914
+ constructor(code, message, stdout, stderr, durationMs) {
78915
+ super(code, message);
78916
+ this.stdout = stdout;
78917
+ this.stderr = stderr;
78918
+ this.durationMs = durationMs;
78919
+ this.name = "BenchmarkCommandExecutionError";
78920
+ }
78921
+ }
78885
78922
  var ZIP_EOCD_SIGNATURE = 101010256;
78886
78923
  var ZIP_CENTRAL_SIGNATURE = 33639248;
78887
78924
  var ZIP_LOCAL_SIGNATURE = 67324752;
@@ -79114,10 +79151,15 @@ async function verifyRecordsUnchanged(records, spec) {
79114
79151
  const relative = safeRelPath(record3.path);
79115
79152
  assertNoSymlinkTraversal(root, relative);
79116
79153
  const candidate = path88.resolve(root, relative);
79117
- if (!isWithin(root, candidate) || !fs81.existsSync(candidate)) {
79154
+ if (!isWithin(root, candidate)) {
79155
+ throw new BenchmarkPhaseError("evidence_tampered", `evidence was removed during evaluation: ${record3.root}:${record3.path}`);
79156
+ }
79157
+ let stat;
79158
+ try {
79159
+ stat = fs81.lstatSync(candidate);
79160
+ } catch {
79118
79161
  throw new BenchmarkPhaseError("evidence_tampered", `evidence was removed during evaluation: ${record3.root}:${record3.path}`);
79119
79162
  }
79120
- const stat = fs81.lstatSync(candidate);
79121
79163
  if (record3.kind === "symlink") {
79122
79164
  const target = stat.isSymbolicLink() ? fs81.readlinkSync(candidate) : null;
79123
79165
  if (target === null || Buffer.byteLength(target) !== record3.size || sha256(target) !== record3.sha256) {
@@ -79212,6 +79254,15 @@ function removeRemoteHydrationInputs(spec) {
79212
79254
  removedSources.add(material.source);
79213
79255
  }
79214
79256
  }
79257
+ function removePrivateHydrationSourcesBeforeSetup(spec, context) {
79258
+ removeRemoteHydrationInputs(spec);
79259
+ for (const temporary of context.temporaryRoots) {
79260
+ fs81.rmSync(temporary, { recursive: true, force: true });
79261
+ }
79262
+ context.temporaryRoots.clear();
79263
+ context.verifiedInputs.clear();
79264
+ context.preparedArchiveFiles.clear();
79265
+ }
79215
79266
  async function downloadInputReference(stagingRoot, input, context) {
79216
79267
  const cacheKey = inputCacheKey(input);
79217
79268
  const cached2 = context.verifiedInputs.get(cacheKey);
@@ -79893,7 +79944,7 @@ function buildEnvironment(spec, secretNames, additions = {}) {
79893
79944
  Object.assign(env3, additions);
79894
79945
  return env3;
79895
79946
  }
79896
- function redactCommandOutput(data, spec) {
79947
+ function redactCommandOutput(data, spec, additionalSecrets = []) {
79897
79948
  let value = data.toString("utf8");
79898
79949
  const sensitiveNames = new Set([
79899
79950
  ...spec.secret_env,
@@ -79904,6 +79955,10 @@ function redactCommandOutput(data, spec) {
79904
79955
  if (secret)
79905
79956
  value = value.split(secret).join("[REDACTED]");
79906
79957
  }
79958
+ for (const secret of additionalSecrets) {
79959
+ if (secret)
79960
+ value = value.split(secret).join("[REDACTED]");
79961
+ }
79907
79962
  return Buffer.from(value);
79908
79963
  }
79909
79964
  function descendantPids(parentPid) {
@@ -79955,7 +80010,99 @@ function terminate(child) {
79955
80010
  child.kill("SIGKILL");
79956
80011
  }
79957
80012
  }
79958
- async function runCommand(command, root, spec, context, additions = {}) {
80013
+ function markedProcessPids(marker) {
80014
+ const assignment = `${COMMAND_PROCESS_MARKER_ENV}=${marker}`;
80015
+ if (process.platform === "linux") {
80016
+ const matches2 = [];
80017
+ let entries;
80018
+ try {
80019
+ entries = fs81.readdirSync("/proc");
80020
+ } catch {
80021
+ return matches2;
80022
+ }
80023
+ for (const entry of entries) {
80024
+ if (!/^\d+$/.test(entry))
80025
+ continue;
80026
+ const pid = Number(entry);
80027
+ if (pid === process.pid)
80028
+ continue;
80029
+ try {
80030
+ const environment = fs81.readFileSync(path88.join("/proc", entry, "environ"), "utf8");
80031
+ if (environment.split("\x00").includes(assignment))
80032
+ matches2.push(pid);
80033
+ } catch {}
80034
+ }
80035
+ return matches2;
80036
+ }
80037
+ if (process.platform === "darwin") {
80038
+ try {
80039
+ const output = execFileSync2("ps", ["eww", "-axo", "pid=,command="], {
80040
+ encoding: "utf8",
80041
+ maxBuffer: 16 * 1024 * 1024,
80042
+ stdio: ["ignore", "pipe", "ignore"]
80043
+ });
80044
+ const matches2 = [];
80045
+ for (const line of output.split(`
80046
+ `)) {
80047
+ const match = line.match(/^\s*(\d+)\s+(.*)$/);
80048
+ if (!match || !match[2].includes(assignment))
80049
+ continue;
80050
+ const pid = Number(match[1]);
80051
+ if (Number.isInteger(pid) && pid !== process.pid)
80052
+ matches2.push(pid);
80053
+ }
80054
+ return matches2;
80055
+ } catch {
80056
+ return [];
80057
+ }
80058
+ }
80059
+ return [];
80060
+ }
80061
+ function processExists(pid) {
80062
+ if (process.platform === "linux") {
80063
+ try {
80064
+ const stat = fs81.readFileSync(path88.join("/proc", String(pid), "stat"), "utf8");
80065
+ const commandEnd = stat.lastIndexOf(")");
80066
+ const state = commandEnd >= 0 ? stat.slice(commandEnd + 2, commandEnd + 3) : "";
80067
+ if (state === "Z" || state === "X")
80068
+ return false;
80069
+ } catch (error2) {
80070
+ if (error2.code === "ENOENT")
80071
+ return false;
80072
+ }
80073
+ }
80074
+ try {
80075
+ process.kill(pid, 0);
80076
+ return true;
80077
+ } catch (error2) {
80078
+ return error2.code !== "ESRCH";
80079
+ }
80080
+ }
80081
+ async function terminateCommandProcesses(child, marker, observedDescendants) {
80082
+ if (child.pid !== undefined) {
80083
+ for (const pid of descendantPids(child.pid))
80084
+ observedDescendants.add(pid);
80085
+ }
80086
+ terminate(child);
80087
+ const deadline = Date.now() + COMMAND_PROCESS_CLEANUP_MS;
80088
+ while (true) {
80089
+ for (const pid of markedProcessPids(marker))
80090
+ observedDescendants.add(pid);
80091
+ const active = [...observedDescendants].filter(processExists);
80092
+ for (const pid of active) {
80093
+ try {
80094
+ process.kill(pid, "SIGKILL");
80095
+ } catch {}
80096
+ }
80097
+ if (active.length === 0)
80098
+ return;
80099
+ if (Date.now() >= deadline) {
80100
+ throw new BenchmarkPhaseError("command_cleanup_failed", "command descendants remained after bounded cleanup");
80101
+ }
80102
+ await new Promise((resolve) => setTimeout(resolve, COMMAND_PROCESS_POLL_MS));
80103
+ }
80104
+ }
80105
+ async function runCommand(command, root, spec, context, options = {}) {
79959
80106
  const cwdRel = normalizedRootRelative(command.cwd);
79960
80107
  assertNoSymlinkTraversal(root, cwdRel);
79961
80108
  const cwd2 = path88.resolve(root, cwdRel);
@@ -79971,29 +80118,59 @@ async function runCommand(command, root, spec, context, additions = {}) {
79971
80118
  const remainingMs = context.deadline - Date.now();
79972
80119
  if (remainingMs <= 0)
79973
80120
  throw new BenchmarkPhaseError("phase_timeout", "phase budget expired");
79974
- const timeoutMs2 = Math.min(command.timeout_ms ?? remainingMs, remainingMs);
80121
+ const reachesPhaseDeadline = command.timeout_ms === undefined || command.timeout_ms >= remainingMs;
80122
+ const timeoutMs2 = reachesPhaseDeadline ? remainingMs : command.timeout_ms;
79975
80123
  const started = Date.now();
80124
+ const commandMarker = crypto6.randomBytes(32).toString("hex");
79976
80125
  return await new Promise((resolve, reject2) => {
79977
80126
  const child = spawn4(command.argv[0], command.argv.slice(1), {
79978
80127
  cwd: cwd2,
79979
- env: buildEnvironment(spec, command.secret_env, additions),
80128
+ env: buildEnvironment(spec, command.secret_env, {
80129
+ ...options.additions,
80130
+ [COMMAND_PROCESS_MARKER_ENV]: commandMarker
80131
+ }),
79980
80132
  stdio: ["ignore", "pipe", "pipe"],
79981
80133
  detached: process.platform !== "win32"
79982
80134
  });
79983
80135
  const stdout = [];
79984
80136
  const stderr = [];
80137
+ const observedDescendants = new Set;
79985
80138
  let captured = 0;
79986
80139
  let settled = false;
79987
80140
  let timer;
80141
+ let observer;
80142
+ let cleanup;
80143
+ const observeDescendants = () => {
80144
+ if (child.pid === undefined)
80145
+ return;
80146
+ for (const pid of descendantPids(child.pid))
80147
+ observedDescendants.add(pid);
80148
+ };
80149
+ const cleanupProcesses = () => {
80150
+ if (observer)
80151
+ clearInterval(observer);
80152
+ cleanup ??= terminateCommandProcesses(child, commandMarker, observedDescendants);
80153
+ return cleanup;
80154
+ };
80155
+ const sanitizedOutput = (chunks) => redactCommandOutput(Buffer.concat(chunks), spec, [commandMarker]);
80156
+ const sanitizedError = (error2) => {
80157
+ if (!(error2 instanceof BenchmarkCommandExecutionError))
80158
+ return error2;
80159
+ return new BenchmarkCommandExecutionError(error2.code, error2.message, sanitizedOutput(stdout), sanitizedOutput(stderr), error2.durationMs);
80160
+ };
79988
80161
  const fail = (error2) => {
79989
80162
  if (settled)
79990
80163
  return;
79991
80164
  settled = true;
79992
80165
  if (timer)
79993
80166
  clearTimeout(timer);
79994
- terminate(child);
79995
- reject2(error2);
80167
+ cleanupProcesses().then(() => reject2(sanitizedError(error2)), reject2);
79996
80168
  };
80169
+ if (process.platform !== "linux") {
80170
+ observeDescendants();
80171
+ observer = setInterval(observeDescendants, COMMAND_PROCESS_POLL_MS);
80172
+ observer.unref();
80173
+ }
79997
80174
  const capture = (target, chunk2) => {
79998
80175
  if (settled)
79999
80176
  return;
@@ -80005,29 +80182,48 @@ async function runCommand(command, root, spec, context, additions = {}) {
80005
80182
  }
80006
80183
  target.push(chunk2);
80007
80184
  };
80185
+ const finish = (code, signal) => {
80186
+ if (observer)
80187
+ clearInterval(observer);
80188
+ if (code === null) {
80189
+ reject2(new BenchmarkCommandExecutionError("command_terminated", `command was terminated by ${signal ?? "an unknown signal"}: ${command.id}`, sanitizedOutput(stdout), sanitizedOutput(stderr), Date.now() - started));
80190
+ return;
80191
+ }
80192
+ resolve({
80193
+ exitCode: code,
80194
+ stdout: sanitizedOutput(stdout),
80195
+ stderr: sanitizedOutput(stderr),
80196
+ durationMs: Date.now() - started,
80197
+ redactions: [commandMarker]
80198
+ });
80199
+ };
80008
80200
  child.stdout?.on("data", (chunk2) => capture(stdout, chunk2));
80009
80201
  child.stderr?.on("data", (chunk2) => capture(stderr, chunk2));
80010
80202
  child.on("error", (error2) => {
80011
- fail(new BenchmarkPhaseError("command_start_failed", `failed to start ${command.id}: ${error2.message}`));
80203
+ fail(new BenchmarkCommandExecutionError("command_start_failed", `failed to start ${command.id}: ${error2.message}`, Buffer.concat(stdout), Buffer.concat(stderr), Date.now() - started));
80012
80204
  });
80013
80205
  timer = setTimeout(() => {
80014
- fail(new BenchmarkPhaseError("command_timeout", `command timed out: ${command.id}`));
80206
+ if (reachesPhaseDeadline) {
80207
+ fail(new BenchmarkPhaseError("phase_timeout", "phase budget expired"));
80208
+ return;
80209
+ }
80210
+ fail(new BenchmarkCommandExecutionError("command_timeout", `command timed out: ${command.id}`, Buffer.concat(stdout), Buffer.concat(stderr), Date.now() - started));
80015
80211
  }, timeoutMs2);
80016
- child.on("close", (code, signal) => {
80212
+ child.on("exit", (code, signal) => {
80017
80213
  if (settled)
80018
80214
  return;
80019
80215
  settled = true;
80020
80216
  clearTimeout(timer);
80021
- if (code === null) {
80022
- reject2(new BenchmarkPhaseError("command_terminated", `command was terminated by ${signal ?? "an unknown signal"}: ${command.id}`));
80023
- return;
80217
+ if (options.descendantCleanup === "always" || code === null || code !== 0) {
80218
+ cleanupProcesses().then(() => finish(code, signal), reject2);
80219
+ } else {
80220
+ if (observer)
80221
+ clearInterval(observer);
80222
+ child.unref();
80223
+ child.stdout?.unref?.();
80224
+ child.stderr?.unref?.();
80225
+ finish(code, signal);
80024
80226
  }
80025
- resolve({
80026
- exitCode: code,
80027
- stdout: Buffer.concat(stdout),
80028
- stderr: Buffer.concat(stderr),
80029
- durationMs: Date.now() - started
80030
- });
80031
80227
  });
80032
80228
  });
80033
80229
  }
@@ -80100,11 +80296,14 @@ async function executeHydrate(spec, context) {
80100
80296
  throw error2;
80101
80297
  }
80102
80298
  }
80299
+ removePrivateHydrationSourcesBeforeSetup(spec, context);
80103
80300
  for (const command of spec.setup_commands) {
80104
80301
  assertBudget(context);
80105
80302
  let result2;
80106
80303
  try {
80107
- result2 = await runCommand(command, spec.workspace_root, spec, context);
80304
+ result2 = await runCommand(command, spec.workspace_root, spec, context, {
80305
+ descendantCleanup: "failure_only"
80306
+ });
80108
80307
  } catch (error2) {
80109
80308
  context.steps.push({
80110
80309
  id: command.id,
@@ -80155,7 +80354,6 @@ async function executeHydrate(spec, context) {
80155
80354
  }
80156
80355
  outputs.splice(0, outputs.length, ...finalOutputs);
80157
80356
  context.outputs.splice(0, context.outputs.length, ...finalOutputs);
80158
- removeRemoteHydrationInputs(spec);
80159
80357
  assertBudget(context);
80160
80358
  return outputs;
80161
80359
  }
@@ -80220,6 +80418,49 @@ async function workspaceManifest(spec, context) {
80220
80418
  }
80221
80419
  return records.sort((a3, b4) => a3.path.localeCompare(b4.path));
80222
80420
  }
80421
+ async function verifyWorkspaceManifestUnchanged(expected, spec, context) {
80422
+ await verifyRecordsUnchanged(expected, spec);
80423
+ const actual = await workspaceManifest(spec, context);
80424
+ if (JSON.stringify(actual) !== JSON.stringify(expected)) {
80425
+ throw new BenchmarkPhaseError("evidence_tampered", "candidate workspace changed during read-only evaluation");
80426
+ }
80427
+ }
80428
+ function treeBytes(root, context) {
80429
+ let totalBytes = 0;
80430
+ const stack = [path88.resolve(root)];
80431
+ while (stack.length > 0) {
80432
+ const directory = stack.pop();
80433
+ const entries = fs81.readdirSync(directory, { withFileTypes: true });
80434
+ for (const entry of entries) {
80435
+ assertBudget(context);
80436
+ const candidate = path88.join(directory, entry.name);
80437
+ const stat = fs81.lstatSync(candidate);
80438
+ if (stat.isDirectory()) {
80439
+ stack.push(candidate);
80440
+ continue;
80441
+ }
80442
+ if (stat.isFile()) {
80443
+ totalBytes += stat.size;
80444
+ continue;
80445
+ }
80446
+ if (stat.isSymbolicLink()) {
80447
+ totalBytes += Buffer.byteLength(fs81.readlinkSync(candidate));
80448
+ continue;
80449
+ }
80450
+ throw new BenchmarkPhaseError("unsafe_path", `evaluate output contains an unsupported filesystem entry: ${path88.relative(root, candidate)}`);
80451
+ }
80452
+ }
80453
+ return totalBytes;
80454
+ }
80455
+ function assertEvaluateOutputBudget(spec, context, additionalBytes = 0) {
80456
+ const actual = treeBytes(spec.logs_root, context) + additionalBytes;
80457
+ if (actual > spec.workspace_limits.max_total_bytes) {
80458
+ throw new BenchmarkPhaseError("workspace_limit_exceeded", "evaluate output bytes exceed the workspace byte limit", {
80459
+ actual,
80460
+ max_total_bytes: spec.workspace_limits.max_total_bytes
80461
+ });
80462
+ }
80463
+ }
80223
80464
  function candidateGlob(pattern) {
80224
80465
  let source = "^";
80225
80466
  for (let index = 0;index < pattern.length; index += 1) {
@@ -80250,6 +80491,8 @@ function manifestDirectories(manifest, context) {
80250
80491
  const directories = new Set;
80251
80492
  for (const entry of manifest) {
80252
80493
  assertBudget(context);
80494
+ if (entry.kind === "symlink")
80495
+ continue;
80253
80496
  let current = path88.posix.dirname(entry.path);
80254
80497
  while (current !== ".") {
80255
80498
  assertBudget(context);
@@ -80267,9 +80510,8 @@ function candidateOutputFiles(output, manifest, context) {
80267
80510
  assertBudget(context);
80268
80511
  if (!matcher.test(entry.path))
80269
80512
  continue;
80270
- if (entry.kind === "symlink") {
80271
- throw new BenchmarkPhaseError("unsafe_path", `candidate output ${output.id} matches a symlink`);
80272
- }
80513
+ if (entry.kind === "symlink")
80514
+ continue;
80273
80515
  matched.push(entry);
80274
80516
  }
80275
80517
  return {
@@ -80277,12 +80519,6 @@ function candidateOutputFiles(output, manifest, context) {
80277
80519
  files: matched
80278
80520
  };
80279
80521
  }
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
80522
  const directories = [];
80287
80523
  for (const directory of manifestDirectories(manifest, context)) {
80288
80524
  assertBudget(context);
@@ -80297,9 +80533,8 @@ function candidateOutputFiles(output, manifest, context) {
80297
80533
  assertBudget(context);
80298
80534
  if (!entry.path.startsWith(prefix))
80299
80535
  continue;
80300
- if (entry.kind === "symlink") {
80301
- throw new BenchmarkPhaseError("unsafe_path", `candidate output ${output.id} contains a symlink`);
80302
- }
80536
+ if (entry.kind === "symlink")
80537
+ continue;
80303
80538
  selected.set(entry.path, entry);
80304
80539
  }
80305
80540
  }
@@ -80332,7 +80567,7 @@ async function copyCandidateOutput(output, manifest, spec, context) {
80332
80567
  for (const frozenFile of selected.files) {
80333
80568
  assertBudget(context);
80334
80569
  const source = path88.resolve(spec.workspace_root, safeRelPath(frozenFile.path));
80335
- const destination = path88.resolve(spec.logs_root, "candidate-artifacts", output.id, safeRelPath(frozenFile.path));
80570
+ const destination = path88.resolve(spec.logs_root, "candidate-outputs", output.id, safeRelPath(frozenFile.path));
80336
80571
  if (fs81.existsSync(destination)) {
80337
80572
  throw new BenchmarkPhaseError("destination_conflict", `candidate output destination already exists: ${output.id}/${frozenFile.path}`);
80338
80573
  }
@@ -80345,21 +80580,29 @@ async function copyCandidateOutput(output, manifest, spec, context) {
80345
80580
  }
80346
80581
  return copied;
80347
80582
  }
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
- }
80583
+ async function copyFrozenWorkspace(manifest, spec, context, sourceRoot = spec.workspace_root, symlinkPolicy = "preserve") {
80355
80584
  const destinationRoot = fs81.mkdtempSync(path88.join(os16.tmpdir(), "brainbase-benchmark-workspace-"));
80356
80585
  fs81.chmodSync(destinationRoot, 448);
80357
80586
  context.temporaryRoots.add(destinationRoot);
80358
80587
  for (const frozenFile of manifest) {
80359
80588
  assertBudget(context);
80360
- const source = path88.resolve(spec.workspace_root, safeRelPath(frozenFile.path));
80589
+ const source = path88.resolve(sourceRoot, safeRelPath(frozenFile.path));
80361
80590
  const destination = path88.resolve(destinationRoot, safeRelPath(frozenFile.path));
80362
- await atomicCopy(source, destination, frozenFile.mode, spec.workspace_root);
80591
+ if (frozenFile.kind === "symlink") {
80592
+ const stat = fs81.lstatSync(source);
80593
+ const target = stat.isSymbolicLink() ? fs81.readlinkSync(source) : null;
80594
+ if (target === null || Buffer.byteLength(target) !== frozenFile.size || sha256(target) !== frozenFile.sha256) {
80595
+ throw new BenchmarkPhaseError("evidence_tampered", `workspace changed after it was frozen: ${frozenFile.path}`);
80596
+ }
80597
+ const resolvedTarget = path88.resolve(path88.dirname(source), target);
80598
+ if (symlinkPolicy === "contained_relative_only" && (path88.isAbsolute(target) || !isWithin(sourceRoot, resolvedTarget))) {
80599
+ continue;
80600
+ }
80601
+ fs81.mkdirSync(path88.dirname(destination), { recursive: true, mode: 448 });
80602
+ fs81.symlinkSync(target, destination);
80603
+ continue;
80604
+ }
80605
+ await atomicCopy(source, destination, frozenFile.mode, sourceRoot);
80363
80606
  const copied = await recordFile(destinationRoot, destination, "workspace");
80364
80607
  if (copied.sha256 !== frozenFile.sha256 || copied.size !== frozenFile.size || copied.mode !== frozenFile.mode) {
80365
80608
  throw new BenchmarkPhaseError("evidence_tampered", `workspace changed after it was frozen: ${frozenFile.path}`);
@@ -80367,13 +80610,52 @@ async function copyFrozenWorkspace(manifest, spec, context) {
80367
80610
  }
80368
80611
  return destinationRoot;
80369
80612
  }
80370
- function evaluatorTestsPath(evaluator, spec) {
80613
+ async function copyEvaluatorTests(spec, evaluator, context) {
80614
+ const destinationRoot = fs81.mkdtempSync(path88.join(os16.tmpdir(), "brainbase-benchmark-tests-"));
80615
+ fs81.chmodSync(destinationRoot, 448);
80616
+ context.temporaryRoots.add(destinationRoot);
80617
+ const sourceRoot = evaluatorTestsPath(evaluator, spec.tests_root);
80618
+ const relativeRoot = evaluator.tests_path ? normalizedRootRelative(evaluator.tests_path) : ".";
80619
+ const destinationStart = relativeRoot === "." ? destinationRoot : path88.resolve(destinationRoot, relativeRoot);
80620
+ fs81.mkdirSync(destinationStart, { recursive: true, mode: 448 });
80621
+ const stack = [{ source: sourceRoot, destination: destinationStart }];
80622
+ while (stack.length > 0) {
80623
+ assertBudget(context);
80624
+ const current = stack.pop();
80625
+ const entries = fs81.readdirSync(current.source, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
80626
+ for (const entry of entries) {
80627
+ assertBudget(context);
80628
+ const source = path88.join(current.source, entry.name);
80629
+ const destination = path88.join(current.destination, entry.name);
80630
+ const stat = fs81.lstatSync(source);
80631
+ if (stat.isSymbolicLink()) {
80632
+ throw new BenchmarkPhaseError("unsafe_path", `evaluator reference tree contains a symlink: ${path88.relative(spec.tests_root, source)}`);
80633
+ }
80634
+ if (stat.isDirectory()) {
80635
+ fs81.mkdirSync(destination, { recursive: true, mode: stat.mode & 511 });
80636
+ stack.push({ source, destination });
80637
+ continue;
80638
+ }
80639
+ if (!stat.isFile()) {
80640
+ throw new BenchmarkPhaseError("unsafe_path", `evaluator reference tree contains an unsupported filesystem entry: ${path88.relative(spec.tests_root, source)}`);
80641
+ }
80642
+ await atomicCopy(source, destination, stat.mode & 511, spec.tests_root);
80643
+ const sourceRecord = await recordFile(spec.tests_root, source, "tests");
80644
+ const copiedRecord = await recordFile(destinationRoot, destination, "tests");
80645
+ if (copiedRecord.path !== sourceRecord.path || copiedRecord.sha256 !== sourceRecord.sha256 || copiedRecord.size !== sourceRecord.size || copiedRecord.mode !== sourceRecord.mode) {
80646
+ throw new BenchmarkPhaseError("evidence_tampered", `evaluator reference changed while it was copied: ${sourceRecord.path}`);
80647
+ }
80648
+ }
80649
+ }
80650
+ return destinationRoot;
80651
+ }
80652
+ function evaluatorTestsPath(evaluator, testsRoot) {
80371
80653
  if (!evaluator.tests_path)
80372
- return spec.tests_root;
80654
+ return testsRoot;
80373
80655
  const relative = normalizedRootRelative(evaluator.tests_path);
80374
- assertNoSymlinkTraversal(spec.tests_root, relative);
80375
- const candidate = path88.resolve(spec.tests_root, relative);
80376
- if (!isWithin(spec.tests_root, candidate) || !fs81.existsSync(candidate)) {
80656
+ assertNoSymlinkTraversal(testsRoot, relative);
80657
+ const candidate = path88.resolve(testsRoot, relative);
80658
+ if (!isWithin(testsRoot, candidate) || !fs81.existsSync(candidate)) {
80377
80659
  throw new BenchmarkPhaseError("invalid_tests_path", `evaluator tests_path does not exist: ${evaluator.tests_path}`);
80378
80660
  }
80379
80661
  const stat = fs81.lstatSync(candidate);
@@ -80398,8 +80680,39 @@ var CriterionResultSchema = exports_external.object({
80398
80680
  });
80399
80681
  var CriteriaResultFileSchema = exports_external.object({
80400
80682
  criteria: exports_external.array(CriterionResultSchema).max(MAX_CRITERIA_PER_EVALUATOR)
80683
+ }).strict().superRefine((value, context) => {
80684
+ if (Buffer.byteLength(JSON.stringify(value)) > MAX_CRITERIA_RESULT_PAYLOAD_BYTES) {
80685
+ context.addIssue({
80686
+ code: exports_external.ZodIssueCode.custom,
80687
+ path: ["criteria"],
80688
+ message: "criterion result exceeds the 20 MiB aggregate payload limit"
80689
+ });
80690
+ }
80691
+ });
80692
+ var JudgeErrorSchema = exports_external.object({
80693
+ code: exports_external.enum([
80694
+ "judge_provider_quota_exhausted",
80695
+ "judge_rate_limited",
80696
+ "judge_provider_authentication_failed",
80697
+ "judge_provider_unavailable",
80698
+ "judge_request_failed"
80699
+ ]),
80700
+ message: exports_external.string().min(1).max(500)
80401
80701
  }).strict();
80402
- function readCriterionResults(resultPath, criterionKeys, allowedEvidenceIds) {
80702
+ function structuredJudgeError(stderr) {
80703
+ for (const line of stderr.toString("utf8").split(/\r?\n/).reverse()) {
80704
+ const marker = line.indexOf(JUDGE_ERROR_PREFIX);
80705
+ if (marker < 0)
80706
+ continue;
80707
+ try {
80708
+ const parsed = JudgeErrorSchema.safeParse(JSON.parse(line.slice(marker + JUDGE_ERROR_PREFIX.length)));
80709
+ if (parsed.success)
80710
+ return parsed.data;
80711
+ } catch {}
80712
+ }
80713
+ return null;
80714
+ }
80715
+ function readCriterionResults(resultPath, criterionKeys, allowedEvidenceIds, spec, additionalSecrets = []) {
80403
80716
  let opened;
80404
80717
  try {
80405
80718
  opened = openRegularFileNoFollow(resultPath, "criterion result", path88.dirname(resultPath));
@@ -80411,7 +80724,7 @@ function readCriterionResults(resultPath, criterionKeys, allowedEvidenceIds) {
80411
80724
  }
80412
80725
  try {
80413
80726
  if (opened.stat.size > MAX_CRITERIA_RESULT_BYTES) {
80414
- throw new BenchmarkPhaseError("invalid_criterion_result", "criterion result exceeds the 1 MiB limit");
80727
+ throw new BenchmarkPhaseError("invalid_criterion_result", "criterion result exceeds the 24 MiB file limit");
80415
80728
  }
80416
80729
  let raw;
80417
80730
  try {
@@ -80443,7 +80756,13 @@ function readCriterionResults(resultPath, criterionKeys, allowedEvidenceIds) {
80443
80756
  if (unknownEvidenceIds.length > 0) {
80444
80757
  throw new BenchmarkPhaseError("invalid_criterion_result", "criterion result cites evidence outside the evaluator plan", { unknown_evidence_ids: unknownEvidenceIds });
80445
80758
  }
80446
- return criterionKeys.map((key2) => byKey.get(key2));
80759
+ return criterionKeys.map((key2) => {
80760
+ const criterion = byKey.get(key2);
80761
+ return {
80762
+ ...criterion,
80763
+ explanation: redactCommandOutput(Buffer.from(criterion.explanation), spec, additionalSecrets).toString("utf8")
80764
+ };
80765
+ });
80447
80766
  } finally {
80448
80767
  fs81.closeSync(opened.fd);
80449
80768
  }
@@ -80466,7 +80785,7 @@ function eventType(event) {
80466
80785
  }
80467
80786
  return;
80468
80787
  }
80469
- async function evaluateOne(evaluator, spec, manifest, finalOutput, trajectory, frozenEvidence, context) {
80788
+ async function evaluateOne(evaluator, spec, manifest, frozenWorkspaceRoot, finalOutput, trajectory, frozenEvidence, context) {
80470
80789
  const started = Date.now();
80471
80790
  const base2 = {
80472
80791
  id: evaluator.id,
@@ -80501,16 +80820,33 @@ async function evaluateOne(evaluator, spec, manifest, finalOutput, trajectory, f
80501
80820
  cwd: ".",
80502
80821
  secret_env: []
80503
80822
  }, spec.tests_root, spec, context, {
80504
- BB_REGEX_INPUT: frozenEvidence.finalOutputPath,
80505
- BB_REGEX_PATTERN_B64: Buffer.from(assertion.pattern).toString("base64"),
80506
- BB_REGEX_FLAGS: assertion.flags
80823
+ additions: {
80824
+ BB_REGEX_INPUT: frozenEvidence.finalOutputPath,
80825
+ BB_REGEX_PATTERN_B64: Buffer.from(assertion.pattern).toString("base64"),
80826
+ BB_REGEX_FLAGS: assertion.flags
80827
+ },
80828
+ descendantCleanup: "always"
80507
80829
  });
80508
80830
  if (regexResult.exitCode === 2) {
80509
80831
  throw new BenchmarkPhaseError("invalid_evaluator", `invalid regex in evaluator: ${evaluator.id}`);
80510
80832
  }
80511
80833
  verdict = regexResult.exitCode === 0;
80512
80834
  }
80513
- return { ...base2, status: verdict ? "passed" : "failed", verdict, duration_ms: Date.now() - started };
80835
+ return {
80836
+ ...base2,
80837
+ status: verdict ? "passed" : "failed",
80838
+ verdict,
80839
+ duration_ms: Date.now() - started,
80840
+ details: {
80841
+ operator: assertion.operator,
80842
+ actual_size_bytes: Buffer.byteLength(finalOutput),
80843
+ ...assertion.operator === "exact" ? {
80844
+ expected_size_bytes: Buffer.byteLength(assertion.expected),
80845
+ actual_sha256: sha256(finalOutput),
80846
+ expected_sha256: sha256(assertion.expected)
80847
+ } : {}
80848
+ }
80849
+ };
80514
80850
  }
80515
80851
  if (evaluator.type === "trajectory_assertion") {
80516
80852
  const count = trajectory.filter((event) => eventType(event) === evaluator.event_type).length;
@@ -80525,8 +80861,8 @@ async function evaluateOne(evaluator, spec, manifest, finalOutput, trajectory, f
80525
80861
  }
80526
80862
  if (evaluator.type === "workspace_assertion") {
80527
80863
  const relative = workspaceRel(evaluator.path);
80528
- assertNoSymlinkTraversal(spec.workspace_root, relative);
80529
- const candidate = path88.resolve(spec.workspace_root, relative);
80864
+ assertNoSymlinkTraversal(frozenWorkspaceRoot, relative);
80865
+ const candidate = path88.resolve(frozenWorkspaceRoot, relative);
80530
80866
  let stat = null;
80531
80867
  try {
80532
80868
  stat = fs81.lstatSync(candidate);
@@ -80540,15 +80876,17 @@ async function evaluateOne(evaluator, spec, manifest, finalOutput, trajectory, f
80540
80876
  }
80541
80877
  const exists2 = stat !== null;
80542
80878
  let verdict = false;
80879
+ let actualSha256;
80543
80880
  if (evaluator.assertion.operator === "exists")
80544
80881
  verdict = exists2;
80545
80882
  if (evaluator.assertion.operator === "not_exists")
80546
80883
  verdict = !exists2;
80547
80884
  if (evaluator.assertion.operator === "sha256") {
80548
80885
  if (stat?.isFile()) {
80549
- const opened = openRegularFileNoFollow(candidate, `workspace assertion ${relative}`, spec.workspace_root);
80886
+ const opened = openRegularFileNoFollow(candidate, `workspace assertion ${relative}`, frozenWorkspaceRoot);
80550
80887
  try {
80551
- verdict = await sha256OfDescriptor(opened.fd) === evaluator.assertion.expected;
80888
+ actualSha256 = await sha256OfDescriptor(opened.fd);
80889
+ verdict = actualSha256 === evaluator.assertion.expected;
80552
80890
  } finally {
80553
80891
  fs81.closeSync(opened.fd);
80554
80892
  }
@@ -80556,7 +80894,7 @@ async function evaluateOne(evaluator, spec, manifest, finalOutput, trajectory, f
80556
80894
  }
80557
80895
  if (evaluator.assertion.operator === "contains") {
80558
80896
  if (stat?.isFile()) {
80559
- const opened = openRegularFileNoFollow(candidate, `workspace assertion ${relative}`, spec.workspace_root);
80897
+ const opened = openRegularFileNoFollow(candidate, `workspace assertion ${relative}`, frozenWorkspaceRoot);
80560
80898
  try {
80561
80899
  verdict = readDescriptor(opened.fd).toString("utf8").includes(evaluator.assertion.expected);
80562
80900
  } finally {
@@ -80564,15 +80902,36 @@ async function evaluateOne(evaluator, spec, manifest, finalOutput, trajectory, f
80564
80902
  }
80565
80903
  }
80566
80904
  }
80567
- return { ...base2, status: verdict ? "passed" : "failed", verdict, duration_ms: Date.now() - started };
80905
+ return {
80906
+ ...base2,
80907
+ status: verdict ? "passed" : "failed",
80908
+ verdict,
80909
+ duration_ms: Date.now() - started,
80910
+ details: {
80911
+ operator: evaluator.assertion.operator,
80912
+ path: relative,
80913
+ exists: exists2,
80914
+ ...stat ? {
80915
+ actual_kind: stat.isFile() ? "file" : stat.isDirectory() ? "directory" : "other",
80916
+ actual_size_bytes: stat.size
80917
+ } : {},
80918
+ ...actualSha256 ? { actual_sha256: actualSha256 } : {}
80919
+ }
80920
+ };
80568
80921
  }
80569
80922
  let isolatedWorkspace;
80923
+ let isolatedTestsRoot;
80570
80924
  let privateResultRoot;
80925
+ const usesLiveWorkspace = evaluator.workspace_mode === "read_only";
80571
80926
  try {
80572
- const evaluatorWorkspace = await copyFrozenWorkspace(manifest, spec, context);
80573
- isolatedWorkspace = evaluatorWorkspace;
80574
- const testsPath = evaluatorTestsPath(evaluator, spec);
80575
- const commandRoot = evaluator.root === "workspace" ? evaluatorWorkspace : spec.tests_root;
80927
+ const evaluatorWorkspace = usesLiveWorkspace ? spec.workspace_root : await copyFrozenWorkspace(manifest, spec, context, frozenWorkspaceRoot, "contained_relative_only");
80928
+ if (!usesLiveWorkspace)
80929
+ isolatedWorkspace = evaluatorWorkspace;
80930
+ else
80931
+ await verifyWorkspaceManifestUnchanged(manifest, spec, context);
80932
+ isolatedTestsRoot = await copyEvaluatorTests(spec, evaluator, context);
80933
+ const testsPath = evaluatorTestsPath(evaluator, isolatedTestsRoot);
80934
+ const commandRoot = evaluator.root === "workspace" ? evaluatorWorkspace : isolatedTestsRoot;
80576
80935
  const command = { id: evaluator.id, ...evaluator.command };
80577
80936
  const environment = {
80578
80937
  BRAINBASE_BENCHMARK_WORKSPACE: evaluatorWorkspace,
@@ -80589,14 +80948,23 @@ async function evaluateOne(evaluator, spec, manifest, finalOutput, trajectory, f
80589
80948
  criterionResultPath = path88.join(privateResultRoot, "result.json");
80590
80949
  environment.BRAINBASE_BENCHMARK_CRITERIA_RESULT = criterionResultPath;
80591
80950
  }
80592
- const result2 = await runCommand(command, commandRoot, spec, context, environment);
80951
+ const result2 = await runCommand(command, commandRoot, spec, context, {
80952
+ additions: environment,
80953
+ descendantCleanup: "always"
80954
+ });
80593
80955
  const stdout = await writeLog(spec.logs_root, `${evaluator.id}.stdout.log`, result2.stdout, spec);
80594
80956
  const stderr = await writeLog(spec.logs_root, `${evaluator.id}.stderr.log`, result2.stderr, spec);
80595
80957
  let criterionResults;
80596
80958
  try {
80597
- criterionResults = evaluator.criterion_keys && criterionResultPath ? readCriterionResults(criterionResultPath, evaluator.criterion_keys, evaluator.allowed_evidence_ids ?? []) : undefined;
80959
+ criterionResults = evaluator.criterion_keys && criterionResultPath ? readCriterionResults(criterionResultPath, evaluator.criterion_keys, evaluator.allowed_evidence_ids ?? [], spec, result2.redactions) : undefined;
80598
80960
  } catch (error2) {
80599
- const normalized = stableError(error2);
80961
+ let normalized = stableError(error2);
80962
+ if (normalized?.code === "missing_criterion_result" && result2.exitCode !== 0) {
80963
+ normalized = structuredJudgeError(result2.stderr) ?? {
80964
+ code: "command_terminated",
80965
+ message: "sandbox evaluator terminated before writing its criterion result"
80966
+ };
80967
+ }
80600
80968
  return {
80601
80969
  ...base2,
80602
80970
  status: "errored",
@@ -80623,11 +80991,17 @@ async function evaluateOne(evaluator, spec, manifest, finalOutput, trajectory, f
80623
80991
  ...criterionResults ? { criterion_results: criterionResults } : {}
80624
80992
  };
80625
80993
  } finally {
80626
- for (const temporary of [privateResultRoot, isolatedWorkspace]) {
80627
- if (!temporary)
80628
- continue;
80629
- fs81.rmSync(temporary, { recursive: true, force: true });
80630
- context.temporaryRoots.delete(temporary);
80994
+ try {
80995
+ if (usesLiveWorkspace) {
80996
+ await verifyWorkspaceManifestUnchanged(manifest, spec, context);
80997
+ }
80998
+ } finally {
80999
+ for (const temporary of [privateResultRoot, isolatedTestsRoot, isolatedWorkspace]) {
81000
+ if (!temporary)
81001
+ continue;
81002
+ fs81.rmSync(temporary, { recursive: true, force: true });
81003
+ context.temporaryRoots.delete(temporary);
81004
+ }
80631
81005
  }
80632
81006
  }
80633
81007
  }
@@ -80672,6 +81046,7 @@ async function executeEvaluate(spec, context) {
80672
81046
  ];
80673
81047
  outputs.push(...frozenEvidenceRecords);
80674
81048
  context.outputs.push(...frozenEvidenceRecords);
81049
+ assertEvaluateOutputBudget(spec, context);
80675
81050
  assertBudget(context);
80676
81051
  const manifest = await workspaceManifest(spec, context);
80677
81052
  const manifestPath2 = path88.join(spec.logs_root, "candidate-workspace-manifest.json");
@@ -80684,6 +81059,7 @@ async function executeEvaluate(spec, context) {
80684
81059
  const manifestRecord = await recordFile(spec.logs_root, manifestPath2, "logs");
80685
81060
  outputs.push(manifestRecord);
80686
81061
  context.outputs.push(manifestRecord);
81062
+ assertEvaluateOutputBudget(spec, context);
80687
81063
  for (const artifactRelInput of spec.candidate_artifacts) {
80688
81064
  const artifactRel = workspaceRel(artifactRelInput);
80689
81065
  assertNoSymlinkTraversal(spec.workspace_root, artifactRel);
@@ -80701,11 +81077,13 @@ async function executeEvaluate(spec, context) {
80701
81077
  outputs.push(artifact);
80702
81078
  context.outputs.push(artifact);
80703
81079
  }
81080
+ assertEvaluateOutputBudget(spec, context);
80704
81081
  for (const candidateOutput of spec.candidate_outputs) {
80705
81082
  const copied = await copyCandidateOutput(candidateOutput, manifest, spec, context);
80706
81083
  outputs.push(...copied);
80707
81084
  context.outputs.push(...copied);
80708
81085
  }
81086
+ assertEvaluateOutputBudget(spec, context);
80709
81087
  if (spec.capture_workspace_archive) {
80710
81088
  const regularFiles = manifest.filter((entry) => entry.kind !== "symlink").map((entry) => entry.path);
80711
81089
  const archive = path88.join(spec.logs_root, "candidate-workspace.tar.gz");
@@ -80719,23 +81097,23 @@ async function executeEvaluate(spec, context) {
80719
81097
  const archiveRecord = await recordFile(spec.logs_root, archive, "logs");
80720
81098
  outputs.push(archiveRecord);
80721
81099
  context.outputs.push(archiveRecord);
81100
+ assertEvaluateOutputBudget(spec, context);
80722
81101
  }
80723
81102
  await verifyRecordsUnchanged(manifest, spec);
81103
+ const frozenWorkspaceRoot = await copyFrozenWorkspace(manifest, spec, context, spec.workspace_root);
80724
81104
  for (const reference of spec.references) {
80725
81105
  const referenceOutputs = await copyMaterial(reference, spec.staging_root, spec.tests_root, "tests", false, context);
80726
81106
  outputs.push(...referenceOutputs);
80727
81107
  context.outputs.push(...referenceOutputs);
80728
81108
  }
80729
- const frozenOutputCount = context.outputs.length;
80730
81109
  const evaluators = [];
80731
81110
  const evaluatorOrder = new Map(spec.evaluators.map((evaluator, index) => [evaluator.id, index]));
80732
81111
  const executionOrder = [...spec.evaluators].sort((left, right) => Number(left.type === "sandbox_command") - Number(right.type === "sandbox_command"));
80733
81112
  for (const evaluator of executionOrder) {
80734
81113
  assertBudget(context);
80735
81114
  const started = Date.now();
80736
- let requiredResultError;
80737
81115
  try {
80738
- const evaluated = await evaluateOne(evaluator, spec, manifest, finalOutput.buffer.toString("utf8"), trajectory, { finalOutputPath, trajectoryPath }, context);
81116
+ const evaluated = await evaluateOne(evaluator, spec, manifest, frozenWorkspaceRoot, finalOutput.buffer.toString("utf8"), trajectory, { finalOutputPath, trajectoryPath }, context);
80739
81117
  assertBudget(context);
80740
81118
  evaluators.push(evaluated);
80741
81119
  context.evaluators.push(evaluated);
@@ -80747,13 +81125,14 @@ async function executeEvaluate(spec, context) {
80747
81125
  outputs.push(evaluated.stderr);
80748
81126
  context.outputs.push(evaluated.stderr);
80749
81127
  }
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
81128
  } catch (error2) {
80756
81129
  const normalized = stableError(error2);
81130
+ let stdout;
81131
+ let stderr;
81132
+ if (error2 instanceof BenchmarkCommandExecutionError) {
81133
+ stdout = await writeLog(spec.logs_root, `${evaluator.id}.stdout.log`, error2.stdout, spec);
81134
+ stderr = await writeLog(spec.logs_root, `${evaluator.id}.stderr.log`, error2.stderr, spec);
81135
+ }
80757
81136
  const errored = {
80758
81137
  id: evaluator.id,
80759
81138
  type: evaluator.type,
@@ -80763,26 +81142,38 @@ async function executeEvaluate(spec, context) {
80763
81142
  verdict: null,
80764
81143
  engine: "brainbase-cli",
80765
81144
  engine_version: VERSION,
80766
- duration_ms: Date.now() - started,
81145
+ duration_ms: error2 instanceof BenchmarkCommandExecutionError ? error2.durationMs : Date.now() - started,
80767
81146
  details: {
80768
81147
  error_code: normalized?.code ?? "phase_failed",
80769
81148
  error_message: normalized?.message ?? "evaluator execution failed"
80770
- }
81149
+ },
81150
+ ...stdout ? { stdout } : {},
81151
+ ...stderr ? { stderr } : {}
80771
81152
  };
80772
81153
  evaluators.push(errored);
80773
81154
  context.evaluators.push(errored);
80774
- if (evaluator.required)
81155
+ if (stdout) {
81156
+ outputs.push(stdout);
81157
+ context.outputs.push(stdout);
81158
+ }
81159
+ if (stderr) {
81160
+ outputs.push(stderr);
81161
+ context.outputs.push(stderr);
81162
+ }
81163
+ if (!(error2 instanceof BenchmarkCommandExecutionError))
80775
81164
  throw error2;
80776
81165
  assertBudget(context);
80777
81166
  }
80778
- if (requiredResultError)
80779
- throw requiredResultError;
81167
+ if (evaluator.type === "sandbox_command") {
81168
+ assertEvaluateOutputBudget(spec, context);
81169
+ }
80780
81170
  }
80781
- await verifyRecordsUnchanged([...context.inputs, ...context.outputs.slice(0, frozenOutputCount)], spec);
81171
+ await verifyRecordsUnchanged([...context.inputs, ...context.outputs], spec);
80782
81172
  if (!verifyOwnedDirectory(spec.tests_root, "tests", spec) || !verifyOwnedDirectory(spec.logs_root, "logs", spec)) {
80783
81173
  throw new BenchmarkPhaseError("evidence_tampered", "benchmark evaluator changed an owned phase directory marker");
80784
81174
  }
80785
81175
  assertBudget(context);
81176
+ assertEvaluateOutputBudget(spec, context);
80786
81177
  evaluators.sort((left, right) => evaluatorOrder.get(left.id) - evaluatorOrder.get(right.id));
80787
81178
  return { outputs, evaluators };
80788
81179
  }
@@ -80809,6 +81200,67 @@ function stableError(error2) {
80809
81200
  message: error2 instanceof Error ? error2.message : "unknown benchmark phase failure"
80810
81201
  };
80811
81202
  }
81203
+ function compactOversizedResult(result2) {
81204
+ const actualBytes = Buffer.byteLength(`${JSON.stringify(result2, null, 2)}
81205
+ `);
81206
+ if (actualBytes <= MAX_PHASE_RESULT_BYTES)
81207
+ return result2;
81208
+ return {
81209
+ schema_version: result2.schema_version,
81210
+ cli_version: result2.cli_version,
81211
+ phase: result2.phase,
81212
+ attempt_id: result2.attempt_id,
81213
+ phase_id: result2.phase_id,
81214
+ spec_digest: result2.spec_digest,
81215
+ status: "failed",
81216
+ started_at: result2.started_at,
81217
+ completed_at: result2.completed_at,
81218
+ duration_ms: result2.duration_ms,
81219
+ steps: [],
81220
+ inputs: [],
81221
+ outputs: [],
81222
+ error: {
81223
+ code: "result_too_large",
81224
+ message: "benchmark phase result exceeds the 32 MiB contract limit",
81225
+ details: {
81226
+ actual_bytes: actualBytes,
81227
+ max_bytes: MAX_PHASE_RESULT_BYTES,
81228
+ step_count: result2.steps.length,
81229
+ input_count: result2.inputs.length,
81230
+ output_count: result2.outputs.length,
81231
+ evaluator_count: result2.evaluators?.length ?? 0
81232
+ }
81233
+ }
81234
+ };
81235
+ }
81236
+ function readCachedPhaseResult(resultPath) {
81237
+ let opened;
81238
+ try {
81239
+ opened = openRegularFileNoFollow(resultPath, "cached benchmark phase result");
81240
+ } catch {
81241
+ return;
81242
+ }
81243
+ try {
81244
+ if (opened.stat.size > MAX_PHASE_RESULT_BYTES)
81245
+ return;
81246
+ const chunks = [];
81247
+ let offset = 0;
81248
+ while (offset <= MAX_PHASE_RESULT_BYTES) {
81249
+ const buffer = Buffer.alloc(Math.min(64 * 1024, MAX_PHASE_RESULT_BYTES + 1 - offset));
81250
+ const bytesRead = fs81.readSync(opened.fd, buffer, 0, buffer.length, offset);
81251
+ if (bytesRead === 0) {
81252
+ return JSON.parse(Buffer.concat(chunks, offset).toString("utf8"));
81253
+ }
81254
+ chunks.push(buffer.subarray(0, bytesRead));
81255
+ offset += bytesRead;
81256
+ }
81257
+ return;
81258
+ } catch {
81259
+ return;
81260
+ } finally {
81261
+ fs81.closeSync(opened.fd);
81262
+ }
81263
+ }
80812
81264
  function rawIdentity(value) {
80813
81265
  if (!value || typeof value !== "object") {
80814
81266
  return { phase: "unknown", attemptId: null, phaseId: null };
@@ -80881,13 +81333,9 @@ function prepareBenchmarkPhaseInvocation(specPathInput, resultPathInput, expecte
80881
81333
  timeout_ms: spec.budget.timeout_ms
80882
81334
  };
80883
81335
  let cachedResult;
80884
- if (fs81.existsSync(resultPath)) {
80885
- try {
80886
- const cached2 = JSON.parse(fs81.readFileSync(resultPath, "utf8"));
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 {}
81336
+ const cached2 = readCachedPhaseResult(resultPath);
81337
+ 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") {
81338
+ cachedResult = cached2;
80891
81339
  }
80892
81340
  if (cachedResult) {
80893
81341
  if (spec.phase === "hydrate") {
@@ -80904,7 +81352,7 @@ function prepareBenchmarkPhaseInvocation(specPathInput, resultPathInput, expecte
80904
81352
  } catch (error2) {
80905
81353
  return {
80906
81354
  ok: false,
80907
- result: {
81355
+ result: compactOversizedResult({
80908
81356
  schema_version: SCHEMA_VERSION,
80909
81357
  cli_version: VERSION,
80910
81358
  phase: identity2.phase === "unknown" ? expectedPhase : identity2.phase,
@@ -80919,7 +81367,7 @@ function prepareBenchmarkPhaseInvocation(specPathInput, resultPathInput, expecte
80919
81367
  inputs: [],
80920
81368
  outputs: [],
80921
81369
  error: stableError(error2)
80922
- }
81370
+ })
80923
81371
  };
80924
81372
  }
80925
81373
  }
@@ -80968,6 +81416,7 @@ async function runBenchmarkPhase(specPathInput, resultPathInput, expectedPhase,
80968
81416
  let status = "failed";
80969
81417
  let error2;
80970
81418
  let context;
81419
+ let validatedSpec;
80971
81420
  let resultPathValidated = false;
80972
81421
  try {
80973
81422
  let bytes;
@@ -80987,6 +81436,7 @@ async function runBenchmarkPhase(specPathInput, resultPathInput, expectedPhase,
80987
81436
  }
80988
81437
  identity2 = rawIdentity(raw);
80989
81438
  const spec = BenchmarkSpecSchema.parse(raw);
81439
+ validatedSpec = spec;
80990
81440
  if (expectedPhase && spec.phase !== expectedPhase) {
80991
81441
  throw new BenchmarkPhaseError("phase_mismatch", `the ${expectedPhase} command cannot execute a ${spec.phase} spec`);
80992
81442
  }
@@ -80997,13 +81447,9 @@ async function runBenchmarkPhase(specPathInput, resultPathInput, expectedPhase,
80997
81447
  }
80998
81448
  resultPathValidated = true;
80999
81449
  let cachedResult;
81000
- if (fs81.existsSync(resultPath)) {
81001
- try {
81002
- const cached2 = JSON.parse(fs81.readFileSync(resultPath, "utf8"));
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 {}
81450
+ const cached2 = readCachedPhaseResult(resultPath);
81451
+ 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") {
81452
+ cachedResult = cached2;
81007
81453
  }
81008
81454
  if (cachedResult) {
81009
81455
  if (spec.phase === "hydrate") {
@@ -81046,7 +81492,7 @@ async function runBenchmarkPhase(specPathInput, resultPathInput, expectedPhase,
81046
81492
  fs81.rmSync(temporary, { recursive: true, force: true });
81047
81493
  }
81048
81494
  }
81049
- const result2 = {
81495
+ let result2 = {
81050
81496
  schema_version: SCHEMA_VERSION,
81051
81497
  cli_version: VERSION,
81052
81498
  phase: identity2.phase === "unknown" && expectedPhase ? expectedPhase : identity2.phase,
@@ -81064,8 +81510,22 @@ async function runBenchmarkPhase(specPathInput, resultPathInput, expectedPhase,
81064
81510
  ...error2 ? { error: error2 } : {}
81065
81511
  };
81066
81512
  if (!resultPathValidated || !context?.logsOwned) {
81513
+ result2 = compactOversizedResult(result2);
81067
81514
  return { exitCode: status === "succeeded" ? 0 : 1, result: result2 };
81068
81515
  }
81516
+ if (status === "succeeded" && validatedSpec?.phase === "evaluate") {
81517
+ try {
81518
+ const resultBytes = Buffer.byteLength(`${JSON.stringify(result2, null, 2)}
81519
+ `);
81520
+ assertEvaluateOutputBudget(validatedSpec, context, resultBytes);
81521
+ } catch (budgetError) {
81522
+ status = "failed";
81523
+ result2.status = "failed";
81524
+ result2.error = stableError(budgetError);
81525
+ }
81526
+ }
81527
+ result2 = compactOversizedResult(result2);
81528
+ status = result2.status;
81069
81529
  try {
81070
81530
  writeJsonAtomic(resultPath, result2);
81071
81531
  } catch (writeError) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainbase-labs/cli",
3
- "version": "0.25.0-eng1209.1",
3
+ "version": "0.25.0-eng1209.11",
4
4
  "description": "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
5
5
  "type": "module",
6
6
  "bin": {