@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.
- package/README.md +14 -7
- package/dist/index.js +242 -59
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,21 +72,28 @@ synchronously. Schema v1 supports:
|
|
|
72
72
|
- `workspace_assertion`: file existence, absence, SHA-256, or content checks.
|
|
73
73
|
- `sandbox_command`: a bounded argv command rooted in the workspace or hidden
|
|
74
74
|
tests directory. Exit zero passes; any other exit code is a valid failed
|
|
75
|
-
verdict. Schema v1 allows
|
|
76
|
-
read-only assertions
|
|
75
|
+
verdict. Schema v1 allows up to 20 command evaluators and runs them after all
|
|
76
|
+
read-only assertions. Each command receives either the verified live workspace
|
|
77
|
+
for legacy read-only evaluation or its own isolated frozen workspace copy.
|
|
77
78
|
|
|
78
79
|
Valid failed verdicts still produce a successful evaluation phase. Invalid
|
|
79
|
-
specs, digest/path violations, missing environment,
|
|
80
|
-
and output-budget violations fail the phase.
|
|
80
|
+
specs, digest/path violations, missing environment, phase-budget exhaustion,
|
|
81
|
+
and output-budget violations fail the phase. Individual command launch failures,
|
|
82
|
+
timeouts, and forced terminations are recorded as errored evaluator results so
|
|
83
|
+
the remaining evaluators can still run. Results are written atomically
|
|
81
84
|
with mode `0600`, include the raw spec digest and checksummed evidence/log
|
|
82
85
|
input/output references with explicit `staging`, `workspace`, `tests`, or
|
|
83
86
|
`logs` roots, redact declared secret values from command logs, require the
|
|
84
87
|
result path to be `<logs_root>/result.json`, and treat a matching successful
|
|
85
88
|
phase result as authoritative on replay.
|
|
86
89
|
|
|
87
|
-
On any phase-level execution failure, MAS must tear down the task sandbox.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
On any phase-level execution failure, MAS must tear down the task sandbox.
|
|
91
|
+
Failed and timed-out commands clean up the process group and descendants the
|
|
92
|
+
CLI can observe. Successful evaluator commands receive the same cleanup so one
|
|
93
|
+
check cannot contaminate the next. Successful hydration setup commands may
|
|
94
|
+
intentionally leave services running for the agent turn; the runtime lifecycle
|
|
95
|
+
remains their final cleanup boundary. Isolated evaluator workspace copies omit
|
|
96
|
+
`.git` and unsafe absolute or escaping symlinks.
|
|
90
97
|
|
|
91
98
|
## Development
|
|
92
99
|
|
package/dist/index.js
CHANGED
|
@@ -36008,7 +36008,7 @@ function padStart(s, n) {
|
|
|
36008
36008
|
// package.json
|
|
36009
36009
|
var package_default = {
|
|
36010
36010
|
name: "@brainbase-labs/cli",
|
|
36011
|
-
version: "0.25.0-eng1209.
|
|
36011
|
+
version: "0.25.0-eng1209.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
|
-
|
|
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,
|
|
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
|
-
|
|
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("
|
|
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
|
-
|
|
80058
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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 (
|
|
80438
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
80628
|
-
|
|
80629
|
-
|
|
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
|
-
|
|
80729
|
-
|
|
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,
|
|
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
|
-
|
|
80783
|
-
if (
|
|
80784
|
-
|
|
80785
|
-
|
|
80786
|
-
|
|
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
|
|
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);
|