@brainbase-labs/cli 0.25.0-eng1209.8 → 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 +84 -53
  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.8",
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,7 +78487,8 @@ 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;
@@ -80052,6 +80053,18 @@ function markedProcessPids(marker) {
80052
80053
  return [];
80053
80054
  }
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
+ }
80055
80068
  try {
80056
80069
  process.kill(pid, 0);
80057
80070
  return true;
@@ -80083,7 +80096,7 @@ async function terminateCommandProcesses(child, marker, observedDescendants) {
80083
80096
  await new Promise((resolve) => setTimeout(resolve, COMMAND_PROCESS_POLL_MS));
80084
80097
  }
80085
80098
  }
80086
- async function runCommand(command, root, spec, context, additions = {}) {
80099
+ async function runCommand(command, root, spec, context, options = {}) {
80087
80100
  const cwdRel = normalizedRootRelative(command.cwd);
80088
80101
  assertNoSymlinkTraversal(root, cwdRel);
80089
80102
  const cwd2 = path88.resolve(root, cwdRel);
@@ -80107,7 +80120,7 @@ async function runCommand(command, root, spec, context, additions = {}) {
80107
80120
  const child = spawn4(command.argv[0], command.argv.slice(1), {
80108
80121
  cwd: cwd2,
80109
80122
  env: buildEnvironment(spec, command.secret_env, {
80110
- ...additions,
80123
+ ...options.additions,
80111
80124
  [COMMAND_PROCESS_MARKER_ENV]: commandMarker
80112
80125
  }),
80113
80126
  stdio: ["ignore", "pipe", "pipe"],
@@ -80163,13 +80176,23 @@ async function runCommand(command, root, spec, context, additions = {}) {
80163
80176
  }
80164
80177
  target.push(chunk2);
80165
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
+ };
80166
80194
  child.stdout?.on("data", (chunk2) => capture(stdout, chunk2));
80167
80195
  child.stderr?.on("data", (chunk2) => capture(stderr, chunk2));
80168
- child.once("exit", () => {
80169
- if (timer)
80170
- clearTimeout(timer);
80171
- cleanupProcesses();
80172
- });
80173
80196
  child.on("error", (error2) => {
80174
80197
  fail(new BenchmarkCommandExecutionError("command_start_failed", `failed to start ${command.id}: ${error2.message}`, Buffer.concat(stdout), Buffer.concat(stderr), Date.now() - started));
80175
80198
  });
@@ -80180,24 +80203,21 @@ async function runCommand(command, root, spec, context, additions = {}) {
80180
80203
  }
80181
80204
  fail(new BenchmarkCommandExecutionError("command_timeout", `command timed out: ${command.id}`, Buffer.concat(stdout), Buffer.concat(stderr), Date.now() - started));
80182
80205
  }, timeoutMs2);
80183
- child.on("close", (code, signal) => {
80206
+ child.on("exit", (code, signal) => {
80184
80207
  if (settled)
80185
80208
  return;
80186
80209
  settled = true;
80187
80210
  clearTimeout(timer);
80188
- cleanupProcesses().then(() => {
80189
- if (code === null) {
80190
- reject2(new BenchmarkCommandExecutionError("command_terminated", `command was terminated by ${signal ?? "an unknown signal"}: ${command.id}`, sanitizedOutput(stdout), sanitizedOutput(stderr), Date.now() - started));
80191
- return;
80192
- }
80193
- resolve({
80194
- exitCode: code,
80195
- stdout: sanitizedOutput(stdout),
80196
- stderr: sanitizedOutput(stderr),
80197
- durationMs: Date.now() - started,
80198
- redactions: [commandMarker]
80199
- });
80200
- }, reject2);
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);
80220
+ }
80201
80221
  });
80202
80222
  });
80203
80223
  }
