assertledger 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.fr.md +14 -4
  2. package/README.md +14 -4
  3. package/conformance/schema-extensions.json +25 -0
  4. package/dist/build-info.json +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +68 -20
  7. package/dist/cli.js.map +1 -1
  8. package/dist/contracts/index.d.ts +510 -4
  9. package/dist/contracts/index.d.ts.map +1 -1
  10. package/dist/contracts/index.js +230 -16
  11. package/dist/contracts/index.js.map +1 -1
  12. package/dist/contracts/runtime-doctor.d.ts +123 -0
  13. package/dist/contracts/runtime-doctor.d.ts.map +1 -1
  14. package/dist/contracts/runtime-doctor.js +64 -0
  15. package/dist/contracts/runtime-doctor.js.map +1 -1
  16. package/dist/core/index.d.ts.map +1 -1
  17. package/dist/core/index.js +5 -2
  18. package/dist/core/index.js.map +1 -1
  19. package/dist/diagnostics.d.ts.map +1 -1
  20. package/dist/diagnostics.js +26 -2
  21. package/dist/diagnostics.js.map +1 -1
  22. package/dist/engine/adapters/bun-test-profile.d.ts +17 -0
  23. package/dist/engine/adapters/bun-test-profile.d.ts.map +1 -0
  24. package/dist/engine/adapters/bun-test-profile.js +17 -0
  25. package/dist/engine/adapters/bun-test-profile.js.map +1 -0
  26. package/dist/engine/git-regression.d.ts +2 -2
  27. package/dist/engine/git-regression.d.ts.map +1 -1
  28. package/dist/engine/git-regression.js.map +1 -1
  29. package/dist/engine/index.d.ts +11 -5
  30. package/dist/engine/index.d.ts.map +1 -1
  31. package/dist/engine/index.js +454 -49
  32. package/dist/engine/index.js.map +1 -1
  33. package/dist/engine/runtime-doctor.d.ts +13 -4
  34. package/dist/engine/runtime-doctor.d.ts.map +1 -1
  35. package/dist/engine/runtime-doctor.js +80 -2
  36. package/dist/engine/runtime-doctor.js.map +1 -1
  37. package/dist/mcp/index.d.ts.map +1 -1
  38. package/dist/mcp/index.js +36 -6
  39. package/dist/mcp/index.js.map +1 -1
  40. package/dist/sdk/index.d.ts +7 -6
  41. package/dist/sdk/index.d.ts.map +1 -1
  42. package/dist/sdk/index.js +20 -7
  43. package/dist/sdk/index.js.map +1 -1
  44. package/docs/adapter-protocol.md +61 -0
  45. package/docs/architecture.md +4 -1
  46. package/docs/developer-experience.md +7 -5
  47. package/docs/distribution.md +8 -3
  48. package/docs/migration-verification-v3.md +33 -0
  49. package/docs/project-intent.md +7 -5
  50. package/docs/reference.md +6 -5
  51. package/docs/repository-init.md +38 -7
  52. package/docs/runtime-doctor.md +12 -9
  53. package/integrations/bun/assertions.d.mts +4 -0
  54. package/integrations/bun/assertions.mjs +25 -0
  55. package/integrations/bun/driver.d.mts +27 -0
  56. package/integrations/bun/driver.mjs +383 -0
  57. package/integrations/bun/preload.mjs +165 -0
  58. package/integrations/skill/SKILL.md +4 -1
  59. package/package.json +7 -3
  60. package/schemas/evidence-manifest.v3.json +897 -0
  61. package/schemas/repository-init-config.v2.json +210 -0
  62. package/schemas/repository-init-lock.v2.json +162 -0
  63. package/schemas/repository-init-result.v2.json +212 -0
  64. package/schemas/verification-request.v3.json +484 -0
@@ -6,20 +6,24 @@ import os from "node:os";
6
6
  import path from "node:path";
7
7
  import { Readable } from "node:stream";
8
8
  import { pipeline } from "node:stream/promises";
9
- import { pathToFileURL } from "node:url";
9
+ import { fileURLToPath, pathToFileURL } from "node:url";
10
10
  import { createScanner, SyntaxKind } from "typescript/unstable/ast";
11
- import { AdapterSchema, parseAgenticBenchmarkAcquisitionRequest, parseAgenticBenchmarkAcquisitionResult, parseEvidenceManifest, parseRepositoryAudit, parseRepositoryInitConfig, parseRepositoryInitLock, parseRepositoryInitResult, parseVerificationRequest, parseVersionedVerificationRequest, repositoryInitConfigDigest, repositoryInitLockDigest, } from "../contracts/index.js";
11
+ import { AdapterSchema, isRepositoryExcludeName, parseAgenticBenchmarkAcquisitionRequest, parseAgenticBenchmarkAcquisitionResult, parseEvidenceManifest, parseRepositoryAudit, parseRepositoryInitConfig, parseRepositoryInitConfigV2, parseRepositoryInitLock, parseRepositoryInitLockV2, parseRepositoryInitResult, parseRepositoryInitResultV2, parseVersionedRepositoryInitConfig, parseVersionedRepositoryInitLock, parseVerificationRequest, parseVersionedVerificationRequest, RepositoryInitDetectionsSchema, repositoryInitConfigDigest, repositoryInitConfigV2Digest, repositoryInitLockDigest, repositoryInitLockV2Digest, } from "../contracts/index.js";
12
12
  import { assertSafeRelativePath, canonicalize, createAgenticBenchmark, decideEvidence, portablePathKey, replayAgenticBenchmark, replayEvidenceManifest, sealManifestArtifact, sha256Canonical, } from "../core/index.js";
13
13
  import { NODE_TEST_ADAPTER_PROFILE } from "./adapters/node-test-profile.js";
14
+ import { BUN_TEST_ADAPTER_PROFILE } from "./adapters/bun-test-profile.js";
14
15
  import { runNodeTestRuntimePreflight, } from "./adapters/node-test-runtime.js";
15
16
  import { CONTAINER_LIMITATIONS, CONTAINER_REPORTER_FILE, CONTAINER_RESULT_FILE, CONTAINER_WORKSPACE, DEFAULT_CONTAINER_RUNTIME_COMMAND, prepareContainerBackend, runContainerExecution, } from "./container.js";
16
17
  import { normalizeRuntimeFacts, RUNTIME_FACTS_VERSION } from "./adapters/runtime-facts.js";
17
18
  import { NODE_TEST_REPORTER_SOURCE } from "./node-test-reporter.js";
