@brainbase-labs/cli 0.25.0-eng1209.7 → 0.25.0-eng1209.9

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 +242 -59
  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.7",
36011
+ version: "0.25.0-eng1209.9",
36012
36012
  description: "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
36013
36013
  type: "module",
36014
36014
  bin: {
@@ -78487,11 +78487,15 @@ var MAX_ARCHIVE_EXTRACTED_BYTES = 2 * 1024 * 1024 * 1024;
78487
78487
  var MAX_ARCHIVE_SCAN_BYTES = 2 * 1024 * 1024 * 1024;
78488
78488
  var MAX_SANDBOX_COMMANDS = 20;
78489
78489
  var MAX_CRITERIA_PER_EVALUATOR = 200;
78490
- var MAX_CRITERIA_RESULT_BYTES = 1024 * 1024;
78490
+ var MAX_CRITERIA_RESULT_BYTES = 24 * 1024 * 1024;
78491
+ var MAX_CRITERIA_RESULT_PAYLOAD_BYTES = 20 * 1024 * 1024;
78491
78492
  var MAX_CRITERION_EXPLANATION_LENGTH = 16384;
78492
78493
  var MAX_CRITERION_EVIDENCE_IDS = 100;
78493
78494
  var MAX_CRITERION_EVIDENCE_ID_LENGTH = 256;
78494
78495
  var MAX_ALLOWED_EVIDENCE_IDS = 1e4;
78496
+ var COMMAND_PROCESS_MARKER_ENV = "BRAINBASE_BENCHMARK_COMMAND_MARKER";
78497
+ var COMMAND_PROCESS_POLL_MS = 10;
78498
+ var COMMAND_PROCESS_CLEANUP_MS = 500;
78495
78499
  var RESERVED_WORKSPACE_PATHS = new Set([
78496
78500
  ".brainbase",
78497
78501
  ".git",
@@ -78712,6 +78716,20 @@ var BenchmarkSpecSchema = exports_external.discriminatedUnion("phase", [
78712
78716
  HydrateSpecSchema,
78713
78717
  EvaluateSpecSchema
78714
78718
  ]).superRefine((value, context) => {
78719
+ if (Object.hasOwn(value.environment, COMMAND_PROCESS_MARKER_ENV)) {
78720
+ context.addIssue({
78721
+ code: exports_external.ZodIssueCode.custom,
78722
+ path: ["environment", COMMAND_PROCESS_MARKER_ENV],
78723
+ message: `${COMMAND_PROCESS_MARKER_ENV} is reserved for command supervision`
78724
+ });
78725
+ }
78726
+ if (value.secret_env.includes(COMMAND_PROCESS_MARKER_ENV)) {
78727
+ context.addIssue({
78728
+ code: exports_external.ZodIssueCode.custom,
78729
+ path: ["secret_env"],
78730
+ message: `${COMMAND_PROCESS_MARKER_ENV} is reserved for command supervision`
78731
+ });
78732
+ }
78715
78733
  for (const name of Object.keys(value.environment)) {
78716
78734
  if (SENSITIVE_ENV_NAME_RE.test(name)) {
78717
78735
  context.addIssue({
@@ -79920,7 +79938,7 @@ function buildEnvironment(spec, secretNames, additions = {}) {
79920
79938
  Object.assign(env3, additions);
79921
79939
  return env3;
79922
79940
  }
79923
- function redactCommandOutput(data, spec) {
79941
+ function redactCommandOutput(data, spec, additionalSecrets = []) {
79924
79942
  let value = data.toString("utf8");
79925
79943
  const sensitiveNames = new Set([
79926
79944
  ...spec.secret_env,
@@ -79931,6 +79949,10 @@ function redactCommandOutput(data, spec) {
79931
79949
  if (secret)
79932
79950
  value = value.split(secret).join("[REDACTED]");
79933
79951
  }
79952
+ for (const secret of additionalSecrets) {
79953
+ if (secret)
79954
+ value = value.split(secret).join("[REDACTED]");
79955
+ }
79934
79956
  return Buffer.from(value);
79935
79957
  }
79936
79958
  function descendantPids(parentPid) {
@@ -79982,7 +80004,99 @@ function terminate(child) {
79982
80004
  child.kill("SIGKILL");
79983
80005
  }
79984
80006
  }
79985
- async function runCommand(command, root, spec, context, additions = {}) {
80007
+ function markedProcessPids(marker) {
80008
+ const assignment = `${COMMAND_PROCESS_MARKER_ENV}=${marker}`;
80009
+ if (process.platform === "linux") {
80010
+ const matches2 = [];
80011
+ let entries;
80012
+ try {
80013
+ entries = fs81.readdirSync("/proc");
80014
+ } catch {
80015
+ return matches2;
80016
+ }
80017
+ for (const entry of entries) {
80018
+ if (!/^\d+$/.test(entry))
80019
+ continue;
80020
+ const pid = Number(entry);
80021
+ if (pid === process.pid)
80022
+ continue;
80023
+ try {
80024
+ const environment = fs81.readFileSync(path88.join("/proc", entry, "environ"), "utf8");
80025
+ if (environment.split("\x00").includes(assignment))
80026
+ matches2.push(pid);
80027
+ } catch {}
80028
+ }
80029
+ return matches2;
80030
+ }
80031
+ if (process.platform === "darwin") {
80032
+ try {
80033
+ const output = execFileSync2("ps", ["eww", "-axo", "pid=,command="], {
80034
+ encoding: "utf8",
80035
+ maxBuffer: 16 * 1024 * 1024,
80036
+ stdio: ["ignore", "pipe", "ignore"]
80037
+ });
80038
+ const matches2 = [];
80039
+ for (const line of output.split(`
80040
+ `)) {
80041
+ const match = line.match(/^\s*(\d+)\s+(.*)$/);
80042
+ if (!match || !match[2].includes(assignment))
80043
+ continue;
80044
+ const pid = Number(match[1]);
80045
+ if (Number.isInteger(pid) && pid !== process.pid)
80046
+ matches2.push(pid);
80047
+ }
80048
+ return matches2;
80049
+ } catch {
80050
+ return [];
80051
+ }
80052
+ }
80053
+ return [];
80054
+ }
80055
+ function processExists(pid) {
80056
+ if (process.platform === "linux") {
80057
+ try {
80058
+ const stat = fs81.readFileSync(path88.join("/proc", String(pid), "stat"), "utf8");
80059
+ const commandEnd = stat.lastIndexOf(")");
80060
+ const state = commandEnd >= 0 ? stat.slice(commandEnd + 2, commandEnd + 3) : "";
80061
+ if (state === "Z" || state === "X")
80062
+ return false;
80063
+ } catch (error2) {
80064
+ if (error2.code === "ENOENT")
80065
+ return false;
80066
+ }
80067
+ }
80068
+ try {
80069
+ process.kill(pid, 0);
80070
+ return true;
80071
+ } catch (error2) {
80072
+ return error2.code !== "ESRCH";
80073
+ }
80074
+ }
80075
+ async function terminateCommandProcesses(child, marker, observedDescendants) {
80076
+ if (child.pid !== undefined) {
80077
+ for (const pid of descendantPids(child.pid))
80078
+ observedDescendants.add(pid);
80079
+ }
80080
+ terminate(child);
80081
+ const deadline = Date.now() + COMMAND_PROCESS_CLEANUP_MS;
80082
+ while (true) {
80083
+ for (const pid of markedProcessPids(marker))
80084
+ observedDescendants.add(pid);
80085
+ const active = [...observedDescendants].filter(processExists);
80086
+ for (const pid of active) {
80087
+ try {
80088
+ process.kill(pid, "SIGKILL");
80089
+ } catch {}
80090
+ }
80091
+ if (active.length === 0)
80092
+ return;
80093
+ if (Date.now() >= deadline) {
80094
+ throw new BenchmarkPhaseError("command_cleanup_failed", "command descendants remained after bounded cleanup");
80095
+ }
80096
+ await new Promise((resolve) => setTimeout(resolve, COMMAND_PROCESS_POLL_MS));
80097
+ }
80098
+ }
80099
+ async function runCommand(command, root, spec, context, options = {}) {
79986
80100
  const cwdRel = normalizedRootRelative(command.cwd);
79987
80101
  assertNoSymlinkTraversal(root, cwdRel);
79988
80102
  const cwd2 = path88.resolve(root, cwdRel);
@@ -80001,27 +80115,56 @@ async function runCommand(command, root, spec, context, additions = {}) {
80001
80115
  const reachesPhaseDeadline = command.timeout_ms === undefined || command.timeout_ms >= remainingMs;
80002
80116
  const timeoutMs2 = reachesPhaseDeadline ? remainingMs : command.timeout_ms;
80003
80117
  const started = Date.now();
80118
+ const commandMarker = crypto6.randomBytes(32).toString("hex");
80004
80119
  return await new Promise((resolve, reject2) => {
80005
80120
  const child = spawn4(command.argv[0], command.argv.slice(1), {
80006
80121
  cwd: cwd2,
80007
- env: buildEnvironment(spec, command.secret_env, additions),
80122
+ env: buildEnvironment(spec, command.secret_env, {
80123
+ ...options.additions,
80124
+ [COMMAND_PROCESS_MARKER_ENV]: commandMarker
80125
+ }),
80008
80126
  stdio: ["ignore", "pipe", "pipe"],
80009
80127
  detached: process.platform !== "win32"
80010
80128
  });
80011
80129
  const stdout = [];
80012
80130
  const stderr = [];
80131
+ const observedDescendants = new Set;
80013
80132
  let captured = 0;
80014
80133
  let settled = false;
80015
80134
  let timer;
80135
+ let observer;
80136
+ let cleanup;
80137
+ const observeDescendants = () => {
80138
+ if (child.pid === undefined)
80139
+ return;
80140
+ for (const pid of descendantPids(child.pid))
80141
+ observedDescendants.add(pid);
80142
+ };
80143
+ const cleanupProcesses = () => {
80144
+ if (observer)
80145
+ clearInterval(observer);
80146
+ cleanup ??= terminateCommandProcesses(child, commandMarker, observedDescendants);
80147
+ return cleanup;
80148
+ };
80149
+ const sanitizedOutput = (chunks) => redactCommandOutput(Buffer.concat(chunks), spec, [commandMarker]);
80150
+ const sanitizedError = (error2) => {
80151
+ if (!(error2 instanceof BenchmarkCommandExecutionError))
80152
+ return error2;
80153
+ return new BenchmarkCommandExecutionError(error2.code, error2.message, sanitizedOutput(stdout), sanitizedOutput(stderr), error2.durationMs);
80154
+ };
80016
80155
  const fail = (error2) => {
80017
80156
  if (settled)
80018
80157
  return;
80019
80158
  settled = true;
80020
80159
  if (timer)
80021
80160
  clearTimeout(timer);
80022
- terminate(child);
80023
- reject2(error2);
80161
+ cleanupProcesses().then(() => reject2(sanitizedError(error2)), reject2);
80024
80162
  };
80163
+ if (process.platform !== "linux") {
80164
+ observeDescendants();
80165
+ observer = setInterval(observeDescendants, COMMAND_PROCESS_POLL_MS);
80166
+ observer.unref();
80167
+ }
80025
80168
  const capture = (target, chunk2) => {
80026
80169
  if (settled)
80027
80170
  return;
@@ -80033,11 +80176,23 @@ async function runCommand(command, root, spec, context, additions = {}) {
80033
80176
  }
80034
80177
  target.push(chunk2);
80035
80178
  };
80179
+ const finish = (code, signal) => {
80180
+ if (observer)
80181
+ clearInterval(observer);
80182
+ if (code === null) {
80183
+ reject2(new BenchmarkCommandExecutionError("command_terminated", `command was terminated by ${signal ?? "an unknown signal"}: ${command.id}`, sanitizedOutput(stdout), sanitizedOutput(stderr), Date.now() - started));
80184
+ return;
80185
+ }
80186
+ resolve({
80187
+ exitCode: code,
80188
+ stdout: sanitizedOutput(stdout),
80189
+ stderr: sanitizedOutput(stderr),
80190
+ durationMs: Date.now() - started,
80191
+ redactions: [commandMarker]
80192
+ });
80193
+ };
80036
80194
  child.stdout?.on("data", (chunk2) => capture(stdout, chunk2));
80037
80195
  child.stderr?.on("data", (chunk2) => capture(stderr, chunk2));
80038
- child.once("exit", () => {
80039
- terminate(child);
80040
- });
80041
80196
  child.on("error", (error2) => {
80042
80197
  fail(new BenchmarkCommandExecutionError("command_start_failed", `failed to start ${command.id}: ${error2.message}`, Buffer.concat(stdout), Buffer.concat(stderr), Date.now() - started));
80043
80198
  });
@@ -80048,21 +80203,21 @@ async function runCommand(command, root, spec, context, additions = {}) {
80048
80203
  }
80049
80204
  fail(new BenchmarkCommandExecutionError("command_timeout", `command timed out: ${command.id}`, Buffer.concat(stdout), Buffer.concat(stderr), Date.now() - started));
80050
80205
  }, timeoutMs2);
80051
- child.on("close", (code, signal) => {
80206
+ child.on("exit", (code, signal) => {
80052
80207
  if (settled)
80053
80208
  return;
80054
80209
  settled = true;
80055
80210
  clearTimeout(timer);
80056
- if (code === null) {
80057
- reject2(new BenchmarkCommandExecutionError("command_terminated", `command was terminated by ${signal ?? "an unknown signal"}: ${command.id}`, Buffer.concat(stdout), Buffer.concat(stderr), Date.now() - started));
80058
- return;
80211
+ if (options.descendantCleanup === "always" || code === null || code !== 0) {
80212
+ cleanupProcesses().then(() => finish(code, signal), reject2);
80213
+ } else {
80214
+ if (observer)
80215
+ clearInterval(observer);
80216
+ child.unref();
80217
+ child.stdout?.unref?.();
80218
+ child.stderr?.unref?.();
80219
+ finish(code, signal);
80059
80220
  }
80060
- resolve({
80061
- exitCode: code,
80062
- stdout: Buffer.concat(stdout),
80063
- stderr: Buffer.concat(stderr),
80064
- durationMs: Date.now() - started
80065
- });
80066
80221
  });
80067
80222
  });
80068
80223
  }
@@ -80140,7 +80295,9 @@ async function executeHydrate(spec, context) {
80140
80295
  assertBudget(context);
80141
80296
  let result2;
80142
80297
  try {
80143
- result2 = await runCommand(command, spec.workspace_root, spec, context);
80298
+ result2 = await runCommand(command, spec.workspace_root, spec, context, {
80299
+ descendantCleanup: "failure_only"
80300
+ });
80144
80301
  } catch (error2) {
80145
80302
  context.steps.push({
80146
80303
  id: command.id,
@@ -80255,6 +80412,13 @@ async function workspaceManifest(spec, context) {
80255
80412
  }
80256
80413
  return records.sort((a3, b4) => a3.path.localeCompare(b4.path));
80257
80414
  }
80415
+ async function verifyWorkspaceManifestUnchanged(expected, spec, context) {
80416
+ await verifyRecordsUnchanged(expected, spec);
80417
+ const actual = await workspaceManifest(spec, context);
80418
+ if (JSON.stringify(actual) !== JSON.stringify(expected)) {
80419
+ throw new BenchmarkPhaseError("evidence_tampered", "candidate workspace changed during read-only evaluation");
80420
+ }
80421
+ }
80258
80422
  function treeBytes(root, context) {
80259
80423
  let totalBytes = 0;
80260
80424
  const stack = [path88.resolve(root)];
@@ -80321,6 +80485,8 @@ function manifestDirectories(manifest, context) {
80321
80485
  const directories = new Set;
80322
80486
  for (const entry of manifest) {
80323
80487
  assertBudget(context);
80488
+ if (entry.kind === "symlink")
80489
+ continue;
80324
80490
  let current = path88.posix.dirname(entry.path);
80325
80491
  while (current !== ".") {
80326
80492
  assertBudget(context);
@@ -80338,9 +80504,8 @@ function candidateOutputFiles(output, manifest, context) {
80338
80504
  assertBudget(context);
80339
80505
  if (!matcher.test(entry.path))
80340
80506
  continue;
80341
- if (entry.kind === "symlink") {
80342
- throw new BenchmarkPhaseError("unsafe_path", `candidate output ${output.id} matches a symlink`);
80343
- }
80507
+ if (entry.kind === "symlink")
80508
+ continue;
80344
80509
  matched.push(entry);
80345
80510
  }
80346
80511
  return {
@@ -80348,12 +80513,6 @@ function candidateOutputFiles(output, manifest, context) {
80348
80513
  files: matched
80349
80514
  };
80350
80515
  }
80351
- for (const entry of manifest) {
80352
- assertBudget(context);
80353
- if (entry.kind === "symlink" && matcher.test(entry.path)) {
80354
- throw new BenchmarkPhaseError("unsafe_path", `candidate output ${output.id} matches a symlink`);
80355
- }
80356
- }
80357
80516
  const directories = [];
80358
80517
  for (const directory of manifestDirectories(manifest, context)) {
80359
80518
  assertBudget(context);
@@ -80368,9 +80527,8 @@ function candidateOutputFiles(output, manifest, context) {
80368
80527
  assertBudget(context);
80369
80528
  if (!entry.path.startsWith(prefix))
80370
80529
  continue;
80371
- if (entry.kind === "symlink") {
80372
- throw new BenchmarkPhaseError("unsafe_path", `candidate output ${output.id} contains a symlink`);
80373
- }
80530
+ if (entry.kind === "symlink")
80531
+ continue;
80374
80532
  selected.set(entry.path, entry);
80375
80533
  }
80376
80534
  }
@@ -80416,7 +80574,7 @@ async function copyCandidateOutput(output, manifest, spec, context) {
80416
80574
  }
80417
80575
  return copied;
80418
80576
  }
80419
- async function copyFrozenWorkspace(manifest, spec, context, sourceRoot = spec.workspace_root, preserveUnsafeSymlinks = false) {
80577
+ async function copyFrozenWorkspace(manifest, spec, context, sourceRoot = spec.workspace_root, symlinkPolicy = "preserve") {
80420
80578
  const destinationRoot = fs81.mkdtempSync(path88.join(os16.tmpdir(), "brainbase-benchmark-workspace-"));
80421
80579
  fs81.chmodSync(destinationRoot, 448);
80422
80580
  context.temporaryRoots.add(destinationRoot);
@@ -80430,12 +80588,9 @@ async function copyFrozenWorkspace(manifest, spec, context, sourceRoot = spec.wo
80430
80588
  if (target === null || Buffer.byteLength(target) !== frozenFile.size || sha256(target) !== frozenFile.sha256) {
80431
80589
  throw new BenchmarkPhaseError("evidence_tampered", `workspace changed after it was frozen: ${frozenFile.path}`);
80432
80590
  }
80433
- if (!preserveUnsafeSymlinks && path88.isAbsolute(target)) {
80434
- throw new BenchmarkPhaseError("unsafe_workspace_symlink", `sandbox evaluator workspace cannot reproduce an absolute symlink: ${frozenFile.path}`);
80435
- }
80436
80591
  const resolvedTarget = path88.resolve(path88.dirname(source), target);
80437
- if (!preserveUnsafeSymlinks && !isWithin(sourceRoot, resolvedTarget)) {
80438
- throw new BenchmarkPhaseError("unsafe_workspace_symlink", `sandbox evaluator workspace symlink escapes the workspace: ${frozenFile.path}`);
80592
+ if (symlinkPolicy === "contained_relative_only" && (path88.isAbsolute(target) || !isWithin(sourceRoot, resolvedTarget))) {
80593
+ continue;
80439
80594
  }
80440
80595
  fs81.mkdirSync(path88.dirname(destination), { recursive: true, mode: 448 });
80441
80596
  fs81.symlinkSync(target, destination);
@@ -80449,11 +80604,15 @@ async function copyFrozenWorkspace(manifest, spec, context, sourceRoot = spec.wo
80449
80604
  }
80450
80605
  return destinationRoot;
80451
80606
  }
80452
- async function copyEvaluatorTests(spec, context) {
80607
+ async function copyEvaluatorTests(spec, evaluator, context) {
80453
80608
  const destinationRoot = fs81.mkdtempSync(path88.join(os16.tmpdir(), "brainbase-benchmark-tests-"));
80454
80609
  fs81.chmodSync(destinationRoot, 448);
80455
80610
  context.temporaryRoots.add(destinationRoot);
80456
- const stack = [{ source: spec.tests_root, destination: destinationRoot }];
80611
+ const sourceRoot = evaluatorTestsPath(evaluator, spec.tests_root);
80612
+ const relativeRoot = evaluator.tests_path ? normalizedRootRelative(evaluator.tests_path) : ".";
80613
+ const destinationStart = relativeRoot === "." ? destinationRoot : path88.resolve(destinationRoot, relativeRoot);
80614
+ fs81.mkdirSync(destinationStart, { recursive: true, mode: 448 });
80615
+ const stack = [{ source: sourceRoot, destination: destinationStart }];
80457
80616
  while (stack.length > 0) {
80458
80617
  assertBudget(context);
80459
80618
  const current = stack.pop();
@@ -80515,8 +80674,16 @@ var CriterionResultSchema = exports_external.object({
80515
80674
  });
80516
80675
  var CriteriaResultFileSchema = exports_external.object({
80517
80676
  criteria: exports_external.array(CriterionResultSchema).max(MAX_CRITERIA_PER_EVALUATOR)
80518
- }).strict();
80519
- function readCriterionResults(resultPath, criterionKeys, allowedEvidenceIds, spec) {
80677
+ }).strict().superRefine((value, context) => {
80678
+ if (Buffer.byteLength(JSON.stringify(value)) > MAX_CRITERIA_RESULT_PAYLOAD_BYTES) {
80679
+ context.addIssue({
80680
+ code: exports_external.ZodIssueCode.custom,
80681
+ path: ["criteria"],
80682
+ message: "criterion result exceeds the 20 MiB aggregate payload limit"
80683
+ });
80684
+ }
80685
+ });
80686
+ function readCriterionResults(resultPath, criterionKeys, allowedEvidenceIds, spec, additionalSecrets = []) {
80520
80687
  let opened;
80521
80688
  try {
80522
80689
  opened = openRegularFileNoFollow(resultPath, "criterion result", path88.dirname(resultPath));
@@ -80528,7 +80695,7 @@ function readCriterionResults(resultPath, criterionKeys, allowedEvidenceIds, spe
80528
80695
  }
80529
80696
  try {
80530
80697
  if (opened.stat.size > MAX_CRITERIA_RESULT_BYTES) {
80531
- throw new BenchmarkPhaseError("invalid_criterion_result", "criterion result exceeds the 1 MiB limit");
80698
+ throw new BenchmarkPhaseError("invalid_criterion_result", "criterion result exceeds the 24 MiB file limit");
80532
80699
  }
80533
80700
  let raw;
80534
80701
  try {
@@ -80564,7 +80731,7 @@ function readCriterionResults(resultPath, criterionKeys, allowedEvidenceIds, spe
80564
80731
  const criterion = byKey.get(key2);
80565
80732
  return {
80566
80733
  ...criterion,
80567
- explanation: redactCommandOutput(Buffer.from(criterion.explanation), spec).toString("utf8")
80734
+ explanation: redactCommandOutput(Buffer.from(criterion.explanation), spec, additionalSecrets).toString("utf8")
80568
80735
  };
80569
80736
  });
80570
80737
  } finally {
@@ -80624,9 +80791,12 @@ async function evaluateOne(evaluator, spec, manifest, frozenWorkspaceRoot, final
80624
80791
  cwd: ".",
80625
80792
  secret_env: []
80626
80793
  }, spec.tests_root, spec, context, {
80627
- BB_REGEX_INPUT: frozenEvidence.finalOutputPath,
80628
- BB_REGEX_PATTERN_B64: Buffer.from(assertion.pattern).toString("base64"),
80629
- BB_REGEX_FLAGS: assertion.flags
80794
+ additions: {
80795
+ BB_REGEX_INPUT: frozenEvidence.finalOutputPath,
80796
+ BB_REGEX_PATTERN_B64: Buffer.from(assertion.pattern).toString("base64"),
80797
+ BB_REGEX_FLAGS: assertion.flags
80798
+ },
80799
+ descendantCleanup: "always"
80630
80800
  });
80631
80801
  if (regexResult.exitCode === 2) {
80632
80802
  throw new BenchmarkPhaseError("invalid_evaluator", `invalid regex in evaluator: ${evaluator.id}`);
@@ -80723,10 +80893,14 @@ async function evaluateOne(evaluator, spec, manifest, frozenWorkspaceRoot, final
80723
80893
  let isolatedWorkspace;
80724
80894
  let isolatedTestsRoot;
80725
80895
  let privateResultRoot;
80896
+ const usesLiveWorkspace = evaluator.workspace_mode === "read_only";
80726
80897
  try {
80727
- const evaluatorWorkspace = await copyFrozenWorkspace(manifest, spec, context, frozenWorkspaceRoot);
80728
- isolatedWorkspace = evaluatorWorkspace;
80729
- isolatedTestsRoot = await copyEvaluatorTests(spec, context);
80898
+ const evaluatorWorkspace = usesLiveWorkspace ? spec.workspace_root : await copyFrozenWorkspace(manifest, spec, context, frozenWorkspaceRoot, "contained_relative_only");
80899
+ if (!usesLiveWorkspace)
80900
+ isolatedWorkspace = evaluatorWorkspace;
80901
+ else
80902
+ await verifyWorkspaceManifestUnchanged(manifest, spec, context);
80903
+ isolatedTestsRoot = await copyEvaluatorTests(spec, evaluator, context);
80730
80904
  const testsPath = evaluatorTestsPath(evaluator, isolatedTestsRoot);
80731
80905
  const commandRoot = evaluator.root === "workspace" ? evaluatorWorkspace : isolatedTestsRoot;
80732
80906
  const command = { id: evaluator.id, ...evaluator.command };
@@ -80745,12 +80919,15 @@ async function evaluateOne(evaluator, spec, manifest, frozenWorkspaceRoot, final
80745
80919
  criterionResultPath = path88.join(privateResultRoot, "result.json");
80746
80920
  environment.BRAINBASE_BENCHMARK_CRITERIA_RESULT = criterionResultPath;
80747
80921
  }
80748
- const result2 = await runCommand(command, commandRoot, spec, context, environment);
80922
+ const result2 = await runCommand(command, commandRoot, spec, context, {
80923
+ additions: environment,
80924
+ descendantCleanup: "always"
80925
+ });
80749
80926
  const stdout = await writeLog(spec.logs_root, `${evaluator.id}.stdout.log`, result2.stdout, spec);
80750
80927
  const stderr = await writeLog(spec.logs_root, `${evaluator.id}.stderr.log`, result2.stderr, spec);
80751
80928
  let criterionResults;
80752
80929
  try {
80753
- criterionResults = evaluator.criterion_keys && criterionResultPath ? readCriterionResults(criterionResultPath, evaluator.criterion_keys, evaluator.allowed_evidence_ids ?? [], spec) : undefined;
80930
+ criterionResults = evaluator.criterion_keys && criterionResultPath ? readCriterionResults(criterionResultPath, evaluator.criterion_keys, evaluator.allowed_evidence_ids ?? [], spec, result2.redactions) : undefined;
80754
80931
  } catch (error2) {
80755
80932
  const normalized = stableError(error2);
80756
80933
  return {
@@ -80779,11 +80956,17 @@ async function evaluateOne(evaluator, spec, manifest, frozenWorkspaceRoot, final
80779
80956
  ...criterionResults ? { criterion_results: criterionResults } : {}
80780
80957
  };
80781
80958
  } finally {
80782
- for (const temporary of [privateResultRoot, isolatedTestsRoot, isolatedWorkspace]) {
80783
- if (!temporary)
80784
- continue;
80785
- fs81.rmSync(temporary, { recursive: true, force: true });
80786
- context.temporaryRoots.delete(temporary);
80959
+ try {
80960
+ if (usesLiveWorkspace) {
80961
+ await verifyWorkspaceManifestUnchanged(manifest, spec, context);
80962
+ }
80963
+ } finally {
80964
+ for (const temporary of [privateResultRoot, isolatedTestsRoot, isolatedWorkspace]) {
80965
+ if (!temporary)
80966
+ continue;
80967
+ fs81.rmSync(temporary, { recursive: true, force: true });
80968
+ context.temporaryRoots.delete(temporary);
80969
+ }
80787
80970
  }
80788
80971
  }
80789
80972
  }
@@ -80882,7 +81065,7 @@ async function executeEvaluate(spec, context) {
80882
81065
  assertEvaluateOutputBudget(spec, context);
80883
81066
  }
80884
81067
  await verifyRecordsUnchanged(manifest, spec);
80885
- const frozenWorkspaceRoot = await copyFrozenWorkspace(manifest, spec, context, spec.workspace_root, true);
81068
+ const frozenWorkspaceRoot = await copyFrozenWorkspace(manifest, spec, context, spec.workspace_root);
80886
81069
  for (const reference of spec.references) {
80887
81070
  const referenceOutputs = await copyMaterial(reference, spec.staging_root, spec.tests_root, "tests", false, context);
80888
81071
  outputs.push(...referenceOutputs);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainbase-labs/cli",
3
- "version": "0.25.0-eng1209.7",
3
+ "version": "0.25.0-eng1209.9",
4
4
  "description": "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
5
5
  "type": "module",
6
6
  "bin": {