@@ -80275,7 +80295,9 @@ async function executeHydrate(spec, context) {
80275
80295
  assertBudget(context);
80276
80296
  let result2;
80277
80297
  try {
80278
- result2 = await runCommand(command, spec.workspace_root, spec, context);
80298
+ result2 = await runCommand(command, spec.workspace_root, spec, context, {
80299
+ descendantCleanup: "failure_only"
80300
+ });
80279
80301
  } catch (error2) {
80280
80302
  context.steps.push({
80281
80303
  id: command.id,
@@ -80463,6 +80485,8 @@ function manifestDirectories(manifest, context) {
80463
80485
  const directories = new Set;
80464
80486
  for (const entry of manifest) {
80465
80487
  assertBudget(context);
80488
+ if (entry.kind === "symlink")
80489
+ continue;
80466
80490
  let current = path88.posix.dirname(entry.path);
80467
80491
  while (current !== ".") {
80468
80492
  assertBudget(context);
@@ -80480,9 +80504,8 @@ function candidateOutputFiles(output, manifest, context) {
80480
80504
  assertBudget(context);
80481
80505
  if (!matcher.test(entry.path))
80482
80506
  continue;
80483
- if (entry.kind === "symlink") {
80484
- throw new BenchmarkPhaseError("unsafe_path", `candidate output ${output.id} matches a symlink`);
80485
- }
80507
+ if (entry.kind === "symlink")
80508
+ continue;
80486
80509
  matched.push(entry);
80487
80510
  }
80488
80511
  return {
@@ -80490,12 +80513,6 @@ function candidateOutputFiles(output, manifest, context) {
80490
80513
  files: matched
80491
80514
  };
80492
80515
  }
80493
- for (const entry of manifest) {
80494
- assertBudget(context);
80495
- if (entry.kind === "symlink" && matcher.test(entry.path)) {
80496
- throw new BenchmarkPhaseError("unsafe_path", `candidate output ${output.id} matches a symlink`);
80497
- }
80498
- }
80499
80516
  const directories = [];
80500
80517
  for (const directory of manifestDirectories(manifest, context)) {
80501
80518
  assertBudget(context);
@@ -80510,9 +80527,8 @@ function candidateOutputFiles(output, manifest, context) {
80510
80527
  assertBudget(context);
80511
80528
  if (!entry.path.startsWith(prefix))
80512
80529
  continue;
80513
- if (entry.kind === "symlink") {
80514
- throw new BenchmarkPhaseError("unsafe_path", `candidate output ${output.id} contains a symlink`);
80515
- }
80530
+ if (entry.kind === "symlink")
80531
+ continue;
80516
80532
  selected.set(entry.path, entry);
80517
80533
  }
80518
80534
  }
@@ -80558,7 +80574,7 @@ async function copyCandidateOutput(output, manifest, spec, context) {
80558
80574
  }
80559
80575
  return copied;
80560
80576
  }
80561
- 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") {
80562
80578
  const destinationRoot = fs81.mkdtempSync(path88.join(os16.tmpdir(), "brainbase-benchmark-workspace-"));
80563
80579
  fs81.chmodSync(destinationRoot, 448);
80564
80580
  context.temporaryRoots.add(destinationRoot);
@@ -80572,12 +80588,9 @@ async function copyFrozenWorkspace(manifest, spec, context, sourceRoot = spec.wo
80572
80588
  if (target === null || Buffer.byteLength(target) !== frozenFile.size || sha256(target) !== frozenFile.sha256) {
80573
80589
  throw new BenchmarkPhaseError("evidence_tampered", `workspace changed after it was frozen: ${frozenFile.path}`);
80574
80590
  }
80575
- if (!preserveUnsafeSymlinks && path88.isAbsolute(target)) {
80576
- throw new BenchmarkPhaseError("unsafe_workspace_symlink", `sandbox evaluator workspace cannot reproduce an absolute symlink: ${frozenFile.path}`);
80577
- }
80578
80591
  const resolvedTarget = path88.resolve(path88.dirname(source), target);
80579
- if (!preserveUnsafeSymlinks && !isWithin(sourceRoot, resolvedTarget)) {
80580
- 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;
80581
80594
  }
80582
80595
  fs81.mkdirSync(path88.dirname(destination), { recursive: true, mode: 448 });
80583
80596
  fs81.symlinkSync(target, destination);
@@ -80591,11 +80604,15 @@ async function copyFrozenWorkspace(manifest, spec, context, sourceRoot = spec.wo
80591
80604
  }
80592
80605
  return destinationRoot;
80593
80606
  }
80594
- async function copyEvaluatorTests(spec, context) {
80607
+ async function copyEvaluatorTests(spec, evaluator, context) {
80595
80608
  const destinationRoot = fs81.mkdtempSync(path88.join(os16.tmpdir(), "brainbase-benchmark-tests-"));
80596
80609
  fs81.chmodSync(destinationRoot, 448);
80597
80610
  context.temporaryRoots.add(destinationRoot);
80598
- 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 }];
80599
80616
  while (stack.length > 0) {
80600
80617
  assertBudget(context);
80601
80618
  const current = stack.pop();
@@ -80657,7 +80674,15 @@ var CriterionResultSchema = exports_external.object({
80657
80674
  });
80658
80675
  var CriteriaResultFileSchema = exports_external.object({
80659
80676
  criteria: exports_external.array(CriterionResultSchema).max(MAX_CRITERIA_PER_EVALUATOR)
80660
- }).strict();
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
+ });
80661
80686
  function readCriterionResults(resultPath, criterionKeys, allowedEvidenceIds, spec, additionalSecrets = []) {
80662
80687
  let opened;
80663
80688
  try {
@@ -80670,7 +80695,7 @@ function readCriterionResults(resultPath, criterionKeys, allowedEvidenceIds, spe
80670
80695
  }
80671
80696
  try {
80672
80697
  if (opened.stat.size > MAX_CRITERIA_RESULT_BYTES) {
80673
- 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");
80674
80699
  }
80675
80700
  let raw;
80676
80701
  try {
@@ -80766,9 +80791,12 @@ async function evaluateOne(evaluator, spec, manifest, frozenWorkspaceRoot, final
80766
80791
  cwd: ".",
80767
80792
  secret_env: []
80768
80793
  }, spec.tests_root, spec, context, {
80769
- BB_REGEX_INPUT: frozenEvidence.finalOutputPath,
80770
- BB_REGEX_PATTERN_B64: Buffer.from(assertion.pattern).toString("base64"),
80771
- 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"
80772
80800
  });
80773
80801
  if (regexResult.exitCode === 2) {
80774
80802
  throw new BenchmarkPhaseError("invalid_evaluator", `invalid regex in evaluator: ${evaluator.id}`);
@@ -80867,12 +80895,12 @@ async function evaluateOne(evaluator, spec, manifest, frozenWorkspaceRoot, final
80867
80895
  let privateResultRoot;
80868
80896
  const usesLiveWorkspace = evaluator.workspace_mode === "read_only";
80869
80897
  try {
80870
- const evaluatorWorkspace = usesLiveWorkspace ? spec.workspace_root : await copyFrozenWorkspace(manifest, spec, context, frozenWorkspaceRoot);
80898
+ const evaluatorWorkspace = usesLiveWorkspace ? spec.workspace_root : await copyFrozenWorkspace(manifest, spec, context, frozenWorkspaceRoot, "contained_relative_only");
80871
80899
  if (!usesLiveWorkspace)
80872
80900
  isolatedWorkspace = evaluatorWorkspace;
80873
80901
  else
80874
80902
  await verifyWorkspaceManifestUnchanged(manifest, spec, context);
80875
- isolatedTestsRoot = await copyEvaluatorTests(spec, context);
80903
+ isolatedTestsRoot = await copyEvaluatorTests(spec, evaluator, context);
80876
80904
  const testsPath = evaluatorTestsPath(evaluator, isolatedTestsRoot);
80877
80905
  const commandRoot = evaluator.root === "workspace" ? evaluatorWorkspace : isolatedTestsRoot;
80878
80906
  const command = { id: evaluator.id, ...evaluator.command };
@@ -80891,7 +80919,10 @@ async function evaluateOne(evaluator, spec, manifest, frozenWorkspaceRoot, final
80891
80919
  criterionResultPath = path88.join(privateResultRoot, "result.json");
80892
80920
  environment.BRAINBASE_BENCHMARK_CRITERIA_RESULT = criterionResultPath;
80893
80921
  }
80894
- 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
+ });
80895
80926
  const stdout = await writeLog(spec.logs_root, `${evaluator.id}.stdout.log`, result2.stdout, spec);
80896
80927
  const stderr = await writeLog(spec.logs_root, `${evaluator.id}.stderr.log`, result2.stderr, spec);
80897
80928
  let criterionResults;
@@ -81034,7 +81065,7 @@ async function executeEvaluate(spec, context) {
81034
81065
  assertEvaluateOutputBudget(spec, context);
81035
81066
  }
81036
81067
  await verifyRecordsUnchanged(manifest, spec);
81037
- const frozenWorkspaceRoot = await copyFrozenWorkspace(manifest, spec, context, spec.workspace_root, true);
81068
+ const frozenWorkspaceRoot = await copyFrozenWorkspace(manifest, spec, context, spec.workspace_root);
81038
81069
  for (const reference of spec.references) {
81039
81070
  const referenceOutputs = await copyMaterial(reference, spec.staging_root, spec.tests_root, "tests", false, context);
81040
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.8",
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": {