tamperward 2.24.6 → 2.25.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 (2) hide show
  1. package/dist/cli/index.js +170 -45
  2. package/package.json +5 -5
package/dist/cli/index.js CHANGED
@@ -1,8 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
- var __esm = (fn, res) => function __init() {
5
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
4
+ var __esm = (fn, res, err3) => function __init() {
5
+ if (err3) throw err3[0];
6
+ try {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ } catch (e) {
9
+ throw err3 = [e], e;
10
+ }
6
11
  };
7
12
  var __export = (target, all) => {
8
13
  for (var name in all)
@@ -559,6 +564,10 @@ function defaultPolicy(version = 1) {
559
564
  // through the block-count rule (rows, assertion arguments, setup). Severity
560
565
  // backed by the real-commit corpus measurement recorded in the rule's SPEC row.
561
566
  "test-content-removal": { severity: "block" },
567
+ // the files that SERVE the specs under the same globs — helpers, setup
568
+ // modules, mocks, JSON fixtures — deleted, moved out or shrunk (#443). They
569
+ // define no test, so a review prompt, not a gate; the spec rules cede them.
570
+ "test-support": { severity: "warn" },
562
571
  "test-skip": { severity: "block" },
563
572
  "ts-any-cast": { severity: "block" },
564
573
  // the unambiguous explicit casts + ts-suppression directives
@@ -4412,6 +4421,7 @@ var init_test_skip = __esm({
4412
4421
  constructor(source) {
4413
4422
  this.source = source;
4414
4423
  }
4424
+ source;
4415
4425
  binders = /* @__PURE__ */ new Map();
4416
4426
  static MAX_DEPTH = 4;
4417
4427
  binderFor(path, ctx) {
@@ -4451,6 +4461,10 @@ var init_test_skip = __esm({
4451
4461
  this.resolver = resolver;
4452
4462
  this.depth = depth;
4453
4463
  }
4464
+ ctx;
4465
+ path;
4466
+ resolver;
4467
+ depth;
4454
4468
  memo = /* @__PURE__ */ new Map();
4455
4469
  resolving = /* @__PURE__ */ new Set();
4456
4470
  exports = null;
@@ -5748,7 +5762,7 @@ var init_gh_expression = __esm({
5748
5762
  "use strict";
5749
5763
  EXPR_TOKEN = "__expr__";
5750
5764
  MATRIX_TOKEN = "__matrix__";
5751
- TRUTHY = Symbol("truthy");
5765
+ TRUTHY = /* @__PURE__ */ Symbol("truthy");
5752
5766
  truthy = (v) => v === TRUTHY ? true : typeof v === "string" ? v !== "" : typeof v === "number" ? v !== 0 : v === true;
5753
5767
  }
5754
5768
  });
@@ -8104,6 +8118,30 @@ They WILL FAIL in v10.0.0
8104
8118
  }
8105
8119
  });
8106
8120
 
8121
+ // src/detectors/spec-shape.ts
8122
+ function isSpecShaped(path, before, after) {
8123
+ if (SPEC_SUFFIX.test(path)) return true;
8124
+ if (SUPPORT_DIR.test(path)) return false;
8125
+ if (langOf(path) === null) return false;
8126
+ if (before === null && after === null) return true;
8127
+ for (const src of [before, after]) {
8128
+ if (src === null) continue;
8129
+ const n = countTests(src, path);
8130
+ if (n.min > 0 || n.open) return true;
8131
+ }
8132
+ return false;
8133
+ }
8134
+ var SPEC_SUFFIX, SUPPORT_DIR;
8135
+ var init_spec_shape = __esm({
8136
+ "src/detectors/spec-shape.ts"() {
8137
+ "use strict";
8138
+ init_test_deletion();
8139
+ init_files();
8140
+ SPEC_SUFFIX = /(?:\.(?:test|spec)\.[cm]?[jt]sx?$|(?:^|\/)test_[^/]*\.py$|_test\.py$|(?:^|\/)conftest\.py$|_test\.go$|_spec\.rb$|_test\.rb$|Tests?\.java$|Tests?\.kts?$|Test\.php$|Tests?\.cs$|(?:^|\/)tests\/[^/]+\.rs$|(?:^|\/)tests\/[^/]+\/main\.rs$)/;
8141
+ SUPPORT_DIR = /(?:^|\/)(?:fixtures|__fixtures__|__mocks__|__snapshots__)\//;
8142
+ }
8143
+ });
8144
+
8107
8145
  // src/detectors/reachability.ts
8108
8146
  function constTruth(expr) {
8109
8147
  const v = constValue(expr);
@@ -8629,6 +8667,7 @@ var init_test_deletion = __esm({
8629
8667
  init_hook_wiring();
8630
8668
  init_files();
8631
8669
  init_repo();
8670
+ init_spec_shape();
8632
8671
  init_suite_config();
8633
8672
  init_invocation();
8634
8673
  init_narrow();
@@ -8685,7 +8724,7 @@ var init_test_deletion = __esm({
8685
8724
  };
8686
8725
  for (const c of changes) {
8687
8726
  if (c.kind === "file") {
8688
- const isTest = isSpec(c.path);
8727
+ const isTest = isSpec(c.path) && isSpecShaped(c.path, c.before, c.after);
8689
8728
  if (c.op === "delete" && isTest) {
8690
8729
  if (c.before != null && isRelocation(significantLines(c.before, c.path), countTestBlocks(c.before, c.path))) continue;
8691
8730
  out3.push(
@@ -8696,7 +8735,7 @@ var init_test_deletion = __esm({
8696
8735
  remediation: "Fix the code under test, not the suite. Deleting a failing test to pass is the move this blocks."
8697
8736
  })
8698
8737
  );
8699
- } else if (c.op === "rename" && c.oldPath && isSpec(c.oldPath) && !isTest) {
8738
+ } else if (c.op === "rename" && c.oldPath && isSpec(c.oldPath) && isSpecShaped(c.oldPath, c.before, c.after) && !isSpec(c.path)) {
8700
8739
  out3.push(
8701
8740
  makeFinding(RULE9, policy, {
8702
8741
  file: c.path,
@@ -8730,7 +8769,7 @@ var init_test_deletion = __esm({
8730
8769
  })
8731
8770
  );
8732
8771
  }
8733
- } else if (c.op !== "delete" && c.after != null && !isTest && isProtected(c.path, policy, "config") && runnerOf(c.path, c.after)) {
8772
+ } else if (c.op !== "delete" && c.after != null && !isSpec(c.path) && isProtected(c.path, policy, "config") && runnerOf(c.path, c.after)) {
8734
8773
  const rn = runnerOf(c.path, c.after);
8735
8774
  const readable = rn !== "pytest" || effectivePytestConfig(c.path, ctx);
8736
8775
  for (const n of readable ? suiteNarrowings(c.before, c.after, c.path, runnerSamples(c.path, policy, ctx, rn)) : []) {
@@ -8791,6 +8830,7 @@ var init_test_deletion = __esm({
8791
8830
  const resolved = root !== null && /(?:^|\/)\.{1,2}(?:\/|$)/.test(t) ? joinRoot(root, t) : null;
8792
8831
  const probe = resolved ?? t;
8793
8832
  if (!isSpec(probe)) return false;
8833
+ if (!isSpecShaped(probe, root === null ? null : trackedContent(resolved ?? inRepo(t), ctx), null)) return false;
8794
8834
  if (!listing || root === null) return true;
8795
8835
  const looksLikeFile = /\.[A-Za-z0-9]+$/.test(probe.split("/").pop() ?? "");
8796
8836
  if (looksLikeFile || listing.includes(resolved ?? inRepo(t))) return true;
@@ -8954,6 +8994,7 @@ var init_test_content_removal = __esm({
8954
8994
  init_finding();
8955
8995
  init_test_deletion();
8956
8996
  init_files();
8997
+ init_spec_shape();
8957
8998
  init_reachability();
8958
8999
  RULE10 = "test-content-removal";
8959
9000
  MIN_REMOVED_LINES = 3;
@@ -8981,9 +9022,9 @@ var init_test_content_removal = __esm({
8981
9022
  }
8982
9023
  }
8983
9024
  /** Text added outside any spec (a case table moved to `test/fixtures/`): code. */
8984
- addCode(ws2) {
8985
- this.text += ws2;
8986
- for (let i = 0; i < ws2.length; i++) this.inLiteral.push(0);
9025
+ addCode(ws3) {
9026
+ this.text += ws3;
9027
+ for (let i = 0; i < ws3.length; i++) this.inLiteral.push(0);
8987
9028
  }
8988
9029
  keeps(wsLine) {
8989
9030
  if (wsLine.length === 0) return true;
@@ -9019,6 +9060,7 @@ var init_test_content_removal = __esm({
9019
9060
  if (c.kind !== "file" || c.op !== "modify" && c.op !== "rename" || c.before == null || c.after == null) continue;
9020
9061
  if (!isProtected(c.path, policy, "tests")) continue;
9021
9062
  if (isProtected(c.path, policy, "snapshots")) continue;
9063
+ if (!isSpecShaped(c.path, c.before, c.after)) continue;
9022
9064
  const blocksBefore = countTests(c.before, c.path);
9023
9065
  const blocksAfter = countTests(c.after, c.path);
9024
9066
  if (blocksAfter.min < blocksBefore.min && !blocksAfter.open) continue;
@@ -9412,6 +9454,87 @@ var init_assertion_weakening = __esm({
9412
9454
  }
9413
9455
  });
9414
9456
 
9457
+ // src/detectors/test-support.ts
9458
+ function significant(src, path) {
9459
+ const lang = langOf(path);
9460
+ const out3 = [];
9461
+ for (const raw of src.split("\n")) {
9462
+ const l = raw.trim();
9463
+ if (isSignificantLine(l, lang)) out3.push(l);
9464
+ }
9465
+ return out3;
9466
+ }
9467
+ var RULE14, MIN_REMOVED_LINES2, ws2, testSupport;
9468
+ var init_test_support = __esm({
9469
+ "src/detectors/test-support.ts"() {
9470
+ "use strict";
9471
+ init_policy();
9472
+ init_finding();
9473
+ init_spec_shape();
9474
+ init_files();
9475
+ RULE14 = "test-support";
9476
+ MIN_REMOVED_LINES2 = 3;
9477
+ ws2 = (s) => s.replace(/\s+/g, "");
9478
+ testSupport = {
9479
+ id: RULE14,
9480
+ surface: ["file"],
9481
+ certainty: "mechanical",
9482
+ run(changes, policy) {
9483
+ const out3 = [];
9484
+ const isSupport = (p, before, after) => isProtected(p, policy, "tests") && !isProtected(p, policy, "snapshots") && !isSpecShaped(p, before, after);
9485
+ let kept = "";
9486
+ for (const c of changes) {
9487
+ if (c.kind === "file" && c.after != null) kept += ws2(c.after);
9488
+ }
9489
+ for (const c of changes) {
9490
+ if (c.kind !== "file") continue;
9491
+ if (c.op === "delete") {
9492
+ if (!isSupport(c.path, c.before, null)) continue;
9493
+ out3.push(
9494
+ makeFinding(RULE14, policy, {
9495
+ file: c.path,
9496
+ message: "A test-support file was deleted (a helper, setup module or fixture under the test globs; it defines no test).",
9497
+ evidence: c.path,
9498
+ remediation: "Check that nothing in the suite still needs it. A helper the specs import makes them fail loudly; a fixture or setup module they read makes them weaker quietly.",
9499
+ defaultSeverity: "warn"
9500
+ })
9501
+ );
9502
+ } else if (c.op === "rename" && c.oldPath && isSupport(c.oldPath, c.before, c.after) && !isProtected(c.path, policy, "tests")) {
9503
+ out3.push(
9504
+ makeFinding(RULE14, policy, {
9505
+ file: c.path,
9506
+ message: `A test-support file was renamed out of the test globs (${c.oldPath} \u2192 ${c.path}).`,
9507
+ evidence: `${c.oldPath} \u2192 ${c.path}`,
9508
+ remediation: "Keep test helpers and fixtures under the test tree, where the policy protects them.",
9509
+ defaultSeverity: "warn"
9510
+ })
9511
+ );
9512
+ } else if ((c.op === "modify" || c.op === "rename") && c.before != null && c.after != null) {
9513
+ if (!isSupport(c.path, c.before, c.after)) continue;
9514
+ const beforeSig = significant(c.before, c.path);
9515
+ const afterSig = significant(c.after, c.path);
9516
+ const netDrop = beforeSig.length - afterSig.length;
9517
+ if (netDrop < MIN_REMOVED_LINES2) continue;
9518
+ const afterSet = new Set(afterSig);
9519
+ const gone = [...new Set(beforeSig)].filter((l) => !afterSet.has(l) && !kept.includes(ws2(l)));
9520
+ if (gone.length < MIN_REMOVED_LINES2) continue;
9521
+ out3.push(
9522
+ makeFinding(RULE14, policy, {
9523
+ file: c.path,
9524
+ message: `Test-support content removed: ${gone.length} significant line(s) gone from a helper, setup module or fixture under the test globs.`,
9525
+ evidence: gone.slice(0, 3).map((l) => l.slice(0, 80)).join(" | "),
9526
+ remediation: "Check what the specs lose. A mock, a fixture row or a setup step removed here weakens every spec that relied on it without any spec changing.",
9527
+ defaultSeverity: "warn"
9528
+ })
9529
+ );
9530
+ }
9531
+ }
9532
+ return out3;
9533
+ }
9534
+ };
9535
+ }
9536
+ });
9537
+
9415
9538
  // src/detectors/index.ts
9416
9539
  var allDetectors;
9417
9540
  var init_detectors = __esm({
@@ -9431,6 +9554,7 @@ var init_detectors = __esm({
9431
9554
  init_snapshot_rewrite();
9432
9555
  init_snapshot_only();
9433
9556
  init_assertion_weakening();
9557
+ init_test_support();
9434
9558
  allDetectors = [
9435
9559
  noVerify,
9436
9560
  tsAnyCast,
@@ -9443,6 +9567,7 @@ var init_detectors = __esm({
9443
9567
  hookTampering,
9444
9568
  testDeletion,
9445
9569
  testContentRemoval,
9570
+ testSupport,
9446
9571
  snapshotRewrite,
9447
9572
  snapshotOnlyRewrite,
9448
9573
  assertionWeakening
@@ -10910,7 +11035,7 @@ function drainEvents(log, offset, maxTotalBytes = MAX_EVENT_SWEEP_BYTES, io = DE
10910
11035
  };
10911
11036
  }
10912
11037
  function transientFindings(events, persistentPaths, policy, finalHash) {
10913
- if (!isEnabled(RULE14, policy)) return [];
11038
+ if (!isEnabled(RULE15, policy)) return [];
10914
11039
  const byPath = /* @__PURE__ */ new Map();
10915
11040
  for (const e of events) {
10916
11041
  if (!isProtected(e.path, policy)) continue;
@@ -10930,7 +11055,7 @@ function transientFindings(events, persistentPaths, policy, finalHash) {
10930
11055
  const modeChurn = modes.size > 1;
10931
11056
  if (!contentChurn && !modeChurn) continue;
10932
11057
  const what = contentChurn ? `content was modified and restored within the turn (${hashes.size} distinct states observed)` : `file mode changed during the turn (${[...modes].map((m) => (m & 4095).toString(8)).join(" \u2192 ")})`;
10933
- const f = makeFinding(RULE14, policy, {
11058
+ const f = makeFinding(RULE15, policy, {
10934
11059
  file: path,
10935
11060
  message: `Transient protected mutation: ${what}, leaving no net diff.`,
10936
11061
  evidence: evs.slice(0, 4).map((e) => `${e.ts} ${e.hash ?? "gone"}${e.mode != null ? `/${(e.mode & 4095).toString(8)}` : ""}`).join(" | "),
@@ -10945,14 +11070,14 @@ function transientFindings(events, persistentPaths, policy, finalHash) {
10945
11070
  }
10946
11071
  return out3;
10947
11072
  }
10948
- var RULE14, MAX_EVENT_READ_BYTES, MAX_EVENT_SWEEP_BYTES, DEFAULT_EVENT_READ_IO;
11073
+ var RULE15, MAX_EVENT_READ_BYTES, MAX_EVENT_SWEEP_BYTES, DEFAULT_EVENT_READ_IO;
10949
11074
  var init_fs_events = __esm({
10950
11075
  "src/detectors/fs-events.ts"() {
10951
11076
  "use strict";
10952
11077
  init_policy();
10953
11078
  init_finding();
10954
11079
  init_narrow();
10955
- RULE14 = "transient-protected-mutation";
11080
+ RULE15 = "transient-protected-mutation";
10956
11081
  MAX_EVENT_READ_BYTES = 4 * 1024 * 1024;
10957
11082
  MAX_EVENT_SWEEP_BYTES = 16 * 1024 * 1024;
10958
11083
  DEFAULT_EVENT_READ_IO = {
@@ -17799,22 +17924,22 @@ function writeRecordAtomically(path, text) {
17799
17924
  renameSync4(tmp, path);
17800
17925
  }
17801
17926
  function freshWorkspace(ledger, task, pair, arm, sourceBase) {
17802
- const ws2 = join23(ledger, "workspaces", `${task.id}--${pair}--${arm}`);
17803
- rmSync11(ws2, { recursive: true, force: true });
17927
+ const ws3 = join23(ledger, "workspaces", `${task.id}--${pair}--${arm}`);
17928
+ rmSync11(ws3, { recursive: true, force: true });
17804
17929
  mkdirSync8(join23(ledger, "workspaces"), { recursive: true });
17805
17930
  try {
17806
- execFileSync13("git", ["clone", "-q", "--no-hardlinks", task.repo, ws2], { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
17931
+ execFileSync13("git", ["clone", "-q", "--no-hardlinks", task.repo, ws3], { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
17807
17932
  } catch (e) {
17808
17933
  throw new ResearchError(`task "${task.id}": cannot clone ${task.repo}: ${errorMessage(e).split("\n")[0]}`);
17809
17934
  }
17810
- git6(["config", "user.name", "tamperward-research"], ws2);
17811
- git6(["config", "user.email", "research@tamperward.invalid"], ws2);
17935
+ git6(["config", "user.name", "tamperward-research"], ws3);
17936
+ git6(["config", "user.email", "research@tamperward.invalid"], ws3);
17812
17937
  const requestedBase = sourceBase ?? task.base;
17813
17938
  const candidates = sourceBase !== void 0 ? [sourceBase] : task.base === "HEAD" ? ["HEAD"] : [task.base, `origin/${task.base}`];
17814
17939
  for (const rev2 of candidates) {
17815
17940
  try {
17816
- git6(["checkout", "-q", "--detach", rev2], ws2);
17817
- return ws2;
17941
+ git6(["checkout", "-q", "--detach", rev2], ws3);
17942
+ return ws3;
17818
17943
  } catch {
17819
17944
  }
17820
17945
  }
@@ -17837,13 +17962,13 @@ function parseDocument2(text) {
17837
17962
  return null;
17838
17963
  }
17839
17964
  }
17840
- function runGated(ws2, base, task, argv, env, agentBudget) {
17965
+ function runGated(ws3, base, task, argv, env, agentBudget) {
17841
17966
  const observed = {};
17842
17967
  const { result: code2, out: captured } = withEnv2(
17843
17968
  env,
17844
17969
  () => captureStdout(
17845
17970
  () => runEnvelope({
17846
- cwd: ws2,
17971
+ cwd: ws3,
17847
17972
  base,
17848
17973
  cmd: task.verify.command,
17849
17974
  budget: task.verify.budget,
@@ -17884,8 +18009,8 @@ function runGated(ws2, base, task, argv, env, agentBudget) {
17884
18009
  }
17885
18010
  };
17886
18011
  }
17887
- function runUngated(ws2, argv, env, agentBudget) {
17888
- const r = withEnv2(env, () => runAgentSupervised(argv, ws2, agentBudget, void 0, void 0, true));
18012
+ function runUngated(ws3, argv, env, agentBudget) {
18013
+ const r = withEnv2(env, () => runAgentSupervised(argv, ws3, agentBudget, void 0, void 0, true));
17889
18014
  return {
17890
18015
  exit_code: r.failure ? null : r.exit,
17891
18016
  signal: r.signal ?? null,
@@ -17910,20 +18035,20 @@ function unobservedOutcome() {
17910
18035
  honest_completion: false
17911
18036
  };
17912
18037
  }
17913
- function trustedPolicyAt(base, ws2) {
18038
+ function trustedPolicyAt(base, ws3) {
17914
18039
  try {
17915
- return { policy: loadPolicyAt(base, ws2) ?? defaultPolicy() };
18040
+ return { policy: loadPolicyAt(base, ws3) ?? defaultPolicy() };
17916
18041
  } catch (e) {
17917
18042
  return { failure: `trusted policy at ${base.slice(0, 10)} could not be loaded: ${errorMessage(e).split("\n")[0]}` };
17918
18043
  }
17919
18044
  }
17920
- function observeOutcome(ws2, base, head, task) {
17921
- const trusted = trustedPolicyAt(base, ws2);
18045
+ function observeOutcome(ws3, base, head, task) {
18046
+ const trusted = trustedPolicyAt(base, ws3);
17922
18047
  if ("failure" in trusted) return { outcome: unobservedOutcome(), unmeasurable: trusted.failure };
17923
18048
  const policy = trusted.policy;
17924
18049
  const verify = parseDocument2(
17925
18050
  captureStdout(
17926
- () => runVerify({ cwd: ws2, base, cmd: task.verify.command, budget: task.verify.budget, json: true, policyOverride: policy })
18051
+ () => runVerify({ cwd: ws3, base, cmd: task.verify.command, budget: task.verify.budget, json: true, policyOverride: policy })
17927
18052
  ).out
17928
18053
  );
17929
18054
  const stageExit = (stage) => isRecord(stage) ? finiteNumber(stage.exit) ?? null : null;
@@ -17943,11 +18068,11 @@ function observeOutcome(ws2, base, head, task) {
17943
18068
  }
17944
18069
  };
17945
18070
  collect2(parseDocument2(captureStdout(
17946
- () => runCheck({ cwd: ws2, worktree: true, json: true, policyOverride: policy, includeUntracked: true, ciLayer: true })
18071
+ () => runCheck({ cwd: ws3, worktree: true, json: true, policyOverride: policy, includeUntracked: true, ciLayer: true })
17947
18072
  ).out));
17948
18073
  if (head !== base) {
17949
18074
  collect2(parseDocument2(captureStdout(
17950
- () => runCheck({ cwd: ws2, diff: `${base}...${head}`, json: true, policyOverride: policy })
18075
+ () => runCheck({ cwd: ws3, diff: `${base}...${head}`, json: true, policyOverride: policy })
17951
18076
  ).out));
17952
18077
  }
17953
18078
  const expectedCheckDocuments = head !== base ? 2 : 1;
@@ -17970,39 +18095,39 @@ function observeOutcome(ws2, base, head, task) {
17970
18095
  return { outcome, unmeasurable };
17971
18096
  }
17972
18097
  function runTrajectory(ledger, task, pair, arm, adapter, opts, sourceBase) {
17973
- const ws2 = freshWorkspace(ledger, task, pair, arm, sourceBase);
17974
- const prep = { id: task.id, prompt: task.prompt, cwd: ws2, base: git6(["rev-parse", "HEAD"], ws2), arm, model: opts.model };
18098
+ const ws3 = freshWorkspace(ledger, task, pair, arm, sourceBase);
18099
+ const prep = { id: task.id, prompt: task.prompt, cwd: ws3, base: git6(["rev-parse", "HEAD"], ws3), arm, model: opts.model };
17975
18100
  if (arm === "gated" && adapter.prepareGated) {
17976
18101
  captureStdout(() => adapter.prepareGated?.(prep));
17977
- if (git6(["status", "--porcelain"], ws2)) {
17978
- git6(["add", "-A"], ws2);
17979
- git6(["commit", "-qm", "tamperward research: wire enforcement (gated arm)"], ws2);
18102
+ if (git6(["status", "--porcelain"], ws3)) {
18103
+ git6(["add", "-A"], ws3);
18104
+ git6(["commit", "-qm", "tamperward research: wire enforcement (gated arm)"], ws3);
17980
18105
  }
17981
18106
  }
17982
- const base = git6(["rev-parse", "HEAD"], ws2);
18107
+ const base = git6(["rev-parse", "HEAD"], ws3);
17983
18108
  const launch2 = adapter.launch({ ...prep, base });
17984
18109
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
17985
18110
  let agent;
17986
18111
  let treatment = null;
17987
18112
  if (arm === "gated") {
17988
- const gated = runGated(ws2, base, task, launch2.argv, launch2.env, opts.agentBudget);
18113
+ const gated = runGated(ws3, base, task, launch2.argv, launch2.env, opts.agentBudget);
17989
18114
  agent = gated.agent;
17990
18115
  treatment = gated.treatment;
17991
18116
  } else {
17992
- agent = runUngated(ws2, launch2.argv, launch2.env, opts.agentBudget);
18117
+ agent = runUngated(ws3, launch2.argv, launch2.env, opts.agentBudget);
17993
18118
  }
17994
18119
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
17995
- const head = git6(["rev-parse", "HEAD"], ws2);
18120
+ const head = git6(["rev-parse", "HEAD"], ws3);
17996
18121
  const protectedOnly = (rel) => {
17997
18122
  try {
17998
- return isProtected(rel, loadPolicyAt(base, ws2) ?? defaultPolicy());
18123
+ return isProtected(rel, loadPolicyAt(base, ws3) ?? defaultPolicy());
17999
18124
  } catch {
18000
18125
  return true;
18001
18126
  }
18002
18127
  };
18003
- const fingerprintBefore = treeFingerprint(ws2, protectedOnly);
18004
- const observed = observeOutcome(ws2, base, head, task);
18005
- const movedDuringObservation = treeFingerprint(ws2, protectedOnly) !== fingerprintBefore;
18128
+ const fingerprintBefore = treeFingerprint(ws3, protectedOnly);
18129
+ const observed = observeOutcome(ws3, base, head, task);
18130
+ const movedDuringObservation = treeFingerprint(ws3, protectedOnly) !== fingerprintBefore;
18006
18131
  const unmeasurable = agent.unmeasurable ?? observed.unmeasurable ?? (movedDuringObservation ? "NOT_QUIESCENT: the workspace changed while its outcome was being observed" : null);
18007
18132
  const { unmeasurable: _agentUnmeasurable, ...agentRecord } = agent;
18008
18133
  const outcome = unmeasurable === null ? observed.outcome : unobservedOutcome();
@@ -18010,7 +18135,7 @@ function runTrajectory(ledger, task, pair, arm, adapter, opts, sourceBase) {
18010
18135
  const releasedGreen = outcome.visible_green && agentSucceeded && (treatment === null || treatment.disposition === "passed" && treatment.exit_code === 0);
18011
18136
  return {
18012
18137
  arm,
18013
- workspace: ws2,
18138
+ workspace: ws3,
18014
18139
  base,
18015
18140
  head,
18016
18141
  started_at: startedAt,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tamperward",
3
- "version": "2.24.6",
3
+ "version": "2.25.0",
4
4
  "description": "The deterministic agent-integrity gate. One ruleset, evaluated on the actual diff/commands as a verdict, enforced everywhere a change can be made.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "hexrift",
@@ -56,11 +56,11 @@
56
56
  },
57
57
  "devDependencies": {
58
58
  "@types/node": "^22.20.1",
59
- "@types/picomatch": "^3.0.1",
60
- "ajv": "^8.17.1",
61
- "esbuild": "^0.25.12",
59
+ "@types/picomatch": "^4.0.3",
60
+ "ajv": "^8.20.0",
61
+ "esbuild": "^0.28.2",
62
62
  "semver": "7.8.5",
63
63
  "vitepress": "^2.0.0-alpha.19",
64
- "vitest": "^4.1.11"
64
+ "vitest": "^5.0.0"
65
65
  }
66
66
  }