18
- import { runRuntimeDoctorChecks } from "./runtime-doctor.js";
19
+ import { runBunRuntimeDoctorChecks, runRuntimeDoctorChecks, } from "./runtime-doctor.js";
19
20
  const DEFAULT_EXCLUDES = new Set([".git", ".testforge", "node_modules"]);
20
21
  const SHA256_PREFIX = "sha256:";
21
22
  const PROCESS_TERMINATION_GRACE_MS = 250;
22
23
  const CONTROLLED_REPORT_MAXIMUM_BYTES = 64 * 1024;
24
+ const BUN_TEST_DRIVER_PATH = fileURLToPath(new URL("../../integrations/bun/driver.mjs", import.meta.url));
25
+ const BUN_TEST_HELPER_PATH = fileURLToPath(new URL("../../integrations/bun/assertions.mjs", import.meta.url));
26
+ const BUN_TEST_PRELOAD_PATH = fileURLToPath(new URL("../../integrations/bun/preload.mjs", import.meta.url));
23
27
  const TEMPORARY_CLEANUP_OPTIONS = {
24
28
  recursive: true,
25
29
  force: true,
@@ -280,6 +284,32 @@ function registerPortablePath(spellings, relative) {
280
284
  throw new Error("PORTABLE_PATH_COLLISION");
281
285
  spellings.set(key, relative);
282
286
  }
287
+ /** The declared list of a valid configuration; anything unreadable falls back to the defaults,
288
+ * which only widens the inventory and so keeps every link check. */
289
+ async function configuredRepositoryExcludes(root) {
290
+ try {
291
+ const configPath = path.join(root, INIT_CONFIG_FILE);
292
+ if (!(await lstat(configPath)).isFile())
293
+ return [];
294
+ const value = JSON.parse(await readFile(configPath, "utf8"));
295
+ return parseVersionedRepositoryInitConfig(value).repository.exclude;
296
+ }
297
+ catch {
298
+ return [];
299
+ }
300
+ }
301
+ async function configuredRepositoryFramework(root) {
302
+ try {
303
+ const configPath = path.join(root, INIT_CONFIG_FILE);
304
+ if (!(await lstat(configPath)).isFile())
305
+ return undefined;
306
+ const value = JSON.parse(await readFile(configPath, "utf8"));
307
+ return parseVersionedRepositoryInitConfig(value).framework;
308
+ }
309
+ catch {
310
+ return undefined;
311
+ }
312
+ }
283
313
  async function walkFiles(root, excludes) {
284
314
  const files = [];
285
315
  const spellings = new Map();
@@ -419,6 +449,9 @@ function initEvidenceKind(file) {
419
449
  function initNodeTestSource(file) {
420
450
  return /(?:^|\/)[^/]+\.test\.[cm]?[jt]s$/u.test(file);
421
451
  }
452
+ function initBunTestSource(file) {
453
+ return /(?:^|\/)[^/]+\.(?:test|spec)\.[cm]?[jt]sx?$/u.test(file);
454
+ }
422
455
  function initSafePathPattern(value) {
423
456
  if (value.startsWith("-") || value.includes("\\"))
424
457
  return false;
@@ -462,7 +495,10 @@ function initPathPatternMatcher(pattern) {
462
495
  }
463
496
  function initCommandPathMatchers(command, testSources) {
464
497
  const conventionalDirectories = new Set(["test", "tests", "spec", "specs", "__tests__"]);
465
- return command.arguments
498
+ const operands = command.executable === "bun" && command.arguments[0] === "test"
499
+ ? command.arguments.slice(1)
500
+ : command.arguments;
501
+ return operands
466
502
  .filter((argument) => initSafePathPattern(argument) &&
467
503
  (/[*?]/u.test(argument) ||
468
504
  argument.includes("/") ||
@@ -571,8 +607,9 @@ function initEmptyDetections(reasonCodes) {
571
607
  };
572
608
  }
573
609
  function initTerminalResult(status, detections, reasonCodes) {
574
- return parseRepositoryInitResult({
575
- schemaVersion: "1.0.0",
610
+ const bunBuiltIn = detections.adapterRecommendation === "bun-test";
611
+ const result = {
612
+ schemaVersion: bunBuiltIn ? "2.0.0" : "1.0.0",
576
613
  status,
577
614
  reasonCodes: [...new Set(reasonCodes)].sort(),
578
615
  detections: { ...detections, reasonCodes: [...new Set(detections.reasonCodes)].sort() },
@@ -580,7 +617,8 @@ function initTerminalResult(status, detections, reasonCodes) {
580
617
  files: [],
581
618
  requiredOperatorInputs: ["worlds", "candidates"],
582
619
  nextCommands: [{ executable: "assertledger", arguments: ["audit", ".", "--json"] }],
583
- });
620
+ };
621
+ return bunBuiltIn ? parseRepositoryInitResultV2(result) : parseRepositoryInitResult(result);
584
622
  }
585
623
  export async function initializeRepository(requestedRoot, options = {}) {
586
624
  let root;
@@ -594,13 +632,32 @@ export async function initializeRepository(requestedRoot, options = {}) {
594
632
  "REPOSITORY_ROOT_INVALID",
595
633
  ]);
596
634
  }
635
+ const declaredExcludes = options.exclude ?? (await configuredRepositoryExcludes(root));
636
+ const repositoryExclude = [...new Set([...DEFAULT_EXCLUDES, ...declaredExcludes])].sort();
637
+ if (repositoryExclude.length > 1_000 ||
638
+ options.exclude?.some((entry) => !isRepositoryExcludeName(entry))) {
639
+ return initTerminalResult("CONFLICT", initEmptyDetections(["INVALID_REPOSITORY_EXCLUDE"]), [
640
+ "INVALID_REPOSITORY_EXCLUDE",
641
+ ]);
642
+ }
643
+ const inventoryExcludes = effectiveExcludes([...INIT_MANAGED_FILES, ...repositoryExclude]);
597
644
  const candidateRoots = ["tests/candidates"];
598
645
  const outsideCandidateRoots = (file) => !candidateRoots.some((candidateRoot) => {
599
646
  const rootKey = portablePathKey(candidateRoot);
600
647
  const fileKey = portablePathKey(file);
601
648
  return fileKey === rootKey || fileKey.startsWith(`${rootKey}/`);
602
649
  });
603
- const inventory = await walkFiles(root, effectiveExcludes([...INIT_MANAGED_FILES]));
650
+ let inventory;
651
+ try {
652
+ inventory = await walkFiles(root, inventoryExcludes);
653
+ }
654
+ catch (error) {
655
+ if (!(error instanceof Error) || error.message !== "UNSUPPORTED_REPOSITORY_SYMLINK")
656
+ throw error;
657
+ return initTerminalResult("CONFLICT", initEmptyDetections(["UNSUPPORTED_REPOSITORY_SYMLINK"]), [
658
+ "UNSUPPORTED_REPOSITORY_SYMLINK",
659
+ ]);
660
+ }
604
661
  const inScopeFiles = inventory.files.filter(outsideCandidateRoots);
605
662
  const contents = new Map();
606
663
  const evidenceBytes = new Map();
@@ -713,7 +770,7 @@ export async function initializeRepository(requestedRoot, options = {}) {
713
770
  (contents.has("requirements.txt") &&
714
771
  /(^|\n)pytest(?:[=<>!~]|$)/u.test(contents.get("requirements.txt") ?? "")))
715
772
  frameworkFacts.add("pytest");
716
- const frameworkOverride = options.framework;
773
+ const frameworkOverride = options.framework ?? (await configuredRepositoryFramework(root));
717
774
  if (frameworkOverride !== undefined && !isInitFramework(frameworkOverride)) {
718
775
  const detections = initEmptyDetections(["FRAMEWORK_OVERRIDE_INVALID"]);
719
776
  detections.packageManager = packageManager;
@@ -825,8 +882,24 @@ export async function initializeRepository(requestedRoot, options = {}) {
825
882
  adapterRecommendation = "node-test";
826
883
  }
827
884
  }
885
+ else if (framework === "bun:test") {
886
+ const testSources = inScopeFiles.filter((file) => initBunTestSource(file) &&
887
+ initJavaScriptModuleSpecifiers(contents.get(file) ?? "").has("bun:test"));
888
+ const pathMatchers = initCommandPathMatchers(testCommand, testSources);
889
+ const baseTestFiles = pathMatchers.length === 0
890
+ ? testSources
891
+ : testSources.filter((file) => pathMatchers.some((matcher) => matcher(file)));
892
+ if (baseTestFiles.length > 0) {
893
+ adapter = { kind: "bun-test", executable: "bun", baseTestFiles };
894
+ adapterRecommendation = "bun-test";
895
+ }
896
+ }
828
897
  const reasonCodes = adapter === undefined
829
- ? [framework === "node:test" ? "BASE_TEST_FILES_UNAVAILABLE" : "OFFICIAL_ADAPTER_UNAVAILABLE"]
898
+ ? [
899
+ framework === "node:test" || framework === "bun:test"
900
+ ? "BASE_TEST_FILES_UNAVAILABLE"
901
+ : "OFFICIAL_ADAPTER_UNAVAILABLE",
902
+ ]
830
903
  : [];
831
904
  const detections = {
832
905
  packageManager,
@@ -839,17 +912,23 @@ export async function initializeRepository(requestedRoot, options = {}) {
839
912
  if (adapter === undefined)
840
913
  return initTerminalResult("BLOCKED", detections, reasonCodes);
841
914
  await options.afterEvidenceSnapshot?.();
842
- const config = parseRepositoryInitConfig({
843
- schemaVersion: "1.0.0",
844
- repository: { root: ".", exclude: [".git", ".testforge", "node_modules"] },
915
+ const bunBuiltIn = adapter.kind === "bun-test";
916
+ const configValue = {
917
+ schemaVersion: bunBuiltIn ? "2.0.0" : "1.0.0",
918
+ repository: { root: ".", exclude: repositoryExclude },
845
919
  packageManager,
846
920
  framework,
847
921
  testCommand,
848
922
  adapter,
849
923
  candidateRoots: [...candidateRoots],
850
- });
924
+ };
925
+ const config = bunBuiltIn
926
+ ? parseRepositoryInitConfigV2(configValue)
927
+ : parseRepositoryInitConfig(configValue);
851
928
  const configContent = initJsonBytes(config);
852
- const configDigest = repositoryInitConfigDigest(config);
929
+ const configDigest = config.schemaVersion === "2.0.0"
930
+ ? repositoryInitConfigV2Digest(config)
931
+ : repositoryInitConfigDigest(config);
853
932
  const evidenceFiles = inScopeFiles.filter((file) => initEvidenceKind(file) !== undefined);
854
933
  if (adapterEvidencePath !== undefined && !evidenceFiles.includes(adapterEvidencePath))
855
934
  evidenceFiles.push(adapterEvidencePath);
@@ -867,17 +946,33 @@ export async function initializeRepository(requestedRoot, options = {}) {
867
946
  : (initEvidenceKind(file) ?? "ADAPTER_CONFIG"),
868
947
  });
869
948
  }
870
- const lockBase = {
871
- schemaVersion: "1.0.0",
872
- configDigest,
873
- detector: { name: "assertledger-init", version: "1.0.0" },
874
- evidence,
875
- detections,
876
- };
877
- const lock = parseRepositoryInitLock({
878
- ...lockBase,
879
- lockDigest: repositoryInitLockDigest(lockBase),
880
- });
949
+ let lock;
950
+ if (bunBuiltIn) {
951
+ const lockBase = {
952
+ schemaVersion: "2.0.0",
953
+ configDigest,
954
+ detector: { name: "assertledger-init", version: "2.0.0" },
955
+ evidence,
956
+ detections,
957
+ };
958
+ lock = parseRepositoryInitLockV2({
959
+ ...lockBase,
960
+ lockDigest: repositoryInitLockV2Digest(lockBase),
961
+ });
962
+ }
963
+ else {
964
+ const lockBase = {
965
+ schemaVersion: "1.0.0",
966
+ configDigest,
967
+ detector: { name: "assertledger-init", version: "1.0.0" },
968
+ evidence,
969
+ detections: RepositoryInitDetectionsSchema.parse(detections),
970
+ };
971
+ lock = parseRepositoryInitLock({
972
+ ...lockBase,
973
+ lockDigest: repositoryInitLockDigest(lockBase),
974
+ });
975
+ }
881
976
  const lockContent = initJsonBytes(lock);
882
977
  const plannedFiles = [
883
978
  { path: INIT_CONFIG_FILE, digest: rawSha256(configContent), content: configContent },
@@ -885,7 +980,7 @@ export async function initializeRepository(requestedRoot, options = {}) {
885
980
  ];
886
981
  let finalInScopeFiles;
887
982
  try {
888
- const finalInventory = await walkFiles(root, effectiveExcludes([...INIT_MANAGED_FILES]));
983
+ const finalInventory = await walkFiles(root, inventoryExcludes);
889
984
  finalInScopeFiles = finalInventory.files.filter(outsideCandidateRoots);
890
985
  }
891
986
  catch {
@@ -940,7 +1035,7 @@ export async function initializeRepository(requestedRoot, options = {}) {
940
1035
  let structural;
941
1036
  try {
942
1037
  const value = JSON.parse(existingLock);
943
- structural = parseRepositoryInitLock(value);
1038
+ structural = parseVersionedRepositoryInitLock(value);
944
1039
  }
945
1040
  catch {
946
1041
  return initTerminalResult("CONFLICT", { ...detections, reasonCodes: ["LOCK_INVALID"] }, [
@@ -952,8 +1047,8 @@ export async function initializeRepository(requestedRoot, options = {}) {
952
1047
  }
953
1048
  }
954
1049
  if (existingConfig === configContent && existingLock === lockContent) {
955
- return parseRepositoryInitResult({
956
- schemaVersion: "1.0.0",
1050
+ const result = {
1051
+ schemaVersion: bunBuiltIn ? "2.0.0" : "1.0.0",
957
1052
  status: "UNCHANGED",
958
1053
  reasonCodes: [],
959
1054
  detections,
@@ -961,7 +1056,8 @@ export async function initializeRepository(requestedRoot, options = {}) {
961
1056
  files: plannedFiles,
962
1057
  requiredOperatorInputs: ["worlds", "candidates"],
963
1058
  nextCommands: [{ executable: "assertledger", arguments: ["audit", ".", "--json"] }],
964
- });
1059
+ };
1060
+ return bunBuiltIn ? parseRepositoryInitResultV2(result) : parseRepositoryInitResult(result);
965
1061
  }
966
1062
  const actions = [];
967
1063
  if (existingConfig === undefined)
@@ -972,8 +1068,8 @@ export async function initializeRepository(requestedRoot, options = {}) {
972
1068
  actions.push({ kind: "REGENERATE", path: INIT_LOCK_FILE });
973
1069
  actions.sort((left, right) => left.path.localeCompare(right.path));
974
1070
  const status = options.dryRun ? "WOULD_CREATE" : "CREATED";
975
- const result = parseRepositoryInitResult({
976
- schemaVersion: "1.0.0",
1071
+ const resultValue = {
1072
+ schemaVersion: bunBuiltIn ? "2.0.0" : "1.0.0",
977
1073
  status,
978
1074
  reasonCodes: [],
979
1075
  detections,
@@ -981,7 +1077,10 @@ export async function initializeRepository(requestedRoot, options = {}) {
981
1077
  files: plannedFiles,
982
1078
  requiredOperatorInputs: ["worlds", "candidates"],
983
1079
  nextCommands: [{ executable: "assertledger", arguments: ["audit", ".", "--json"] }],
984
- });
1080
+ };
1081
+ const result = bunBuiltIn
1082
+ ? parseRepositoryInitResultV2(resultValue)
1083
+ : parseRepositoryInitResult(resultValue);
985
1084
  if (!options.dryRun) {
986
1085
  for (const action of actions) {
987
1086
  const file = plannedFiles.find((candidate) => candidate.path === action.path);
@@ -1029,9 +1128,52 @@ async function probeRuntimeTemporaryWorkspace() {
1029
1128
  }
1030
1129
  export async function doctorRepositoryRuntime(requestedRoot, options) {
1031
1130
  const repositoryRoot = path.resolve(requestedRoot);
1131
+ const configuration = options.allowUnsafeExecution
1132
+ ? await initializeRepository(repositoryRoot, { dryRun: true })
1133
+ : undefined;
1134
+ if (configuration?.schemaVersion === "2.0.0" &&
1135
+ configuration.detections.adapterRecommendation === "bun-test") {
1136
+ const configFile = configuration.files.find((file) => file.path === INIT_CONFIG_FILE);
1137
+ if (configFile === undefined)
1138
+ throw new Error("BUN_TEST_CONFIG_MISSING");
1139
+ const config = parseRepositoryInitConfigV2(JSON.parse(configFile.content));
1140
+ const environmentAllowlist = ["PATH", "SystemRoot", "WINDIR", "TEMP", "TMP"];
1141
+ let resolvedExecutable = config.adapter.executable;
1142
+ return runBunRuntimeDoctorChecks(repositoryRoot, configuration, {
1143
+ probeExecutable: async () => {
1144
+ const identity = await probeBunTestExecutable(config.adapter.executable, repositoryRoot, environmentAllowlist, 5_000, CONTROLLED_REPORT_MAXIMUM_BYTES);
1145
+ resolvedExecutable = identity.resolvedExecutable;
1146
+ return { bunVersion: identity.bunVersion };
1147
+ },
1148
+ probeDependencies: async () => {
1149
+ const result = await runProcess({
1150
+ executable: resolvedExecutable,
1151
+ args: ["-e", 'import("bun:test").then(() => process.stdout.write("READY"))'],
1152
+ cwd: repositoryRoot,
1153
+ environment: environmentFromAllowlist(environmentAllowlist),
1154
+ timeoutMs: 5_000,
1155
+ maximumOutputBytes: CONTROLLED_REPORT_MAXIMUM_BYTES,
1156
+ });
1157
+ if (result.outcome !== "PASS" ||
1158
+ result.stdout.text !== "READY" ||
1159
+ result.stderr.totalBytes !== 0) {
1160
+ throw new Error("BUN_TEST_DEPENDENCY_UNAVAILABLE");
1161
+ }
1162
+ },
1163
+ probeTemporaryWorkspace: probeRuntimeTemporaryWorkspace,
1164
+ runSyntheticPreflight: async () => {
1165
+ await runBunTestRuntimePreflight(resolvedExecutable, environmentAllowlist, 5_000, CONTROLLED_REPORT_MAXIMUM_BYTES);
1166
+ },
1167
+ });
1168
+ }
1032
1169
  let resolvedExecutable = process.execPath;
1033
1170
  return runRuntimeDoctorChecks(repositoryRoot, options.allowUnsafeExecution, {
1034
- inspectConfiguration: () => initializeRepository(repositoryRoot, { dryRun: true }),
1171
+ inspectConfiguration: async () => {
1172
+ const inspected = configuration ?? (await initializeRepository(repositoryRoot, { dryRun: true }));
1173
+ if (inspected.schemaVersion !== "1.0.0")
1174
+ throw new Error("RUNTIME_CONFIGURATION_UNAVAILABLE");
1175
+ return inspected;
1176
+ },
1035
1177
  probeExecutable: async () => {
1036
1178
  const identity = await probeNodeTestExecutable(process.execPath, repositoryRoot, [], 5_000, CONTROLLED_REPORT_MAXIMUM_BYTES);
1037
1179
  resolvedExecutable = identity.resolvedExecutable;
@@ -1368,9 +1510,10 @@ async function resolveRepositoryRoot(rootInput) {
1368
1510
  throw new TypeError("INVALID_REPOSITORY_ROOT");
1369
1511
  }
1370
1512
  }
1371
- export async function analyzeRepository(rootInput) {
1513
+ export async function analyzeRepository(rootInput, options = {}) {
1372
1514
  const root = await resolveRepositoryRoot(rootInput);
1373
- const { files } = await walkFiles(root, effectiveExcludes([]));
1515
+ const declaredExcludes = options.configuredExcludes === true ? await configuredRepositoryExcludes(root) : [];
1516
+ const { files } = await walkFiles(root, effectiveExcludes(declaredExcludes));
1374
1517
  const digest = createHash("sha256");
1375
1518
  const languageCounts = new Map();
1376
1519
  const frameworks = new Set();
@@ -1777,7 +1920,9 @@ function parseRequest(value) {
1777
1920
  if (candidateRoots.length === 0)
1778
1921
  throw new TypeError("INVALID_CANDIDATE_ROOTS");
1779
1922
  assertPortableStringCollection(candidateRoots, "DUPLICATE_CANDIDATE_ROOT");
1780
- if (adapter.kind !== "node-test" && adapter.kind !== "testforge-command") {
1923
+ if (adapter.kind !== "node-test" &&
1924
+ adapter.kind !== "bun-test" &&
1925
+ adapter.kind !== "testforge-command") {
1781
1926
  throw new TypeError("UNSUPPORTED_ADAPTER");
1782
1927
  }
1783
1928
  // The versioned contract already restricts container isolation to v2 requests.
@@ -1896,13 +2041,15 @@ function parseRequest(value) {
1896
2041
  if (minimumTargetWeightPermille > 1_000) {
1897
2042
  throw new TypeError("INVALID_MINIMUM_TARGET_WEIGHT_PERMILLE");
1898
2043
  }
1899
- const normalizedAdapter = adapter.kind === "node-test"
2044
+ const normalizedAdapter = adapter.kind === "node-test" || adapter.kind === "bun-test"
1900
2045
  ? (() => {
1901
- const extraArguments = parseNodeTestExtraArguments(adapter.extraArguments);
2046
+ const extraArguments = adapter.kind === "node-test"
2047
+ ? parseNodeTestExtraArguments(adapter.extraArguments)
2048
+ : undefined;
1902
2049
  const baseTestFiles = requireStringArray(adapter.baseTestFiles, "base_test_files").map((file) => assertSafeRelativePath(file, ["."]));
1903
2050
  assertPortableStringCollection(baseTestFiles, "DUPLICATE_BASE_TEST_FILE");
1904
2051
  return {
1905
- kind: "node-test",
2052
+ kind: adapter.kind,
1906
2053
  executable: requireString(adapter.executable, "adapter_executable"),
1907
2054
  baseTestFiles,
1908
2055
  ...(extraArguments === undefined ? {} : { extraArguments }),
@@ -2099,6 +2246,19 @@ async function readStructuredCommandReport(resultFile, processResult, maximumByt
2099
2246
  }
2100
2247
  return parseStructuredCommandReport(await readBoundedJsonFile(resultFile, maximumBytes), processResult);
2101
2248
  }
2249
+ function readBunDriverReport(processResult) {
2250
+ if (processResult.outcome === "TIMEOUT" ||
2251
+ processResult.outcome === "INFRA_ERROR" ||
2252
+ processResult.stdout.truncated ||
2253
+ processResult.stdout.totalBytes > CONTROLLED_REPORT_MAXIMUM_BYTES)
2254
+ return undefined;
2255
+ try {
2256
+ return parseStructuredCommandReport(JSON.parse(processResult.stdout.text), processResult);
2257
+ }
2258
+ catch {
2259
+ return undefined;
2260
+ }
2261
+ }
2102
2262
  async function readNodeTestReport(resultFile, processResult, maximumBytes) {
2103
2263
  if (processResult.outcome === "TIMEOUT" || processResult.outcome === "INFRA_ERROR") {
2104
2264
  return undefined;
@@ -2179,6 +2339,71 @@ async function probeNodeTestExecutable(requestedExecutable, repositoryRoot, envi
2179
2339
  nodeVersion: secondProbe.nodeVersion,
2180
2340
  };
2181
2341
  }
2342
+ async function runBunExecutableProbe(executable, repositoryRoot, environmentAllowlist, timeoutMs, maximumOutputBytes) {
2343
+ const result = await runProcess({
2344
+ executable,
2345
+ args: [
2346
+ "-e",
2347
+ "console.log(JSON.stringify({execPath:process.execPath,bunVersion:Bun.version,bunRevision:Bun.revision}))",
2348
+ ],
2349
+ cwd: repositoryRoot,
2350
+ environment: environmentFromAllowlist(environmentAllowlist),
2351
+ timeoutMs: Math.min(timeoutMs, 5_000),
2352
+ maximumOutputBytes: Math.min(maximumOutputBytes, CONTROLLED_REPORT_MAXIMUM_BYTES),
2353
+ });
2354
+ if (result.outcome !== "PASS" || result.stdout.truncated || result.stderr.totalBytes !== 0) {
2355
+ throw new Error("BUN_TEST_EXECUTABLE_PROBE_FAILED");
2356
+ }
2357
+ let report;
2358
+ try {
2359
+ report = JSON.parse(result.stdout.text.trim());
2360
+ }
2361
+ catch {
2362
+ throw new Error("BUN_TEST_EXECUTABLE_PROBE_FAILED");
2363
+ }
2364
+ if (!isRecord(report) ||
2365
+ Object.keys(report).sort().join("\0") !== "bunRevision\0bunVersion\0execPath" ||
2366
+ typeof report.execPath !== "string" ||
2367
+ report.execPath.length === 0 ||
2368
+ typeof report.bunVersion !== "string" ||
2369
+ typeof report.bunRevision !== "string") {
2370
+ throw new Error("BUN_TEST_EXECUTABLE_PROBE_FAILED");
2371
+ }
2372
+ if (report.bunVersion !== BUN_TEST_ADAPTER_PROFILE.bunVersion ||
2373
+ report.bunRevision !== BUN_TEST_ADAPTER_PROFILE.bunRevision) {
2374
+ throw new Error("BUN_TEST_VERSION_UNSUPPORTED");
2375
+ }
2376
+ return {
2377
+ execPath: report.execPath,
2378
+ bunVersion: report.bunVersion,
2379
+ bunRevision: report.bunRevision,
2380
+ };
2381
+ }
2382
+ async function probeBunTestExecutable(requestedExecutable, repositoryRoot, environmentAllowlist, timeoutMs, maximumOutputBytes) {
2383
+ const first = await runBunExecutableProbe(requestedExecutable, repositoryRoot, environmentAllowlist, timeoutMs, maximumOutputBytes);
2384
+ let resolvedExecutable;
2385
+ try {
2386
+ resolvedExecutable = await realpath(first.execPath);
2387
+ if (!(await stat(resolvedExecutable)).isFile())
2388
+ throw new Error("not a file");
2389
+ }
2390
+ catch {
2391
+ throw new Error("BUN_TEST_EXECUTABLE_PROBE_FAILED");
2392
+ }
2393
+ const second = await runBunExecutableProbe(resolvedExecutable, repositoryRoot, environmentAllowlist, timeoutMs, maximumOutputBytes);
2394
+ if ((await realpath(second.execPath)) !== resolvedExecutable ||
2395
+ second.bunVersion !== first.bunVersion ||
2396
+ second.bunRevision !== first.bunRevision) {
2397
+ throw new Error("BUN_TEST_EXECUTABLE_PROBE_FAILED");
2398
+ }
2399
+ return {
2400
+ requestedExecutable,
2401
+ resolvedExecutable,
2402
+ executableDigest: await sha256File(resolvedExecutable),
2403
+ bunVersion: second.bunVersion,
2404
+ bunRevision: second.bunRevision,
2405
+ };
2406
+ }
2182
2407
  // Evaluated by `node -p` inside a fresh container before any candidate code runs there.
2183
2408
  const CONTAINER_NODE_PROBE_SOURCE = '(() => { const fs = require("node:fs"); const crypto = require("node:crypto"); const execPath = fs.realpathSync(process.execPath); return JSON.stringify({ digest: "sha256:" + crypto.createHash("sha256").update(fs.readFileSync(execPath)).digest("hex"), execPath, node: process.versions.node }); })()';
2184
2409
  function parseJsonBytes(bytes) {
@@ -2286,6 +2511,8 @@ function containerPreflightExecutor(container, executable) {
2286
2511
  }
2287
2512
  async function executeContainerAdapter(request, container, workspace, candidateFiles) {
2288
2513
  const adapter = request.adapter;
2514
+ if (adapter.kind === "bun-test")
2515
+ throw new Error("BUN_TEST_CONTAINER_UNSUPPORTED");
2289
2516
  const executed = await runContainerExecution(container, {
2290
2517
  workspace,
2291
2518
  ...(adapter.kind === "node-test" ? { reporterSource: NODE_TEST_REPORTER_SOURCE } : {}),
@@ -2370,6 +2597,9 @@ async function executeTrustedLocalAdapter(request, workspace, temporaryRoot, can
2370
2597
  environment.TESTFORGE_RESULT_FILE = resultFile;
2371
2598
  environment.TESTFORGE_CANDIDATE_FILES = JSON.stringify(candidateFiles);
2372
2599
  }
2600
+ else if (request.adapter.kind === "bun-test") {
2601
+ environment.TESTFORGE_CANDIDATE_FILES = JSON.stringify(candidateFiles);
2602
+ }
2373
2603
  else {
2374
2604
  if (nodeTestReporterPath === undefined)
2375
2605
  throw new Error("NODE_TEST_REPORTER_MISSING");
@@ -2385,20 +2615,30 @@ async function executeTrustedLocalAdapter(request, workspace, temporaryRoot, can
2385
2615
  ...request.adapter.baseTestFiles,
2386
2616
  ...candidateFiles,
2387
2617
  ]
2388
- : request.adapter.arguments;
2618
+ : request.adapter.kind === "bun-test"
2619
+ ? [
2620
+ BUN_TEST_DRIVER_PATH,
2621
+ request.adapter.executable,
2622
+ BUN_TEST_HELPER_PATH,
2623
+ ...request.adapter.baseTestFiles,
2624
+ ]
2625
+ : request.adapter.arguments;
2389
2626
  const result = await runProcess({
2390
- executable: request.adapter.executable,
2627
+ executable: request.adapter.kind === "bun-test" ? process.execPath : request.adapter.executable,
2391
2628
  args,
2392
2629
  cwd: workspace,
2393
2630
  environment,
2394
2631
  timeoutMs: request.budgets.timeoutMsPerExecution,
2395
2632
  maximumOutputBytes: request.budgets.maximumOutputBytes,
2396
2633
  });
2634
+ const structuredReport = request.adapter.kind === "testforge-command"
2635
+ ? await readStructuredCommandReport(resultFile, result, CONTROLLED_REPORT_MAXIMUM_BYTES)
2636
+ : request.adapter.kind === "bun-test"
2637
+ ? readBunDriverReport(result)
2638
+ : undefined;
2397
2639
  return {
2398
2640
  result,
2399
- structuredReport: request.adapter.kind === "testforge-command"
2400
- ? await readStructuredCommandReport(resultFile, result, CONTROLLED_REPORT_MAXIMUM_BYTES)
2401
- : undefined,
2641
+ structuredReport,
2402
2642
  nodeTestReport: request.adapter.kind === "node-test"
2403
2643
  ? await readNodeTestReport(resultFile, result, CONTROLLED_REPORT_MAXIMUM_BYTES)
2404
2644
  : undefined,
@@ -2444,6 +2684,119 @@ async function executeObservation(request, repositoryRoot, world, candidate, att
2444
2684
  await removeTemporaryDirectory(temporaryRoot);
2445
2685
  }
2446
2686
  }
2687
+ async function runBunTestRuntimePreflight(executable, environmentAllowlist, timeoutMs, maximumOutputBytes) {
2688
+ const probeSources = [
2689
+ {
2690
+ name: "assertion",
2691
+ source: 'import { test } from "bun:test"; import { assertSame } from "assertledger/bun"; test("assertion", () => assertSame(1, 2));\n',
2692
+ outcome: "ASSERTION_FAILURE",
2693
+ attributed: true,
2694
+ },
2695
+ {
2696
+ name: "generic-throw",
2697
+ source: 'import { test } from "bun:test"; test("generic", () => { throw new Error("generic"); });\n',
2698
+ outcome: "PROCESS_CRASH",
2699
+ attributed: false,
2700
+ },
2701
+ {
2702
+ name: "caught-assertion",
2703
+ source: 'import { test } from "bun:test"; import { assertSame } from "assertledger/bun"; test("caught", () => { try { assertSame(1, 2); } catch {} throw new Error("generic"); });\n',
2704
+ outcome: "PROCESS_CRASH",
2705
+ attributed: false,
2706
+ },
2707
+ {
2708
+ name: "replayed-assertion",
2709
+ source: 'import { test } from "bun:test"; import { assertSame } from "assertledger/bun"; let saved; try { assertSame(1, 2); } catch (error) { saved = error; } test("replayed", () => { throw saved; });\n',
2710
+ outcome: "PROCESS_CRASH",
2711
+ attributed: false,
2712
+ },
2713
+ {
2714
+ name: "replayed-row",
2715
+ source: 'import { test } from "bun:test"; import { assertSame } from "assertledger/bun"; let saved; test.each([0, 1])("row %i", (row) => { if (row === 0) { try { assertSame(1, 2); } catch (error) { saved = error; } return; } throw saved; });\n',
2716
+ outcome: "PROCESS_CRASH",
2717
+ attributed: false,
2718
+ },
2719
+ {
2720
+ name: "async-cross-test",
2721
+ source: 'import { test } from "bun:test"; import { assertSame } from "assertledger/bun"; let signalReady; const ready = new Promise((resolve) => { signalReady = resolve; }); let rejectReceiver; test("origin", () => { ready.then(() => { try { assertSame(1, 2); } catch (error) { rejectReceiver(error); } }); }); test("receiver", () => new Promise((_, reject) => { rejectReceiver = reject; signalReady(); }));\n',
2722
+ outcome: "PROCESS_CRASH",
2723
+ attributed: false,
2724
+ },
2725
+ {
2726
+ name: "multi-assertion-rows",
2727
+ source: 'import { test } from "bun:test"; import { assertSame } from "assertledger/bun"; test.each([1, 2])("row %i", (row) => assertSame(row, 0));\n',
2728
+ outcome: "ASSERTION_FAILURE",
2729
+ attributed: true,
2730
+ },
2731
+ {
2732
+ name: "operand-throw",
2733
+ source: 'import { test } from "bun:test"; import { assertSame } from "assertledger/bun"; test("operand", () => assertSame((() => { throw new Error("operand"); })(), 1));\n',
2734
+ outcome: "PROCESS_CRASH",
2735
+ attributed: false,
2736
+ },
2737
+ {
2738
+ name: "native-expect",
2739
+ source: 'import { test, expect } from "bun:test"; test("native", () => expect(1).toBe(2));\n',
2740
+ outcome: "PROCESS_CRASH",
2741
+ attributed: false,
2742
+ },
2743
+ {
2744
+ name: "hook-failure",
2745
+ source: 'import { afterEach, test } from "bun:test"; import { assertSame } from "assertledger/bun"; afterEach(() => { throw new Error("hook"); }); test("candidate", () => assertSame(1, 2));\n',
2746
+ outcome: "INFRA_ERROR",
2747
+ attributed: false,
2748
+ },
2749
+ ];
2750
+ try {
2751
+ const probes = [];
2752
+ for (const probe of probeSources) {
2753
+ const evidence = await withEmptyWorkspace(async (workspace) => {
2754
+ await writeFile(path.join(workspace, "package.json"), '{"type":"module"}\n');
2755
+ await writeFile(path.join(workspace, "base.test.ts"), 'import { test } from "bun:test"; test("control", () => {});\n');
2756
+ const candidatePath = path.join(workspace, "candidate.test.ts");
2757
+ await writeFile(candidatePath, probe.source);
2758
+ const result = await runProcess({
2759
+ executable: process.execPath,
2760
+ args: [BUN_TEST_DRIVER_PATH, executable, BUN_TEST_HELPER_PATH, "base.test.ts"],
2761
+ cwd: workspace,
2762
+ environment: {
2763
+ ...environmentFromAllowlist(environmentAllowlist),
2764
+ TESTFORGE_CANDIDATE_FILES: JSON.stringify(["candidate.test.ts"]),
2765
+ },
2766
+ timeoutMs: Math.min(timeoutMs, 5_000),
2767
+ maximumOutputBytes: Math.min(maximumOutputBytes, CONTROLLED_REPORT_MAXIMUM_BYTES),
2768
+ });
2769
+ const report = readBunDriverReport(result);
2770
+ const expectedCandidateCount = probe.name === "hook-failure"
2771
+ ? 0
2772
+ : probe.name === "replayed-row" ||
2773
+ probe.name === "async-cross-test" ||
2774
+ probe.name === "multi-assertion-rows"
2775
+ ? 2
2776
+ : 1;
2777
+ if (report?.outcome !== probe.outcome ||
2778
+ report.attributed !== probe.attributed ||
2779
+ report.testsDiscovered !==
2780
+ (expectedCandidateCount === 0 ? 0 : expectedCandidateCount + 1) ||
2781
+ report.candidateTestsDiscovered !== expectedCandidateCount) {
2782
+ throw new Error("BUN_TEST_PROFILE_PREFLIGHT_FAILED");
2783
+ }
2784
+ return {
2785
+ name: probe.name,
2786
+ outcome: report.outcome,
2787
+ attributed: report.attributed,
2788
+ testsDiscovered: report.testsDiscovered,
2789
+ candidateTestsDiscovered: report.candidateTestsDiscovered,
2790
+ };
2791
+ });
2792
+ probes.push(evidence);
2793
+ }
2794
+ return { protocolVersion: "1.0.0", probes };
2795
+ }
2796
+ catch (error) {
2797
+ throw new Error("BUN_TEST_PROFILE_PREFLIGHT_FAILED", { cause: error });
2798
+ }
2799
+ }
2447
2800
  export async function verifyCampaign(value, options = {}) {
2448
2801
  const request = parseRequest(value);
2449
2802
  const container = request.isolation.kind === "container"
@@ -2466,6 +2819,15 @@ export async function verifyCampaign(value, options = {}) {
2466
2819
  if (request.adapter.kind === "node-test" && nodeIdentity !== undefined) {
2467
2820
  request.adapter.executable = nodeIdentity.resolvedExecutable;
2468
2821
  }
2822
+ const bunIdentity = request.adapter.kind === "bun-test"
2823
+ ? await probeBunTestExecutable(request.adapter.executable, repositoryRoot, trustedLocalEnvironmentAllowlist(request), request.budgets.timeoutMsPerExecution, request.budgets.maximumOutputBytes)
2824
+ : undefined;
2825
+ if (request.adapter.kind === "bun-test" && bunIdentity !== undefined) {
2826
+ request.adapter.executable = bunIdentity.resolvedExecutable;
2827
+ }
2828
+ const bunDriverDigest = request.adapter.kind === "bun-test" ? await sha256File(BUN_TEST_DRIVER_PATH) : undefined;
2829
+ const bunHelperDigest = request.adapter.kind === "bun-test" ? await sha256File(BUN_TEST_HELPER_PATH) : undefined;
2830
+ const bunPreloadDigest = request.adapter.kind === "bun-test" ? await sha256File(BUN_TEST_PRELOAD_PATH) : undefined;
2469
2831
  const nodeRuntimePreflight = request.adapter.kind === "node-test" && nodeIdentity !== undefined
2470
2832
  ? await runNodeTestRuntimePreflight({
2471
2833
  executable: nodeIdentity.resolvedExecutable,
@@ -2481,6 +2843,9 @@ export async function verifyCampaign(value, options = {}) {
2481
2843
  }),
2482
2844
  })
2483
2845
  : undefined;
2846
+ const bunRuntimePreflight = request.adapter.kind === "bun-test" && bunIdentity !== undefined
2847
+ ? await runBunTestRuntimePreflight(bunIdentity.resolvedExecutable, trustedLocalEnvironmentAllowlist(request), request.budgets.timeoutMsPerExecution, request.budgets.maximumOutputBytes)
2848
+ : undefined;
2484
2849
  const campaignRoot = await realpath(await mkdtemp(path.join(os.tmpdir(), "testforge-campaign-")));
2485
2850
  const repositorySnapshot = path.join(campaignRoot, "repository");
2486
2851
  try {
@@ -2495,7 +2860,9 @@ export async function verifyCampaign(value, options = {}) {
2495
2860
  const inputPaths = [
2496
2861
  ...request.worlds.flatMap((world) => world.files.map((file) => file.path)),
2497
2862
  ...request.candidates.flatMap((candidate) => candidate.files.map((file) => file.path)),
2498
- ...(request.adapter.kind === "node-test" ? request.adapter.baseTestFiles : []),
2863
+ ...(request.adapter.kind === "node-test" || request.adapter.kind === "bun-test"
2864
+ ? request.adapter.baseTestFiles
2865
+ : []),
2499
2866
  ...request.candidateRoots,
2500
2867
  ];
2501
2868
  for (const inputPath of inputPaths) {
@@ -2537,7 +2904,9 @@ export async function verifyCampaign(value, options = {}) {
2537
2904
  name: request.adapter.kind,
2538
2905
  version: request.adapter.kind === "testforge-command"
2539
2906
  ? request.adapter.protocolVersion
2540
- : (nodeIdentity?.nodeVersion ?? "unprobed"),
2907
+ : request.adapter.kind === "bun-test"
2908
+ ? (bunIdentity?.bunVersion ?? "unprobed")
2909
+ : (nodeIdentity?.nodeVersion ?? "unprobed"),
2541
2910
  configuration: request.adapter.kind === "node-test" && nodeIdentity !== undefined
2542
2911
  ? {
2543
2912
  kind: "node-test",
@@ -2560,7 +2929,31 @@ export async function verifyCampaign(value, options = {}) {
2560
2929
  ...request.adapter.baseTestFiles,
2561
2930
  ],
2562
2931
  }
2563
- : request.adapter,
2932
+ : request.adapter.kind === "bun-test" && bunIdentity !== undefined
2933
+ ? {
2934
+ kind: "bun-test",
2935
+ profile: BUN_TEST_ADAPTER_PROFILE,
2936
+ requestedExecutable: bunIdentity.requestedExecutable,
2937
+ resolvedExecutable: bunIdentity.resolvedExecutable,
2938
+ executableDigest: bunIdentity.executableDigest,
2939
+ bunVersion: bunIdentity.bunVersion,
2940
+ bunRevision: bunIdentity.bunRevision,
2941
+ driverDigest: bunDriverDigest,
2942
+ helperDigest: bunHelperDigest,
2943
+ preloadDigest: bunPreloadDigest,
2944
+ runtimePreflight: bunRuntimePreflight,
2945
+ arguments: [
2946
+ "test",
2947
+ "--max-concurrency=1",
2948
+ "--retry=0",
2949
+ "--preload",
2950
+ "./node_modules/assertledger/preload.mjs",
2951
+ "--reporter=junit",
2952
+ "--reporter-outfile=<generated-junit-file>",
2953
+ ...request.adapter.baseTestFiles,
2954
+ ],
2955
+ }
2956
+ : request.adapter,
2564
2957
  },
2565
2958
  execution: {
2566
2959
  isolation: container === undefined ? "UNSANDBOXED" : "CONTAINER",
@@ -2612,9 +3005,21 @@ export async function verifyCampaign(value, options = {}) {
2612
3005
  ? [
2613
3006
  "UNSANDBOXED trusted-local execution cannot safely contain hostile candidate code.",
2614
3007
  "Process-tree termination is best effort and depends on host operating-system facilities.",
3008
+ ...(request.adapter.kind === "bun-test"
3009
+ ? [
3010
+ "Only failures from the AssertLedger-owned assertSame helper are admitted as Bun assertions; native bun:test expect failures are not assertion evidence.",
3011
+ "Bun callback instrumentation determines assertion ownership; JUnit counts only check completeness and never classify assertions.",
3012
+ ]
3013
+ : []),
2615
3014
  ]
2616
3015
  : [...CONTAINER_LIMITATIONS],
2617
3016
  };
3017
+ if (request.adapter.kind === "bun-test" &&
3018
+ ((await sha256File(BUN_TEST_DRIVER_PATH)) !== bunDriverDigest ||
3019
+ (await sha256File(BUN_TEST_HELPER_PATH)) !== bunHelperDigest ||
3020
+ (await sha256File(BUN_TEST_PRELOAD_PATH)) !== bunPreloadDigest)) {
3021
+ throw new Error("BUN_TEST_ASSET_CHANGED_DURING_CAMPAIGN");
3022
+ }
2618
3023
  return sealManifestArtifact(finalManifest);
2619
3024
  }
2620
3025
  finally {