@brainbase-labs/cli 0.24.0 → 0.25.0-eng1209.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +14 -7
  2. package/dist/index.js +905 -98
  3. package/package.json +1 -1
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.24.0",
36011
+ version: "0.25.0-eng1209.10",
36012
36012
  description: "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
36013
36013
  type: "module",
36014
36014
  bin: {
@@ -78485,6 +78485,19 @@ var MAX_TRAJECTORY_BYTES = 100 * 1024 * 1024;
78485
78485
  var MAX_REMOTE_INPUT_BYTES = 2 * 1024 * 1024 * 1024;
78486
78486
  var MAX_ARCHIVE_EXTRACTED_BYTES = 2 * 1024 * 1024 * 1024;
78487
78487
  var MAX_ARCHIVE_SCAN_BYTES = 2 * 1024 * 1024 * 1024;
78488
+ var MAX_HYDRATE_COMMANDS = 150;
78489
+ var MAX_PHASE_RESULT_BYTES = 32 * 1024 * 1024;
78490
+ var MAX_SANDBOX_COMMANDS = 20;
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;
78494
+ var MAX_CRITERION_EXPLANATION_LENGTH = 16384;
78495
+ var MAX_CRITERION_EVIDENCE_IDS = 100;
78496
+ var MAX_CRITERION_EVIDENCE_ID_LENGTH = 256;
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;
78488
78501
  var RESERVED_WORKSPACE_PATHS = new Set([
78489
78502
  ".brainbase",
78490
78503
  ".git",
@@ -78512,6 +78525,17 @@ var NonSecretEnvironmentValueSchema = exports_external.object({
78512
78525
  sensitive: exports_external.literal(false)
78513
78526
  }).strict();
78514
78527
  var RootRelativePathSchema = exports_external.string().min(1).max(1024);
78528
+ var CandidateOutputPatternSchema = RootRelativePathSchema.refine((value) => {
78529
+ if (value.includes("\\") || /[\[\]{}]/.test(value))
78530
+ return false;
78531
+ try {
78532
+ return safeRelPath(value) === value;
78533
+ } catch {
78534
+ return false;
78535
+ }
78536
+ }, {
78537
+ message: "must be a normalized safe relative glob using only *, ?, and ** wildcards"
78538
+ });
78515
78539
  var ArchivePathSchema = RootRelativePathSchema.refine((value) => {
78516
78540
  try {
78517
78541
  return safeRelPath(value) === value;
@@ -78618,7 +78642,11 @@ var EvaluatorSchema = exports_external.discriminatedUnion("type", [
78618
78642
  ...EvaluatorBase,
78619
78643
  type: exports_external.literal("sandbox_command"),
78620
78644
  command: CommandSchema.omit({ id: true }),
78621
- root: exports_external.enum(["workspace", "tests"]).default("tests")
78645
+ root: exports_external.enum(["workspace", "tests"]).default("tests"),
78646
+ criterion_keys: exports_external.array(IdSchema).min(1).max(MAX_CRITERIA_PER_EVALUATOR).optional(),
78647
+ allowed_evidence_ids: exports_external.array(exports_external.string().min(1).max(MAX_CRITERION_EVIDENCE_ID_LENGTH)).max(MAX_ALLOWED_EVIDENCE_IDS).optional(),
78648
+ tests_path: ArchivePathSchema.optional(),
78649
+ workspace_mode: exports_external.enum(["read_only", "isolated_copy"]).default("read_only")
78622
78650
  }).strict()
78623
78651
  ]);
78624
78652
  var EvidenceFileSchema = exports_external.object({
@@ -78636,8 +78664,39 @@ var TrajectoryEvidenceSchema = EvidenceFileSchema.extend({
78636
78664
  var HydrateSpecSchema = BaseSpecSchema.extend({
78637
78665
  phase: exports_external.literal("hydrate"),
78638
78666
  materials: exports_external.array(MaterialSchema).max(1e4).default([]),
78639
- setup_commands: exports_external.array(CommandSchema).max(128).default([])
78667
+ setup_commands: exports_external.array(CommandSchema).max(MAX_HYDRATE_COMMANDS).default([])
78640
78668
  }).strict();
78669
+ var CandidateOutputSchema = exports_external.object({
78670
+ id: IdSchema,
78671
+ pattern: CandidateOutputPatternSchema,
78672
+ kind: exports_external.enum(["file", "directory"]),
78673
+ required: exports_external.boolean(),
78674
+ min_matches: exports_external.number().int().min(0).max(1e5),
78675
+ max_matches: exports_external.number().int().min(1).max(1e5),
78676
+ max_total_bytes: exports_external.number().int().min(0).max(2 * 1024 * 1024 * 1024)
78677
+ }).strict().superRefine((value, context) => {
78678
+ if (value.min_matches > value.max_matches) {
78679
+ context.addIssue({
78680
+ code: exports_external.ZodIssueCode.custom,
78681
+ path: ["min_matches"],
78682
+ message: "min_matches cannot exceed max_matches"
78683
+ });
78684
+ }
78685
+ if (value.required && value.min_matches === 0) {
78686
+ context.addIssue({
78687
+ code: exports_external.ZodIssueCode.custom,
78688
+ path: ["min_matches"],
78689
+ message: "required candidate outputs must require at least one match"
78690
+ });
78691
+ }
78692
+ if (!value.required && value.min_matches > 0) {
78693
+ context.addIssue({
78694
+ code: exports_external.ZodIssueCode.custom,
78695
+ path: ["min_matches"],
78696
+ message: "optional candidate outputs must allow zero matches"
78697
+ });
78698
+ }
78699
+ });
78641
78700
  var EvaluateSpecSchema = BaseSpecSchema.extend({
78642
78701
  phase: exports_external.literal("evaluate"),
78643
78702
  tests_root: AbsolutePathSchema,
@@ -78648,6 +78707,7 @@ var EvaluateSpecSchema = BaseSpecSchema.extend({
78648
78707
  references: exports_external.array(MaterialSchema).max(1e4).default([]),
78649
78708
  evaluators: exports_external.array(EvaluatorSchema).min(1).max(1000),
78650
78709
  candidate_artifacts: exports_external.array(RootRelativePathSchema).max(1000).default([]),
78710
+ candidate_outputs: exports_external.array(CandidateOutputSchema).max(1000).default([]),
78651
78711
  capture_workspace_archive: exports_external.boolean().default(false),
78652
78712
  workspace_limits: exports_external.object({
78653
78713
  max_file_count: exports_external.number().int().min(1).max(1e6).default(1e5),
@@ -78658,6 +78718,20 @@ var BenchmarkSpecSchema = exports_external.discriminatedUnion("phase", [
78658
78718
  HydrateSpecSchema,
78659
78719
  EvaluateSpecSchema
78660
78720
  ]).superRefine((value, context) => {
78721
+ if (Object.hasOwn(value.environment, COMMAND_PROCESS_MARKER_ENV)) {
78722
+ context.addIssue({
78723
+ code: exports_external.ZodIssueCode.custom,
78724
+ path: ["environment", COMMAND_PROCESS_MARKER_ENV],
78725
+ message: `${COMMAND_PROCESS_MARKER_ENV} is reserved for command supervision`
78726
+ });
78727
+ }
78728
+ if (value.secret_env.includes(COMMAND_PROCESS_MARKER_ENV)) {
78729
+ context.addIssue({
78730
+ code: exports_external.ZodIssueCode.custom,
78731
+ path: ["secret_env"],
78732
+ message: `${COMMAND_PROCESS_MARKER_ENV} is reserved for command supervision`
78733
+ });
78734
+ }
78661
78735
  for (const name of Object.keys(value.environment)) {
78662
78736
  if (SENSITIVE_ENV_NAME_RE.test(name)) {
78663
78737
  context.addIssue({
@@ -78727,15 +78801,49 @@ var BenchmarkSpecSchema = exports_external.discriminatedUnion("phase", [
78727
78801
  { items: value.setup_commands, path: "setup_commands" }
78728
78802
  ] : [
78729
78803
  { items: value.references, path: "references" },
78730
- { items: value.evaluators, path: "evaluators" }
78804
+ { items: value.evaluators, path: "evaluators" },
78805
+ { items: value.candidate_outputs, path: "candidate_outputs" }
78731
78806
  ];
78732
78807
  for (const { items, path: issuePath } of uniqueLists) {
78733
- const ids = items.map((item) => item.id);
78808
+ const ids = items.map((item) => issuePath === "candidate_outputs" ? item.id.toLowerCase() : item.id);
78734
78809
  if (new Set(ids).size !== ids.length) {
78735
78810
  context.addIssue({
78736
78811
  code: exports_external.ZodIssueCode.custom,
78737
78812
  path: [issuePath],
78738
- message: `${issuePath} ids must be unique`
78813
+ message: `${issuePath} ids must be unique${issuePath === "candidate_outputs" ? " ignoring case" : ""}`
78814
+ });
78815
+ }
78816
+ }
78817
+ if (value.phase === "evaluate") {
78818
+ value.evaluators.forEach((evaluator, index) => {
78819
+ if (evaluator.type === "sandbox_command" && evaluator.criterion_keys && new Set(evaluator.criterion_keys).size !== evaluator.criterion_keys.length) {
78820
+ context.addIssue({
78821
+ code: exports_external.ZodIssueCode.custom,
78822
+ path: ["evaluators", index, "criterion_keys"],
78823
+ message: "criterion_keys must be unique"
78824
+ });
78825
+ }
78826
+ if (evaluator.type === "sandbox_command" && evaluator.criterion_keys && evaluator.allowed_evidence_ids === undefined) {
78827
+ context.addIssue({
78828
+ code: exports_external.ZodIssueCode.custom,
78829
+ path: ["evaluators", index, "allowed_evidence_ids"],
78830
+ message: "structured criteria require allowed_evidence_ids"
78831
+ });
78832
+ }
78833
+ if (evaluator.type === "sandbox_command" && evaluator.allowed_evidence_ids && new Set(evaluator.allowed_evidence_ids).size !== evaluator.allowed_evidence_ids.length) {
78834
+ context.addIssue({
78835
+ code: exports_external.ZodIssueCode.custom,
78836
+ path: ["evaluators", index, "allowed_evidence_ids"],
78837
+ message: "allowed_evidence_ids must be unique"
78838
+ });
78839
+ }
78840
+ });
78841
+ const candidateOutputBytes = value.candidate_outputs.reduce((total, output) => total + output.max_total_bytes, 0);
78842
+ if (candidateOutputBytes > value.workspace_limits.max_total_bytes) {
78843
+ context.addIssue({
78844
+ code: exports_external.ZodIssueCode.custom,
78845
+ path: ["candidate_outputs"],
78846
+ message: "candidate output byte limits exceed the workspace byte limit"
78739
78847
  });
78740
78848
  }
78741
78849
  }
@@ -78748,11 +78856,11 @@ var BenchmarkSpecSchema = exports_external.discriminatedUnion("phase", [
78748
78856
  message: "archive-file materials exceed the aggregate extracted byte limit"
78749
78857
  });
78750
78858
  }
78751
- if (value.phase === "evaluate" && value.evaluators.filter((evaluator) => evaluator.type === "sandbox_command").length > 1) {
78859
+ if (value.phase === "evaluate" && value.evaluators.filter((evaluator) => evaluator.type === "sandbox_command").length > MAX_SANDBOX_COMMANDS) {
78752
78860
  context.addIssue({
78753
78861
  code: exports_external.ZodIssueCode.custom,
78754
78862
  path: ["evaluators"],
78755
- message: "schema version 1 supports at most one sandbox_command evaluator"
78863
+ message: `schema version 1 supports at most ${MAX_SANDBOX_COMMANDS} sandbox_command evaluators`
78756
78864
  });
78757
78865
  }
78758
78866
  });
@@ -78763,7 +78871,12 @@ var BENCHMARK_CAPABILITIES = {
78763
78871
  phases: ["hydrate", "evaluate"],
78764
78872
  features: [
78765
78873
  "remote_input_references_v1",
78766
- "archive_file_materials_v1"
78874
+ "archive_file_materials_v1",
78875
+ "structured_criterion_results_v1",
78876
+ "multiple_sandbox_commands_v1",
78877
+ "sandbox_command_workspace_modes_v1",
78878
+ "candidate_outputs_v1",
78879
+ "criterion_evidence_allowlist_v1"
78767
78880
  ],
78768
78881
  evaluator_types: [
78769
78882
  "output_assertion",
@@ -78773,8 +78886,11 @@ var BENCHMARK_CAPABILITIES = {
78773
78886
  ],
78774
78887
  limits: {
78775
78888
  max_secret_bindings: 100,
78889
+ max_hydrate_commands: MAX_HYDRATE_COMMANDS,
78890
+ max_phase_result_bytes: MAX_PHASE_RESULT_BYTES,
78776
78891
  max_evaluators: 1000,
78777
- max_sandbox_commands: 1
78892
+ max_sandbox_commands: MAX_SANDBOX_COMMANDS,
78893
+ max_criteria_per_evaluator: MAX_CRITERIA_PER_EVALUATOR
78778
78894
  }
78779
78895
  };
78780
78896
 
@@ -78788,6 +78904,19 @@ class BenchmarkPhaseError extends Error {
78788
78904
  this.name = "BenchmarkPhaseError";
78789
78905
  }
78790
78906
  }
78907
+
78908
+ class BenchmarkCommandExecutionError extends BenchmarkPhaseError {
78909
+ stdout;
78910
+ stderr;
78911
+ durationMs;
78912
+ constructor(code, message, stdout, stderr, durationMs) {
78913
+ super(code, message);
78914
+ this.stdout = stdout;
78915
+ this.stderr = stderr;
78916
+ this.durationMs = durationMs;
78917
+ this.name = "BenchmarkCommandExecutionError";
78918
+ }
78919
+ }
78791
78920
  var ZIP_EOCD_SIGNATURE = 101010256;
78792
78921
  var ZIP_CENTRAL_SIGNATURE = 33639248;
78793
78922
  var ZIP_LOCAL_SIGNATURE = 67324752;
@@ -79020,10 +79149,15 @@ async function verifyRecordsUnchanged(records, spec) {
79020
79149
  const relative = safeRelPath(record3.path);
79021
79150
  assertNoSymlinkTraversal(root, relative);
79022
79151
  const candidate = path88.resolve(root, relative);
79023
- if (!isWithin(root, candidate) || !fs81.existsSync(candidate)) {
79152
+ if (!isWithin(root, candidate)) {
79153
+ throw new BenchmarkPhaseError("evidence_tampered", `evidence was removed during evaluation: ${record3.root}:${record3.path}`);
79154
+ }
79155
+ let stat;
79156
+ try {
79157
+ stat = fs81.lstatSync(candidate);
79158
+ } catch {
79024
79159
  throw new BenchmarkPhaseError("evidence_tampered", `evidence was removed during evaluation: ${record3.root}:${record3.path}`);
79025
79160
  }
79026
- const stat = fs81.lstatSync(candidate);
79027
79161
  if (record3.kind === "symlink") {
79028
79162
  const target = stat.isSymbolicLink() ? fs81.readlinkSync(candidate) : null;
79029
79163
  if (target === null || Buffer.byteLength(target) !== record3.size || sha256(target) !== record3.sha256) {
@@ -79118,6 +79252,15 @@ function removeRemoteHydrationInputs(spec) {
79118
79252
  removedSources.add(material.source);
79119
79253
  }
79120
79254
  }
79255
+ function removePrivateHydrationSourcesBeforeSetup(spec, context) {
79256
+ removeRemoteHydrationInputs(spec);
79257
+ for (const temporary of context.temporaryRoots) {
79258
+ fs81.rmSync(temporary, { recursive: true, force: true });
79259
+ }
79260
+ context.temporaryRoots.clear();
79261
+ context.verifiedInputs.clear();
79262
+ context.preparedArchiveFiles.clear();
79263
+ }
79121
79264
  async function downloadInputReference(stagingRoot, input, context) {
79122
79265
  const cacheKey = inputCacheKey(input);
79123
79266
  const cached2 = context.verifiedInputs.get(cacheKey);
@@ -79799,7 +79942,7 @@ function buildEnvironment(spec, secretNames, additions = {}) {
79799
79942
  Object.assign(env3, additions);
79800
79943
  return env3;
79801
79944
  }
79802
- function redactCommandOutput(data, spec) {
79945
+ function redactCommandOutput(data, spec, additionalSecrets = []) {
79803
79946
  let value = data.toString("utf8");
79804
79947
  const sensitiveNames = new Set([
79805
79948
  ...spec.secret_env,
@@ -79810,6 +79953,10 @@ function redactCommandOutput(data, spec) {
79810
79953
  if (secret)
79811
79954
  value = value.split(secret).join("[REDACTED]");
79812
79955
  }
79956
+ for (const secret of additionalSecrets) {
79957
+ if (secret)
79958
+ value = value.split(secret).join("[REDACTED]");
79959
+ }
79813
79960
  return Buffer.from(value);
79814
79961
  }
79815
79962
  function descendantPids(parentPid) {
@@ -79861,7 +80008,99 @@ function terminate(child) {
79861
80008
  child.kill("SIGKILL");
79862
80009
  }
79863
80010
  }
79864
- async function runCommand(command, root, spec, context, additions = {}) {
80011
+ function markedProcessPids(marker) {
80012
+ const assignment = `${COMMAND_PROCESS_MARKER_ENV}=${marker}`;
80013
+ if (process.platform === "linux") {
80014
+ const matches2 = [];
80015
+ let entries;
80016
+ try {
80017
+ entries = fs81.readdirSync("/proc");
80018
+ } catch {
80019
+ return matches2;
80020
+ }
80021
+ for (const entry of entries) {
80022
+ if (!/^\d+$/.test(entry))
80023
+ continue;
80024
+ const pid = Number(entry);
80025
+ if (pid === process.pid)
80026
+ continue;
80027
+ try {
80028
+ const environment = fs81.readFileSync(path88.join("/proc", entry, "environ"), "utf8");
80029
+ if (environment.split("\x00").includes(assignment))
80030
+ matches2.push(pid);
80031
+ } catch {}
80032
+ }
80033
+ return matches2;
80034
+ }
80035
+ if (process.platform === "darwin") {
80036
+ try {
80037
+ const output = execFileSync2("ps", ["eww", "-axo", "pid=,command="], {
80038
+ encoding: "utf8",
80039
+ maxBuffer: 16 * 1024 * 1024,
80040
+ stdio: ["ignore", "pipe", "ignore"]
80041
+ });
80042
+ const matches2 = [];
80043
+ for (const line of output.split(`
80044
+ `)) {
80045
+ const match = line.match(/^\s*(\d+)\s+(.*)$/);
80046
+ if (!match || !match[2].includes(assignment))
80047
+ continue;
80048
+ const pid = Number(match[1]);
80049
+ if (Number.isInteger(pid) && pid !== process.pid)
80050
+ matches2.push(pid);
80051
+ }
80052
+ return matches2;
80053
+ } catch {
80054
+ return [];
80055
+ }
80056
+ }
80057
+ return [];
80058
+ }
80059
+ function processExists(pid) {
80060
+ if (process.platform === "linux") {
80061
+ try {
80062
+ const stat = fs81.readFileSync(path88.join("/proc", String(pid), "stat"), "utf8");
80063
+ const commandEnd = stat.lastIndexOf(")");
80064
+ const state = commandEnd >= 0 ? stat.slice(commandEnd + 2, commandEnd + 3) : "";
80065
+ if (state === "Z" || state === "X")
80066
+ return false;
80067
+ } catch (error2) {
80068
+ if (error2.code === "ENOENT")
80069
+ return false;
80070
+ }
80071
+ }
80072
+ try {
80073
+ process.kill(pid, 0);
80074
+ return true;
80075
+ } catch (error2) {
80076
+ return error2.code !== "ESRCH";
80077
+ }
80078
+ }
80079
+ async function terminateCommandProcesses(child, marker, observedDescendants) {
80080
+ if (child.pid !== undefined) {
80081
+ for (const pid of descendantPids(child.pid))
80082
+ observedDescendants.add(pid);
80083
+ }
80084
+ terminate(child);
80085
+ const deadline = Date.now() + COMMAND_PROCESS_CLEANUP_MS;
80086
+ while (true) {
80087
+ for (const pid of markedProcessPids(marker))
80088
+ observedDescendants.add(pid);
80089
+ const active = [...observedDescendants].filter(processExists);
80090
+ for (const pid of active) {
80091
+ try {
80092
+ process.kill(pid, "SIGKILL");
80093
+ } catch {}
80094
+ }
80095
+ if (active.length === 0)
80096
+ return;
80097
+ if (Date.now() >= deadline) {
80098
+ throw new BenchmarkPhaseError("command_cleanup_failed", "command descendants remained after bounded cleanup");
80099
+ }
80100
+ await new Promise((resolve) => setTimeout(resolve, COMMAND_PROCESS_POLL_MS));
80101
+ }
80102
+ }
80103
+ async function runCommand(command, root, spec, context, options = {}) {
79865
80104
  const cwdRel = normalizedRootRelative(command.cwd);
79866
80105
  assertNoSymlinkTraversal(root, cwdRel);
79867
80106
  const cwd2 = path88.resolve(root, cwdRel);
@@ -79877,29 +80116,59 @@ async function runCommand(command, root, spec, context, additions = {}) {
79877
80116
  const remainingMs = context.deadline - Date.now();
79878
80117
  if (remainingMs <= 0)
79879
80118
  throw new BenchmarkPhaseError("phase_timeout", "phase budget expired");
79880
- const timeoutMs2 = Math.min(command.timeout_ms ?? remainingMs, remainingMs);
80119
+ const reachesPhaseDeadline = command.timeout_ms === undefined || command.timeout_ms >= remainingMs;
80120
+ const timeoutMs2 = reachesPhaseDeadline ? remainingMs : command.timeout_ms;
79881
80121
  const started = Date.now();
80122
+ const commandMarker = crypto6.randomBytes(32).toString("hex");
79882
80123
  return await new Promise((resolve, reject2) => {
79883
80124
  const child = spawn4(command.argv[0], command.argv.slice(1), {
79884
80125
  cwd: cwd2,
79885
- env: buildEnvironment(spec, command.secret_env, additions),
80126
+ env: buildEnvironment(spec, command.secret_env, {
80127
+ ...options.additions,
80128
+ [COMMAND_PROCESS_MARKER_ENV]: commandMarker
80129
+ }),
79886
80130
  stdio: ["ignore", "pipe", "pipe"],
79887
80131
  detached: process.platform !== "win32"
79888
80132
  });
79889
80133
  const stdout = [];
79890
80134
  const stderr = [];
80135
+ const observedDescendants = new Set;
79891
80136
  let captured = 0;
79892
80137
  let settled = false;
79893
80138
  let timer;
80139
+ let observer;
80140
+ let cleanup;
80141
+ const observeDescendants = () => {
80142
+ if (child.pid === undefined)
80143
+ return;
80144
+ for (const pid of descendantPids(child.pid))
80145
+ observedDescendants.add(pid);
80146
+ };
80147
+ const cleanupProcesses = () => {
80148
+ if (observer)
80149
+ clearInterval(observer);
80150
+ cleanup ??= terminateCommandProcesses(child, commandMarker, observedDescendants);
80151
+ return cleanup;
80152
+ };
80153
+ const sanitizedOutput = (chunks) => redactCommandOutput(Buffer.concat(chunks), spec, [commandMarker]);
80154
+ const sanitizedError = (error2) => {
80155
+ if (!(error2 instanceof BenchmarkCommandExecutionError))
80156
+ return error2;
80157
+ return new BenchmarkCommandExecutionError(error2.code, error2.message, sanitizedOutput(stdout), sanitizedOutput(stderr), error2.durationMs);
80158
+ };
79894
80159
  const fail = (error2) => {
79895
80160
  if (settled)
79896
80161
  return;
79897
80162
  settled = true;
79898
80163
  if (timer)
79899
80164
  clearTimeout(timer);
79900
- terminate(child);
79901
- reject2(error2);
80165
+ cleanupProcesses().then(() => reject2(sanitizedError(error2)), reject2);
79902
80166
  };
80167
+ if (process.platform !== "linux") {
80168
+ observeDescendants();
80169
+ observer = setInterval(observeDescendants, COMMAND_PROCESS_POLL_MS);
80170
+ observer.unref();
80171
+ }
79903
80172
  const capture = (target, chunk2) => {
79904
80173
  if (settled)
79905
80174
  return;
@@ -79911,29 +80180,48 @@ async function runCommand(command, root, spec, context, additions = {}) {
79911
80180
  }
79912
80181
  target.push(chunk2);
79913
80182
  };
80183
+ const finish = (code, signal) => {
80184
+ if (observer)
80185
+ clearInterval(observer);
80186
+ if (code === null) {
80187
+ reject2(new BenchmarkCommandExecutionError("command_terminated", `command was terminated by ${signal ?? "an unknown signal"}: ${command.id}`, sanitizedOutput(stdout), sanitizedOutput(stderr), Date.now() - started));
80188
+ return;
80189
+ }
80190
+ resolve({
80191
+ exitCode: code,
80192
+ stdout: sanitizedOutput(stdout),
80193
+ stderr: sanitizedOutput(stderr),
80194
+ durationMs: Date.now() - started,
80195
+ redactions: [commandMarker]
80196
+ });
80197
+ };
79914
80198
  child.stdout?.on("data", (chunk2) => capture(stdout, chunk2));
79915
80199
  child.stderr?.on("data", (chunk2) => capture(stderr, chunk2));
79916
80200
  child.on("error", (error2) => {
79917
- fail(new BenchmarkPhaseError("command_start_failed", `failed to start ${command.id}: ${error2.message}`));
80201
+ fail(new BenchmarkCommandExecutionError("command_start_failed", `failed to start ${command.id}: ${error2.message}`, Buffer.concat(stdout), Buffer.concat(stderr), Date.now() - started));
79918
80202
  });
79919
80203
  timer = setTimeout(() => {
79920
- fail(new BenchmarkPhaseError("command_timeout", `command timed out: ${command.id}`));
80204
+ if (reachesPhaseDeadline) {
80205
+ fail(new BenchmarkPhaseError("phase_timeout", "phase budget expired"));
80206
+ return;
80207
+ }
80208
+ fail(new BenchmarkCommandExecutionError("command_timeout", `command timed out: ${command.id}`, Buffer.concat(stdout), Buffer.concat(stderr), Date.now() - started));
79921
80209
  }, timeoutMs2);
79922
- child.on("close", (code, signal) => {
80210
+ child.on("exit", (code, signal) => {
79923
80211
  if (settled)
79924
80212
  return;
79925
80213
  settled = true;
79926
80214
  clearTimeout(timer);
79927
- if (code === null) {
79928
- reject2(new BenchmarkPhaseError("command_terminated", `command was terminated by ${signal ?? "an unknown signal"}: ${command.id}`));
79929
- return;
80215
+ if (options.descendantCleanup === "always" || code === null || code !== 0) {
80216
+ cleanupProcesses().then(() => finish(code, signal), reject2);
80217
+ } else {
80218
+ if (observer)
80219
+ clearInterval(observer);
80220
+ child.unref();
80221
+ child.stdout?.unref?.();
80222
+ child.stderr?.unref?.();
80223
+ finish(code, signal);
79930
80224
  }
79931
- resolve({
79932
- exitCode: code,
79933
- stdout: Buffer.concat(stdout),
79934
- stderr: Buffer.concat(stderr),
79935
- durationMs: Date.now() - started
79936
- });
79937
80225
  });
79938
80226
  });
79939
80227
  }
@@ -80006,11 +80294,14 @@ async function executeHydrate(spec, context) {
80006
80294
  throw error2;
80007
80295
  }
80008
80296
  }
80297
+ removePrivateHydrationSourcesBeforeSetup(spec, context);
80009
80298
  for (const command of spec.setup_commands) {
80010
80299
  assertBudget(context);
80011
80300
  let result2;
80012
80301
  try {
80013
- result2 = await runCommand(command, spec.workspace_root, spec, context);
80302
+ result2 = await runCommand(command, spec.workspace_root, spec, context, {
80303
+ descendantCleanup: "failure_only"
80304
+ });
80014
80305
  } catch (error2) {
80015
80306
  context.steps.push({
80016
80307
  id: command.id,
@@ -80061,7 +80352,6 @@ async function executeHydrate(spec, context) {
80061
80352
  }
80062
80353
  outputs.splice(0, outputs.length, ...finalOutputs);
80063
80354
  context.outputs.splice(0, context.outputs.length, ...finalOutputs);
80064
- removeRemoteHydrationInputs(spec);
80065
80355
  assertBudget(context);
80066
80356
  return outputs;
80067
80357
  }
@@ -80126,6 +80416,332 @@ async function workspaceManifest(spec, context) {
80126
80416
  }
80127
80417
  return records.sort((a3, b4) => a3.path.localeCompare(b4.path));
80128
80418
  }
80419
+ async function verifyWorkspaceManifestUnchanged(expected, spec, context) {
80420
+ await verifyRecordsUnchanged(expected, spec);
80421
+ const actual = await workspaceManifest(spec, context);
80422
+ if (JSON.stringify(actual) !== JSON.stringify(expected)) {
80423
+ throw new BenchmarkPhaseError("evidence_tampered", "candidate workspace changed during read-only evaluation");
80424
+ }
80425
+ }
80426
+ function treeBytes(root, context) {
80427
+ let totalBytes = 0;
80428
+ const stack = [path88.resolve(root)];
80429
+ while (stack.length > 0) {
80430
+ const directory = stack.pop();
80431
+ const entries = fs81.readdirSync(directory, { withFileTypes: true });
80432
+ for (const entry of entries) {
80433
+ assertBudget(context);
80434
+ const candidate = path88.join(directory, entry.name);
80435
+ const stat = fs81.lstatSync(candidate);
80436
+ if (stat.isDirectory()) {
80437
+ stack.push(candidate);
80438
+ continue;
80439
+ }
80440
+ if (stat.isFile()) {
80441
+ totalBytes += stat.size;
80442
+ continue;
80443
+ }
80444
+ if (stat.isSymbolicLink()) {
80445
+ totalBytes += Buffer.byteLength(fs81.readlinkSync(candidate));
80446
+ continue;
80447
+ }
80448
+ throw new BenchmarkPhaseError("unsafe_path", `evaluate output contains an unsupported filesystem entry: ${path88.relative(root, candidate)}`);
80449
+ }
80450
+ }
80451
+ return totalBytes;
80452
+ }
80453
+ function assertEvaluateOutputBudget(spec, context, additionalBytes = 0) {
80454
+ const actual = treeBytes(spec.logs_root, context) + additionalBytes;
80455
+ if (actual > spec.workspace_limits.max_total_bytes) {
80456
+ throw new BenchmarkPhaseError("workspace_limit_exceeded", "evaluate output bytes exceed the workspace byte limit", {
80457
+ actual,
80458
+ max_total_bytes: spec.workspace_limits.max_total_bytes
80459
+ });
80460
+ }
80461
+ }
80462
+ function candidateGlob(pattern) {
80463
+ let source = "^";
80464
+ for (let index = 0;index < pattern.length; index += 1) {
80465
+ const char = pattern[index];
80466
+ if (char === "*") {
80467
+ if (pattern[index + 1] === "*") {
80468
+ if (pattern[index + 2] === "/") {
80469
+ source += "(?:.*/)?";
80470
+ index += 2;
80471
+ } else {
80472
+ source += ".*";
80473
+ index += 1;
80474
+ }
80475
+ } else {
80476
+ source += "[^/]*";
80477
+ }
80478
+ continue;
80479
+ }
80480
+ if (char === "?") {
80481
+ source += "[^/]";
80482
+ continue;
80483
+ }
80484
+ source += char.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
80485
+ }
80486
+ return new RegExp(`${source}$`);
80487
+ }
80488
+ function manifestDirectories(manifest, context) {
80489
+ const directories = new Set;
80490
+ for (const entry of manifest) {
80491
+ assertBudget(context);
80492
+ if (entry.kind === "symlink")
80493
+ continue;
80494
+ let current = path88.posix.dirname(entry.path);
80495
+ while (current !== ".") {
80496
+ assertBudget(context);
80497
+ directories.add(current);
80498
+ current = path88.posix.dirname(current);
80499
+ }
80500
+ }
80501
+ return [...directories].sort();
80502
+ }
80503
+ function candidateOutputFiles(output, manifest, context) {
80504
+ const matcher = candidateGlob(output.pattern);
80505
+ if (output.kind === "file") {
80506
+ const matched = [];
80507
+ for (const entry of manifest) {
80508
+ assertBudget(context);
80509
+ if (!matcher.test(entry.path))
80510
+ continue;
80511
+ if (entry.kind === "symlink")
80512
+ continue;
80513
+ matched.push(entry);
80514
+ }
80515
+ return {
80516
+ matchedCount: matched.length,
80517
+ files: matched
80518
+ };
80519
+ }
80520
+ const directories = [];
80521
+ for (const directory of manifestDirectories(manifest, context)) {
80522
+ assertBudget(context);
80523
+ if (matcher.test(directory))
80524
+ directories.push(directory);
80525
+ }
80526
+ const selected = new Map;
80527
+ for (const directory of directories) {
80528
+ assertBudget(context);
80529
+ const prefix = `${directory}/`;
80530
+ for (const entry of manifest) {
80531
+ assertBudget(context);
80532
+ if (!entry.path.startsWith(prefix))
80533
+ continue;
80534
+ if (entry.kind === "symlink")
80535
+ continue;
80536
+ selected.set(entry.path, entry);
80537
+ }
80538
+ }
80539
+ return {
80540
+ matchedCount: directories.length,
80541
+ files: [...selected.values()].sort((left, right) => left.path.localeCompare(right.path))
80542
+ };
80543
+ }
80544
+ async function copyCandidateOutput(output, manifest, spec, context) {
80545
+ const selected = candidateOutputFiles(output, manifest, context);
80546
+ if (!output.required && selected.matchedCount === 0)
80547
+ return [];
80548
+ const minimumMatches = output.required ? Math.max(1, output.min_matches) : output.min_matches;
80549
+ if (selected.matchedCount < minimumMatches || selected.matchedCount > output.max_matches) {
80550
+ throw new BenchmarkPhaseError("candidate_output_match_count", `candidate output ${output.id} matched ${selected.matchedCount} ${output.kind}s`, {
80551
+ actual: selected.matchedCount,
80552
+ min_matches: minimumMatches,
80553
+ max_matches: output.max_matches
80554
+ });
80555
+ }
80556
+ let totalBytes = 0;
80557
+ for (const file of selected.files) {
80558
+ assertBudget(context);
80559
+ totalBytes += file.size;
80560
+ }
80561
+ if (totalBytes > output.max_total_bytes) {
80562
+ throw new BenchmarkPhaseError("candidate_output_size_exceeded", `candidate output ${output.id} exceeds its byte limit`, { actual: totalBytes, max_total_bytes: output.max_total_bytes });
80563
+ }
80564
+ const copied = [];
80565
+ for (const frozenFile of selected.files) {
80566
+ assertBudget(context);
80567
+ const source = path88.resolve(spec.workspace_root, safeRelPath(frozenFile.path));
80568
+ const destination = path88.resolve(spec.logs_root, "candidate-outputs", output.id, safeRelPath(frozenFile.path));
80569
+ if (fs81.existsSync(destination)) {
80570
+ throw new BenchmarkPhaseError("destination_conflict", `candidate output destination already exists: ${output.id}/${frozenFile.path}`);
80571
+ }
80572
+ await atomicCopy(source, destination, frozenFile.mode, spec.workspace_root);
80573
+ const record3 = await recordFile(spec.logs_root, destination, "logs");
80574
+ if (record3.sha256 !== frozenFile.sha256 || record3.size !== frozenFile.size || record3.mode !== frozenFile.mode) {
80575
+ throw new BenchmarkPhaseError("evidence_tampered", `candidate output changed after the workspace freeze: ${frozenFile.path}`);
80576
+ }
80577
+ copied.push(record3);
80578
+ }
80579
+ return copied;
80580
+ }
80581
+ async function copyFrozenWorkspace(manifest, spec, context, sourceRoot = spec.workspace_root, symlinkPolicy = "preserve") {
80582
+ const destinationRoot = fs81.mkdtempSync(path88.join(os16.tmpdir(), "brainbase-benchmark-workspace-"));
80583
+ fs81.chmodSync(destinationRoot, 448);
80584
+ context.temporaryRoots.add(destinationRoot);
80585
+ for (const frozenFile of manifest) {
80586
+ assertBudget(context);
80587
+ const source = path88.resolve(sourceRoot, safeRelPath(frozenFile.path));
80588
+ const destination = path88.resolve(destinationRoot, safeRelPath(frozenFile.path));
80589
+ if (frozenFile.kind === "symlink") {
80590
+ const stat = fs81.lstatSync(source);
80591
+ const target = stat.isSymbolicLink() ? fs81.readlinkSync(source) : null;
80592
+ if (target === null || Buffer.byteLength(target) !== frozenFile.size || sha256(target) !== frozenFile.sha256) {
80593
+ throw new BenchmarkPhaseError("evidence_tampered", `workspace changed after it was frozen: ${frozenFile.path}`);
80594
+ }
80595
+ const resolvedTarget = path88.resolve(path88.dirname(source), target);
80596
+ if (symlinkPolicy === "contained_relative_only" && (path88.isAbsolute(target) || !isWithin(sourceRoot, resolvedTarget))) {
80597
+ continue;
80598
+ }
80599
+ fs81.mkdirSync(path88.dirname(destination), { recursive: true, mode: 448 });
80600
+ fs81.symlinkSync(target, destination);
80601
+ continue;
80602
+ }
80603
+ await atomicCopy(source, destination, frozenFile.mode, sourceRoot);
80604
+ const copied = await recordFile(destinationRoot, destination, "workspace");
80605
+ if (copied.sha256 !== frozenFile.sha256 || copied.size !== frozenFile.size || copied.mode !== frozenFile.mode) {
80606
+ throw new BenchmarkPhaseError("evidence_tampered", `workspace changed after it was frozen: ${frozenFile.path}`);
80607
+ }
80608
+ }
80609
+ return destinationRoot;
80610
+ }
80611
+ async function copyEvaluatorTests(spec, evaluator, context) {
80612
+ const destinationRoot = fs81.mkdtempSync(path88.join(os16.tmpdir(), "brainbase-benchmark-tests-"));
80613
+ fs81.chmodSync(destinationRoot, 448);
80614
+ context.temporaryRoots.add(destinationRoot);
80615
+ const sourceRoot = evaluatorTestsPath(evaluator, spec.tests_root);
80616
+ const relativeRoot = evaluator.tests_path ? normalizedRootRelative(evaluator.tests_path) : ".";
80617
+ const destinationStart = relativeRoot === "." ? destinationRoot : path88.resolve(destinationRoot, relativeRoot);
80618
+ fs81.mkdirSync(destinationStart, { recursive: true, mode: 448 });
80619
+ const stack = [{ source: sourceRoot, destination: destinationStart }];
80620
+ while (stack.length > 0) {
80621
+ assertBudget(context);
80622
+ const current = stack.pop();
80623
+ const entries = fs81.readdirSync(current.source, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
80624
+ for (const entry of entries) {
80625
+ assertBudget(context);
80626
+ const source = path88.join(current.source, entry.name);
80627
+ const destination = path88.join(current.destination, entry.name);
80628
+ const stat = fs81.lstatSync(source);
80629
+ if (stat.isSymbolicLink()) {
80630
+ throw new BenchmarkPhaseError("unsafe_path", `evaluator reference tree contains a symlink: ${path88.relative(spec.tests_root, source)}`);
80631
+ }
80632
+ if (stat.isDirectory()) {
80633
+ fs81.mkdirSync(destination, { recursive: true, mode: stat.mode & 511 });
80634
+ stack.push({ source, destination });
80635
+ continue;
80636
+ }
80637
+ if (!stat.isFile()) {
80638
+ throw new BenchmarkPhaseError("unsafe_path", `evaluator reference tree contains an unsupported filesystem entry: ${path88.relative(spec.tests_root, source)}`);
80639
+ }
80640
+ await atomicCopy(source, destination, stat.mode & 511, spec.tests_root);
80641
+ const sourceRecord = await recordFile(spec.tests_root, source, "tests");
80642
+ const copiedRecord = await recordFile(destinationRoot, destination, "tests");
80643
+ if (copiedRecord.path !== sourceRecord.path || copiedRecord.sha256 !== sourceRecord.sha256 || copiedRecord.size !== sourceRecord.size || copiedRecord.mode !== sourceRecord.mode) {
80644
+ throw new BenchmarkPhaseError("evidence_tampered", `evaluator reference changed while it was copied: ${sourceRecord.path}`);
80645
+ }
80646
+ }
80647
+ }
80648
+ return destinationRoot;
80649
+ }
80650
+ function evaluatorTestsPath(evaluator, testsRoot) {
80651
+ if (!evaluator.tests_path)
80652
+ return testsRoot;
80653
+ const relative = normalizedRootRelative(evaluator.tests_path);
80654
+ assertNoSymlinkTraversal(testsRoot, relative);
80655
+ const candidate = path88.resolve(testsRoot, relative);
80656
+ if (!isWithin(testsRoot, candidate) || !fs81.existsSync(candidate)) {
80657
+ throw new BenchmarkPhaseError("invalid_tests_path", `evaluator tests_path does not exist: ${evaluator.tests_path}`);
80658
+ }
80659
+ const stat = fs81.lstatSync(candidate);
80660
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
80661
+ throw new BenchmarkPhaseError("invalid_tests_path", `evaluator tests_path must be a directory: ${evaluator.tests_path}`);
80662
+ }
80663
+ return candidate;
80664
+ }
80665
+ var CriterionResultSchema = exports_external.object({
80666
+ criterion_key: IdSchema,
80667
+ outcome: exports_external.enum(["satisfied", "not_satisfied", "not_applicable"]),
80668
+ explanation: exports_external.string().min(1).max(MAX_CRITERION_EXPLANATION_LENGTH),
80669
+ evidence_ids: exports_external.array(exports_external.string().min(1).max(MAX_CRITERION_EVIDENCE_ID_LENGTH)).max(MAX_CRITERION_EVIDENCE_IDS)
80670
+ }).strict().superRefine((value, context) => {
80671
+ if (new Set(value.evidence_ids).size !== value.evidence_ids.length) {
80672
+ context.addIssue({
80673
+ code: exports_external.ZodIssueCode.custom,
80674
+ path: ["evidence_ids"],
80675
+ message: "evidence_ids must be unique"
80676
+ });
80677
+ }
80678
+ });
80679
+ var CriteriaResultFileSchema = exports_external.object({
80680
+ criteria: exports_external.array(CriterionResultSchema).max(MAX_CRITERIA_PER_EVALUATOR)
80681
+ }).strict().superRefine((value, context) => {
80682
+ if (Buffer.byteLength(JSON.stringify(value)) > MAX_CRITERIA_RESULT_PAYLOAD_BYTES) {
80683
+ context.addIssue({
80684
+ code: exports_external.ZodIssueCode.custom,
80685
+ path: ["criteria"],
80686
+ message: "criterion result exceeds the 20 MiB aggregate payload limit"
80687
+ });
80688
+ }
80689
+ });
80690
+ function readCriterionResults(resultPath, criterionKeys, allowedEvidenceIds, spec, additionalSecrets = []) {
80691
+ let opened;
80692
+ try {
80693
+ opened = openRegularFileNoFollow(resultPath, "criterion result", path88.dirname(resultPath));
80694
+ } catch (error2) {
80695
+ if (error2.code === "ENOENT") {
80696
+ throw new BenchmarkPhaseError("missing_criterion_result", "sandbox evaluator did not write its criterion result");
80697
+ }
80698
+ throw error2;
80699
+ }
80700
+ try {
80701
+ if (opened.stat.size > MAX_CRITERIA_RESULT_BYTES) {
80702
+ throw new BenchmarkPhaseError("invalid_criterion_result", "criterion result exceeds the 24 MiB file limit");
80703
+ }
80704
+ let raw;
80705
+ try {
80706
+ raw = JSON.parse(readDescriptor(opened.fd).toString("utf8"));
80707
+ } catch {
80708
+ throw new BenchmarkPhaseError("invalid_criterion_result", "criterion result is not valid JSON");
80709
+ }
80710
+ let parsed;
80711
+ try {
80712
+ parsed = CriteriaResultFileSchema.parse(raw);
80713
+ } catch (error2) {
80714
+ throw new BenchmarkPhaseError("invalid_criterion_result", "criterion result does not match the required schema", error2 instanceof exports_external.ZodError ? error2.issues : undefined);
80715
+ }
80716
+ const byKey = new Map;
80717
+ for (const criterion of parsed.criteria) {
80718
+ if (byKey.has(criterion.criterion_key)) {
80719
+ throw new BenchmarkPhaseError("invalid_criterion_result", `criterion result is duplicated: ${criterion.criterion_key}`);
80720
+ }
80721
+ byKey.set(criterion.criterion_key, criterion);
80722
+ }
80723
+ const expected = new Set(criterionKeys);
80724
+ const extra = [...byKey.keys()].filter((key2) => !expected.has(key2));
80725
+ const missing = criterionKeys.filter((key2) => !byKey.has(key2));
80726
+ if (extra.length > 0 || missing.length > 0) {
80727
+ throw new BenchmarkPhaseError("invalid_criterion_result", "criterion result keys do not match the evaluator plan", { missing, extra });
80728
+ }
80729
+ const allowedEvidence = new Set(allowedEvidenceIds);
80730
+ const unknownEvidenceIds = [...new Set(parsed.criteria.flatMap((criterion) => criterion.evidence_ids))].filter((evidenceId) => !allowedEvidence.has(evidenceId));
80731
+ if (unknownEvidenceIds.length > 0) {
80732
+ throw new BenchmarkPhaseError("invalid_criterion_result", "criterion result cites evidence outside the evaluator plan", { unknown_evidence_ids: unknownEvidenceIds });
80733
+ }
80734
+ return criterionKeys.map((key2) => {
80735
+ const criterion = byKey.get(key2);
80736
+ return {
80737
+ ...criterion,
80738
+ explanation: redactCommandOutput(Buffer.from(criterion.explanation), spec, additionalSecrets).toString("utf8")
80739
+ };
80740
+ });
80741
+ } finally {
80742
+ fs81.closeSync(opened.fd);
80743
+ }
80744
+ }
80129
80745
  function trajectoryEvents(value) {
80130
80746
  if (Array.isArray(value))
80131
80747
  return value;
@@ -80144,7 +80760,7 @@ function eventType(event) {
80144
80760
  }
80145
80761
  return;
80146
80762
  }
80147
- async function evaluateOne(evaluator, spec, finalOutput, trajectory, frozenEvidence, context) {
80763
+ async function evaluateOne(evaluator, spec, manifest, frozenWorkspaceRoot, finalOutput, trajectory, frozenEvidence, context) {
80148
80764
  const started = Date.now();
80149
80765
  const base2 = {
80150
80766
  id: evaluator.id,
@@ -80156,11 +80772,11 @@ async function evaluateOne(evaluator, spec, finalOutput, trajectory, frozenEvide
80156
80772
  };
80157
80773
  if (evaluator.type === "output_assertion") {
80158
80774
  const assertion = evaluator.assertion;
80159
- let verdict2 = false;
80775
+ let verdict = false;
80160
80776
  if (assertion.operator === "exact")
80161
- verdict2 = finalOutput === assertion.expected;
80777
+ verdict = finalOutput === assertion.expected;
80162
80778
  if (assertion.operator === "contains")
80163
- verdict2 = finalOutput.includes(assertion.expected);
80779
+ verdict = finalOutput.includes(assertion.expected);
80164
80780
  if (assertion.operator === "regex") {
80165
80781
  const regexResult = await runCommand({
80166
80782
  id: `${evaluator.id}.regex`,
@@ -80179,32 +80795,49 @@ async function evaluateOne(evaluator, spec, finalOutput, trajectory, frozenEvide
80179
80795
  cwd: ".",
80180
80796
  secret_env: []
80181
80797
  }, spec.tests_root, spec, context, {
80182
- BB_REGEX_INPUT: frozenEvidence.finalOutputPath,
80183
- BB_REGEX_PATTERN_B64: Buffer.from(assertion.pattern).toString("base64"),
80184
- BB_REGEX_FLAGS: assertion.flags
80798
+ additions: {
80799
+ BB_REGEX_INPUT: frozenEvidence.finalOutputPath,
80800
+ BB_REGEX_PATTERN_B64: Buffer.from(assertion.pattern).toString("base64"),
80801
+ BB_REGEX_FLAGS: assertion.flags
80802
+ },
80803
+ descendantCleanup: "always"
80185
80804
  });
80186
80805
  if (regexResult.exitCode === 2) {
80187
80806
  throw new BenchmarkPhaseError("invalid_evaluator", `invalid regex in evaluator: ${evaluator.id}`);
80188
80807
  }
80189
- verdict2 = regexResult.exitCode === 0;
80808
+ verdict = regexResult.exitCode === 0;
80190
80809
  }
80191
- return { ...base2, status: verdict2 ? "passed" : "failed", verdict: verdict2, duration_ms: Date.now() - started };
80810
+ return {
80811
+ ...base2,
80812
+ status: verdict ? "passed" : "failed",
80813
+ verdict,
80814
+ duration_ms: Date.now() - started,
80815
+ details: {
80816
+ operator: assertion.operator,
80817
+ actual_size_bytes: Buffer.byteLength(finalOutput),
80818
+ ...assertion.operator === "exact" ? {
80819
+ expected_size_bytes: Buffer.byteLength(assertion.expected),
80820
+ actual_sha256: sha256(finalOutput),
80821
+ expected_sha256: sha256(assertion.expected)
80822
+ } : {}
80823
+ }
80824
+ };
80192
80825
  }
80193
80826
  if (evaluator.type === "trajectory_assertion") {
80194
80827
  const count = trajectory.filter((event) => eventType(event) === evaluator.event_type).length;
80195
- const verdict2 = count >= evaluator.min_count && (evaluator.max_count === undefined || count <= evaluator.max_count);
80828
+ const verdict = count >= evaluator.min_count && (evaluator.max_count === undefined || count <= evaluator.max_count);
80196
80829
  return {
80197
80830
  ...base2,
80198
- status: verdict2 ? "passed" : "failed",
80199
- verdict: verdict2,
80831
+ status: verdict ? "passed" : "failed",
80832
+ verdict,
80200
80833
  duration_ms: Date.now() - started,
80201
80834
  details: { count, min_count: evaluator.min_count, max_count: evaluator.max_count }
80202
80835
  };
80203
80836
  }
80204
80837
  if (evaluator.type === "workspace_assertion") {
80205
80838
  const relative = workspaceRel(evaluator.path);
80206
- assertNoSymlinkTraversal(spec.workspace_root, relative);
80207
- const candidate = path88.resolve(spec.workspace_root, relative);
80839
+ assertNoSymlinkTraversal(frozenWorkspaceRoot, relative);
80840
+ const candidate = path88.resolve(frozenWorkspaceRoot, relative);
80208
80841
  let stat = null;
80209
80842
  try {
80210
80843
  stat = fs81.lstatSync(candidate);
@@ -80217,16 +80850,18 @@ async function evaluateOne(evaluator, spec, finalOutput, trajectory, frozenEvide
80217
80850
  throw new BenchmarkPhaseError("unsafe_path", `workspace assertion cannot target a symlink: ${relative}`);
80218
80851
  }
80219
80852
  const exists2 = stat !== null;
80220
- let verdict2 = false;
80853
+ let verdict = false;
80854
+ let actualSha256;
80221
80855
  if (evaluator.assertion.operator === "exists")
80222
- verdict2 = exists2;
80856
+ verdict = exists2;
80223
80857
  if (evaluator.assertion.operator === "not_exists")
80224
- verdict2 = !exists2;
80858
+ verdict = !exists2;
80225
80859
  if (evaluator.assertion.operator === "sha256") {
80226
80860
  if (stat?.isFile()) {
80227
- const opened = openRegularFileNoFollow(candidate, `workspace assertion ${relative}`, spec.workspace_root);
80861
+ const opened = openRegularFileNoFollow(candidate, `workspace assertion ${relative}`, frozenWorkspaceRoot);
80228
80862
  try {
80229
- verdict2 = await sha256OfDescriptor(opened.fd) === evaluator.assertion.expected;
80863
+ actualSha256 = await sha256OfDescriptor(opened.fd);
80864
+ verdict = actualSha256 === evaluator.assertion.expected;
80230
80865
  } finally {
80231
80866
  fs81.closeSync(opened.fd);
80232
80867
  }
@@ -80234,37 +80869,110 @@ async function evaluateOne(evaluator, spec, finalOutput, trajectory, frozenEvide
80234
80869
  }
80235
80870
  if (evaluator.assertion.operator === "contains") {
80236
80871
  if (stat?.isFile()) {
80237
- const opened = openRegularFileNoFollow(candidate, `workspace assertion ${relative}`, spec.workspace_root);
80872
+ const opened = openRegularFileNoFollow(candidate, `workspace assertion ${relative}`, frozenWorkspaceRoot);
80238
80873
  try {
80239
- verdict2 = readDescriptor(opened.fd).toString("utf8").includes(evaluator.assertion.expected);
80874
+ verdict = readDescriptor(opened.fd).toString("utf8").includes(evaluator.assertion.expected);
80240
80875
  } finally {
80241
80876
  fs81.closeSync(opened.fd);
80242
80877
  }
80243
80878
  }
80244
80879
  }
80245
- return { ...base2, status: verdict2 ? "passed" : "failed", verdict: verdict2, duration_ms: Date.now() - started };
80880
+ return {
80881
+ ...base2,
80882
+ status: verdict ? "passed" : "failed",
80883
+ verdict,
80884
+ duration_ms: Date.now() - started,
80885
+ details: {
80886
+ operator: evaluator.assertion.operator,
80887
+ path: relative,
80888
+ exists: exists2,
80889
+ ...stat ? {
80890
+ actual_kind: stat.isFile() ? "file" : stat.isDirectory() ? "directory" : "other",
80891
+ actual_size_bytes: stat.size
80892
+ } : {},
80893
+ ...actualSha256 ? { actual_sha256: actualSha256 } : {}
80894
+ }
80895
+ };
80896
+ }
80897
+ let isolatedWorkspace;
80898
+ let isolatedTestsRoot;
80899
+ let privateResultRoot;
80900
+ const usesLiveWorkspace = evaluator.workspace_mode === "read_only";
80901
+ try {
80902
+ const evaluatorWorkspace = usesLiveWorkspace ? spec.workspace_root : await copyFrozenWorkspace(manifest, spec, context, frozenWorkspaceRoot, "contained_relative_only");
80903
+ if (!usesLiveWorkspace)
80904
+ isolatedWorkspace = evaluatorWorkspace;
80905
+ else
80906
+ await verifyWorkspaceManifestUnchanged(manifest, spec, context);
80907
+ isolatedTestsRoot = await copyEvaluatorTests(spec, evaluator, context);
80908
+ const testsPath = evaluatorTestsPath(evaluator, isolatedTestsRoot);
80909
+ const commandRoot = evaluator.root === "workspace" ? evaluatorWorkspace : isolatedTestsRoot;
80910
+ const command = { id: evaluator.id, ...evaluator.command };
80911
+ const environment = {
80912
+ BRAINBASE_BENCHMARK_WORKSPACE: evaluatorWorkspace,
80913
+ BRAINBASE_BENCHMARK_TESTS: testsPath,
80914
+ BRAINBASE_BENCHMARK_LOGS: spec.logs_root,
80915
+ BRAINBASE_BENCHMARK_FINAL_OUTPUT: frozenEvidence.finalOutputPath,
80916
+ BRAINBASE_BENCHMARK_TRAJECTORY: frozenEvidence.trajectoryPath
80917
+ };
80918
+ let criterionResultPath;
80919
+ if (evaluator.criterion_keys) {
80920
+ privateResultRoot = fs81.mkdtempSync(path88.join(os16.tmpdir(), "brainbase-benchmark-criteria-"));
80921
+ fs81.chmodSync(privateResultRoot, 448);
80922
+ context.temporaryRoots.add(privateResultRoot);
80923
+ criterionResultPath = path88.join(privateResultRoot, "result.json");
80924
+ environment.BRAINBASE_BENCHMARK_CRITERIA_RESULT = criterionResultPath;
80925
+ }
80926
+ const result2 = await runCommand(command, commandRoot, spec, context, {
80927
+ additions: environment,
80928
+ descendantCleanup: "always"
80929
+ });
80930
+ const stdout = await writeLog(spec.logs_root, `${evaluator.id}.stdout.log`, result2.stdout, spec);
80931
+ const stderr = await writeLog(spec.logs_root, `${evaluator.id}.stderr.log`, result2.stderr, spec);
80932
+ let criterionResults;
80933
+ try {
80934
+ criterionResults = evaluator.criterion_keys && criterionResultPath ? readCriterionResults(criterionResultPath, evaluator.criterion_keys, evaluator.allowed_evidence_ids ?? [], spec, result2.redactions) : undefined;
80935
+ } catch (error2) {
80936
+ const normalized = stableError(error2);
80937
+ return {
80938
+ ...base2,
80939
+ status: "errored",
80940
+ verdict: null,
80941
+ duration_ms: result2.durationMs,
80942
+ details: {
80943
+ exit_code: result2.exitCode,
80944
+ error_code: normalized?.code ?? "invalid_criterion_result",
80945
+ error_message: normalized?.message ?? "criterion result validation failed"
80946
+ },
80947
+ stdout,
80948
+ stderr
80949
+ };
80950
+ }
80951
+ const verdict = result2.exitCode === 0;
80952
+ return {
80953
+ ...base2,
80954
+ status: verdict ? "passed" : "failed",
80955
+ verdict,
80956
+ duration_ms: result2.durationMs,
80957
+ details: { exit_code: result2.exitCode },
80958
+ stdout,
80959
+ stderr,
80960
+ ...criterionResults ? { criterion_results: criterionResults } : {}
80961
+ };
80962
+ } finally {
80963
+ try {
80964
+ if (usesLiveWorkspace) {
80965
+ await verifyWorkspaceManifestUnchanged(manifest, spec, context);
80966
+ }
80967
+ } finally {
80968
+ for (const temporary of [privateResultRoot, isolatedTestsRoot, isolatedWorkspace]) {
80969
+ if (!temporary)
80970
+ continue;
80971
+ fs81.rmSync(temporary, { recursive: true, force: true });
80972
+ context.temporaryRoots.delete(temporary);
80973
+ }
80974
+ }
80246
80975
  }
80247
- const commandRoot = evaluator.root === "workspace" ? spec.workspace_root : spec.tests_root;
80248
- const command = { id: evaluator.id, ...evaluator.command };
80249
- const result2 = await runCommand(command, commandRoot, spec, context, {
80250
- BRAINBASE_BENCHMARK_WORKSPACE: spec.workspace_root,
80251
- BRAINBASE_BENCHMARK_TESTS: spec.tests_root,
80252
- BRAINBASE_BENCHMARK_LOGS: spec.logs_root,
80253
- BRAINBASE_BENCHMARK_FINAL_OUTPUT: frozenEvidence.finalOutputPath,
80254
- BRAINBASE_BENCHMARK_TRAJECTORY: frozenEvidence.trajectoryPath
80255
- });
80256
- const stdout = await writeLog(spec.logs_root, `${evaluator.id}.stdout.log`, result2.stdout, spec);
80257
- const stderr = await writeLog(spec.logs_root, `${evaluator.id}.stderr.log`, result2.stderr, spec);
80258
- const verdict = result2.exitCode === 0;
80259
- return {
80260
- ...base2,
80261
- status: verdict ? "passed" : "failed",
80262
- verdict,
80263
- duration_ms: result2.durationMs,
80264
- details: { exit_code: result2.exitCode },
80265
- stdout,
80266
- stderr
80267
- };
80268
80976
  }
80269
80977
  async function executeEvaluate(spec, context) {
80270
80978
  await downloadInputReference(spec.staging_root, spec.evidence.final_output, context);
@@ -80307,6 +81015,7 @@ async function executeEvaluate(spec, context) {
80307
81015
  ];
80308
81016
  outputs.push(...frozenEvidenceRecords);
80309
81017
  context.outputs.push(...frozenEvidenceRecords);
81018
+ assertEvaluateOutputBudget(spec, context);
80310
81019
  assertBudget(context);
80311
81020
  const manifest = await workspaceManifest(spec, context);
80312
81021
  const manifestPath2 = path88.join(spec.logs_root, "candidate-workspace-manifest.json");
@@ -80319,6 +81028,7 @@ async function executeEvaluate(spec, context) {
80319
81028
  const manifestRecord = await recordFile(spec.logs_root, manifestPath2, "logs");
80320
81029
  outputs.push(manifestRecord);
80321
81030
  context.outputs.push(manifestRecord);
81031
+ assertEvaluateOutputBudget(spec, context);
80322
81032
  for (const artifactRelInput of spec.candidate_artifacts) {
80323
81033
  const artifactRel = workspaceRel(artifactRelInput);
80324
81034
  assertNoSymlinkTraversal(spec.workspace_root, artifactRel);
@@ -80336,6 +81046,13 @@ async function executeEvaluate(spec, context) {
80336
81046
  outputs.push(artifact);
80337
81047
  context.outputs.push(artifact);
80338
81048
  }
81049
+ assertEvaluateOutputBudget(spec, context);
81050
+ for (const candidateOutput of spec.candidate_outputs) {
81051
+ const copied = await copyCandidateOutput(candidateOutput, manifest, spec, context);
81052
+ outputs.push(...copied);
81053
+ context.outputs.push(...copied);
81054
+ }
81055
+ assertEvaluateOutputBudget(spec, context);
80339
81056
  if (spec.capture_workspace_archive) {
80340
81057
  const regularFiles = manifest.filter((entry) => entry.kind !== "symlink").map((entry) => entry.path);
80341
81058
  const archive = path88.join(spec.logs_root, "candidate-workspace.tar.gz");
@@ -80349,14 +81066,15 @@ async function executeEvaluate(spec, context) {
80349
81066
  const archiveRecord = await recordFile(spec.logs_root, archive, "logs");
80350
81067
  outputs.push(archiveRecord);
80351
81068
  context.outputs.push(archiveRecord);
81069
+ assertEvaluateOutputBudget(spec, context);
80352
81070
  }
80353
81071
  await verifyRecordsUnchanged(manifest, spec);
81072
+ const frozenWorkspaceRoot = await copyFrozenWorkspace(manifest, spec, context, spec.workspace_root);
80354
81073
  for (const reference of spec.references) {
80355
81074
  const referenceOutputs = await copyMaterial(reference, spec.staging_root, spec.tests_root, "tests", false, context);
80356
81075
  outputs.push(...referenceOutputs);
80357
81076
  context.outputs.push(...referenceOutputs);
80358
81077
  }
80359
- const frozenOutputCount = context.outputs.length;
80360
81078
  const evaluators = [];
80361
81079
  const evaluatorOrder = new Map(spec.evaluators.map((evaluator, index) => [evaluator.id, index]));
80362
81080
  const executionOrder = [...spec.evaluators].sort((left, right) => Number(left.type === "sandbox_command") - Number(right.type === "sandbox_command"));
@@ -80364,7 +81082,7 @@ async function executeEvaluate(spec, context) {
80364
81082
  assertBudget(context);
80365
81083
  const started = Date.now();
80366
81084
  try {
80367
- const evaluated = await evaluateOne(evaluator, spec, finalOutput.buffer.toString("utf8"), trajectory, { finalOutputPath, trajectoryPath }, context);
81085
+ const evaluated = await evaluateOne(evaluator, spec, manifest, frozenWorkspaceRoot, finalOutput.buffer.toString("utf8"), trajectory, { finalOutputPath, trajectoryPath }, context);
80368
81086
  assertBudget(context);
80369
81087
  evaluators.push(evaluated);
80370
81088
  context.evaluators.push(evaluated);
@@ -80378,6 +81096,12 @@ async function executeEvaluate(spec, context) {
80378
81096
  }
80379
81097
  } catch (error2) {
80380
81098
  const normalized = stableError(error2);
81099
+ let stdout;
81100
+ let stderr;
81101
+ if (error2 instanceof BenchmarkCommandExecutionError) {
81102
+ stdout = await writeLog(spec.logs_root, `${evaluator.id}.stdout.log`, error2.stdout, spec);
81103
+ stderr = await writeLog(spec.logs_root, `${evaluator.id}.stderr.log`, error2.stderr, spec);
81104
+ }
80381
81105
  const errored = {
80382
81106
  id: evaluator.id,
80383
81107
  type: evaluator.type,
@@ -80387,24 +81111,38 @@ async function executeEvaluate(spec, context) {
80387
81111
  verdict: null,
80388
81112
  engine: "brainbase-cli",
80389
81113
  engine_version: VERSION,
80390
- duration_ms: Date.now() - started,
81114
+ duration_ms: error2 instanceof BenchmarkCommandExecutionError ? error2.durationMs : Date.now() - started,
80391
81115
  details: {
80392
81116
  error_code: normalized?.code ?? "phase_failed",
80393
81117
  error_message: normalized?.message ?? "evaluator execution failed"
80394
- }
81118
+ },
81119
+ ...stdout ? { stdout } : {},
81120
+ ...stderr ? { stderr } : {}
80395
81121
  };
80396
81122
  evaluators.push(errored);
80397
81123
  context.evaluators.push(errored);
80398
- if (evaluator.required)
81124
+ if (stdout) {
81125
+ outputs.push(stdout);
81126
+ context.outputs.push(stdout);
81127
+ }
81128
+ if (stderr) {
81129
+ outputs.push(stderr);
81130
+ context.outputs.push(stderr);
81131
+ }
81132
+ if (!(error2 instanceof BenchmarkCommandExecutionError))
80399
81133
  throw error2;
80400
81134
  assertBudget(context);
80401
81135
  }
81136
+ if (evaluator.type === "sandbox_command") {
81137
+ assertEvaluateOutputBudget(spec, context);
81138
+ }
80402
81139
  }
80403
- await verifyRecordsUnchanged([...context.inputs, ...context.outputs.slice(0, frozenOutputCount)], spec);
81140
+ await verifyRecordsUnchanged([...context.inputs, ...context.outputs], spec);
80404
81141
  if (!verifyOwnedDirectory(spec.tests_root, "tests", spec) || !verifyOwnedDirectory(spec.logs_root, "logs", spec)) {
80405
81142
  throw new BenchmarkPhaseError("evidence_tampered", "benchmark evaluator changed an owned phase directory marker");
80406
81143
  }
80407
81144
  assertBudget(context);
81145
+ assertEvaluateOutputBudget(spec, context);
80408
81146
  evaluators.sort((left, right) => evaluatorOrder.get(left.id) - evaluatorOrder.get(right.id));
80409
81147
  return { outputs, evaluators };
80410
81148
  }
@@ -80431,6 +81169,67 @@ function stableError(error2) {
80431
81169
  message: error2 instanceof Error ? error2.message : "unknown benchmark phase failure"
80432
81170
  };
80433
81171
  }
81172
+ function compactOversizedResult(result2) {
81173
+ const actualBytes = Buffer.byteLength(`${JSON.stringify(result2, null, 2)}
81174
+ `);
81175
+ if (actualBytes <= MAX_PHASE_RESULT_BYTES)
81176
+ return result2;
81177
+ return {
81178
+ schema_version: result2.schema_version,
81179
+ cli_version: result2.cli_version,
81180
+ phase: result2.phase,
81181
+ attempt_id: result2.attempt_id,
81182
+ phase_id: result2.phase_id,
81183
+ spec_digest: result2.spec_digest,
81184
+ status: "failed",
81185
+ started_at: result2.started_at,
81186
+ completed_at: result2.completed_at,
81187
+ duration_ms: result2.duration_ms,
81188
+ steps: [],
81189
+ inputs: [],
81190
+ outputs: [],
81191
+ error: {
81192
+ code: "result_too_large",
81193
+ message: "benchmark phase result exceeds the 32 MiB contract limit",
81194
+ details: {
81195
+ actual_bytes: actualBytes,
81196
+ max_bytes: MAX_PHASE_RESULT_BYTES,
81197
+ step_count: result2.steps.length,
81198
+ input_count: result2.inputs.length,
81199
+ output_count: result2.outputs.length,
81200
+ evaluator_count: result2.evaluators?.length ?? 0
81201
+ }
81202
+ }
81203
+ };
81204
+ }
81205
+ function readCachedPhaseResult(resultPath) {
81206
+ let opened;
81207
+ try {
81208
+ opened = openRegularFileNoFollow(resultPath, "cached benchmark phase result");
81209
+ } catch {
81210
+ return;
81211
+ }
81212
+ try {
81213
+ if (opened.stat.size > MAX_PHASE_RESULT_BYTES)
81214
+ return;
81215
+ const chunks = [];
81216
+ let offset = 0;
81217
+ while (offset <= MAX_PHASE_RESULT_BYTES) {
81218
+ const buffer = Buffer.alloc(Math.min(64 * 1024, MAX_PHASE_RESULT_BYTES + 1 - offset));
81219
+ const bytesRead = fs81.readSync(opened.fd, buffer, 0, buffer.length, offset);
81220
+ if (bytesRead === 0) {
81221
+ return JSON.parse(Buffer.concat(chunks, offset).toString("utf8"));
81222
+ }
81223
+ chunks.push(buffer.subarray(0, bytesRead));
81224
+ offset += bytesRead;
81225
+ }
81226
+ return;
81227
+ } catch {
81228
+ return;
81229
+ } finally {
81230
+ fs81.closeSync(opened.fd);
81231
+ }
81232
+ }
80434
81233
  function rawIdentity(value) {
80435
81234
  if (!value || typeof value !== "object") {
80436
81235
  return { phase: "unknown", attemptId: null, phaseId: null };
@@ -80503,13 +81302,9 @@ function prepareBenchmarkPhaseInvocation(specPathInput, resultPathInput, expecte
80503
81302
  timeout_ms: spec.budget.timeout_ms
80504
81303
  };
80505
81304
  let cachedResult;
80506
- if (fs81.existsSync(resultPath)) {
80507
- try {
80508
- const cached2 = JSON.parse(fs81.readFileSync(resultPath, "utf8"));
80509
- 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") {
80510
- cachedResult = cached2;
80511
- }
80512
- } catch {}
81305
+ const cached2 = readCachedPhaseResult(resultPath);
81306
+ if (cached2?.schema_version === SCHEMA_VERSION && cached2.phase === spec.phase && cached2.attempt_id === spec.attempt_id && cached2.phase_id === spec.phase_id && cached2.spec_digest === digest && cached2.status === "succeeded") {
81307
+ cachedResult = cached2;
80513
81308
  }
80514
81309
  if (cachedResult) {
80515
81310
  if (spec.phase === "hydrate") {
@@ -80526,7 +81321,7 @@ function prepareBenchmarkPhaseInvocation(specPathInput, resultPathInput, expecte
80526
81321
  } catch (error2) {
80527
81322
  return {
80528
81323
  ok: false,
80529
- result: {
81324
+ result: compactOversizedResult({
80530
81325
  schema_version: SCHEMA_VERSION,
80531
81326
  cli_version: VERSION,
80532
81327
  phase: identity2.phase === "unknown" ? expectedPhase : identity2.phase,
@@ -80541,7 +81336,7 @@ function prepareBenchmarkPhaseInvocation(specPathInput, resultPathInput, expecte
80541
81336
  inputs: [],
80542
81337
  outputs: [],
80543
81338
  error: stableError(error2)
80544
- }
81339
+ })
80545
81340
  };
80546
81341
  }
80547
81342
  }
@@ -80590,6 +81385,7 @@ async function runBenchmarkPhase(specPathInput, resultPathInput, expectedPhase,
80590
81385
  let status = "failed";
80591
81386
  let error2;
80592
81387
  let context;
81388
+ let validatedSpec;
80593
81389
  let resultPathValidated = false;
80594
81390
  try {
80595
81391
  let bytes;
@@ -80609,6 +81405,7 @@ async function runBenchmarkPhase(specPathInput, resultPathInput, expectedPhase,
80609
81405
  }
80610
81406
  identity2 = rawIdentity(raw);
80611
81407
  const spec = BenchmarkSpecSchema.parse(raw);
81408
+ validatedSpec = spec;
80612
81409
  if (expectedPhase && spec.phase !== expectedPhase) {
80613
81410
  throw new BenchmarkPhaseError("phase_mismatch", `the ${expectedPhase} command cannot execute a ${spec.phase} spec`);
80614
81411
  }
@@ -80619,13 +81416,9 @@ async function runBenchmarkPhase(specPathInput, resultPathInput, expectedPhase,
80619
81416
  }
80620
81417
  resultPathValidated = true;
80621
81418
  let cachedResult;
80622
- if (fs81.existsSync(resultPath)) {
80623
- try {
80624
- const cached2 = JSON.parse(fs81.readFileSync(resultPath, "utf8"));
80625
- 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") {
80626
- cachedResult = cached2;
80627
- }
80628
- } catch {}
81419
+ const cached2 = readCachedPhaseResult(resultPath);
81420
+ if (cached2?.schema_version === SCHEMA_VERSION && cached2.phase === spec.phase && cached2.attempt_id === spec.attempt_id && cached2.phase_id === spec.phase_id && cached2.spec_digest === digest && cached2.status === "succeeded") {
81421
+ cachedResult = cached2;
80629
81422
  }
80630
81423
  if (cachedResult) {
80631
81424
  if (spec.phase === "hydrate") {
@@ -80668,7 +81461,7 @@ async function runBenchmarkPhase(specPathInput, resultPathInput, expectedPhase,
80668
81461
  fs81.rmSync(temporary, { recursive: true, force: true });
80669
81462
  }
80670
81463
  }
80671
- const result2 = {
81464
+ let result2 = {
80672
81465
  schema_version: SCHEMA_VERSION,
80673
81466
  cli_version: VERSION,
80674
81467
  phase: identity2.phase === "unknown" && expectedPhase ? expectedPhase : identity2.phase,
@@ -80686,8 +81479,22 @@ async function runBenchmarkPhase(specPathInput, resultPathInput, expectedPhase,
80686
81479
  ...error2 ? { error: error2 } : {}
80687
81480
  };
80688
81481
  if (!resultPathValidated || !context?.logsOwned) {
81482
+ result2 = compactOversizedResult(result2);
80689
81483
  return { exitCode: status === "succeeded" ? 0 : 1, result: result2 };
80690
81484
  }
81485
+ if (status === "succeeded" && validatedSpec?.phase === "evaluate") {
81486
+ try {
81487
+ const resultBytes = Buffer.byteLength(`${JSON.stringify(result2, null, 2)}
81488
+ `);
81489
+ assertEvaluateOutputBudget(validatedSpec, context, resultBytes);
81490
+ } catch (budgetError) {
81491
+ status = "failed";
81492
+ result2.status = "failed";
81493
+ result2.error = stableError(budgetError);
81494
+ }
81495
+ }
81496
+ result2 = compactOversizedResult(result2);
81497
+ status = result2.status;
80691
81498
  try {
80692
81499
  writeJsonAtomic(resultPath, result2);
80693
81500
  } catch (writeError) {