@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.
- package/README.md +14 -7
- package/dist/index.js +905 -98
- 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.
|
|
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(
|
|
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 >
|
|
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:
|
|
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:
|
|
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)
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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("
|
|
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
|
-
|
|
79929
|
-
|
|
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
|
|
80775
|
+
let verdict = false;
|
|
80160
80776
|
if (assertion.operator === "exact")
|
|
80161
|
-
|
|
80777
|
+
verdict = finalOutput === assertion.expected;
|
|
80162
80778
|
if (assertion.operator === "contains")
|
|
80163
|
-
|
|
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
|
-
|
|
80183
|
-
|
|
80184
|
-
|
|
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
|
-
|
|
80808
|
+
verdict = regexResult.exitCode === 0;
|
|
80190
80809
|
}
|
|
80191
|
-
return {
|
|
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
|
|
80828
|
+
const verdict = count >= evaluator.min_count && (evaluator.max_count === undefined || count <= evaluator.max_count);
|
|
80196
80829
|
return {
|
|
80197
80830
|
...base2,
|
|
80198
|
-
status:
|
|
80199
|
-
verdict
|
|
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(
|
|
80207
|
-
const candidate = path88.resolve(
|
|
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
|
|
80853
|
+
let verdict = false;
|
|
80854
|
+
let actualSha256;
|
|
80221
80855
|
if (evaluator.assertion.operator === "exists")
|
|
80222
|
-
|
|
80856
|
+
verdict = exists2;
|
|
80223
80857
|
if (evaluator.assertion.operator === "not_exists")
|
|
80224
|
-
|
|
80858
|
+
verdict = !exists2;
|
|
80225
80859
|
if (evaluator.assertion.operator === "sha256") {
|
|
80226
80860
|
if (stat?.isFile()) {
|
|
80227
|
-
const opened = openRegularFileNoFollow(candidate, `workspace assertion ${relative}`,
|
|
80861
|
+
const opened = openRegularFileNoFollow(candidate, `workspace assertion ${relative}`, frozenWorkspaceRoot);
|
|
80228
80862
|
try {
|
|
80229
|
-
|
|
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}`,
|
|
80872
|
+
const opened = openRegularFileNoFollow(candidate, `workspace assertion ${relative}`, frozenWorkspaceRoot);
|
|
80238
80873
|
try {
|
|
80239
|
-
|
|
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 {
|
|
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 (
|
|
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
|
|
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
|
-
|
|
80507
|
-
|
|
80508
|
-
|
|
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
|
-
|
|
80623
|
-
|
|
80624
|
-
|
|
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
|
-
|
|
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) {
|