tamperward 2.39.7 → 2.39.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli/index.js +169 -130
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -10697,7 +10697,6 @@ var init_engine = __esm({
10697
10697
  });
10698
10698
 
10699
10699
  // src/policy-load.ts
10700
- import { readFileSync as readFileSync5, existsSync as existsSync5 } from "node:fs";
10701
10700
  import { isAbsolute as isAbsolute7, join as join8, posix as posix3 } from "node:path";
10702
10701
  function normalizeVersion(v, where = POLICY_FILE) {
10703
10702
  if (v === void 0 || v === null) return 1;
@@ -10869,10 +10868,29 @@ function parseOrThrow(src, where) {
10869
10868
  }
10870
10869
  return parsePolicy(raw, where);
10871
10870
  }
10871
+ function notARegularFile(e) {
10872
+ switch (e.kind) {
10873
+ case "symlink":
10874
+ return `is a symbolic link to ${escapeControl(e.detail)}`;
10875
+ case "directory":
10876
+ return "is a directory";
10877
+ case "irregular":
10878
+ return `is a ${e.detail}`;
10879
+ case "oversize":
10880
+ return `is ${e.detail}, above the ${READ_CAP / (1024 * 1024)} MiB the gate reads`;
10881
+ default:
10882
+ return `cannot be read (${e.detail})`;
10883
+ }
10884
+ }
10872
10885
  function loadPolicy(cwd = process.cwd()) {
10873
- const path = join8(repoRoot(cwd), POLICY_FILE);
10874
- if (!existsSync5(path)) return defaultPolicy();
10875
- return parseOrThrow(readFileSync5(path, "utf8"), POLICY_FILE);
10886
+ const entry = inspectPath(join8(repoRoot(cwd), POLICY_FILE));
10887
+ if (entry.kind === "absent") return defaultPolicy();
10888
+ if (entry.kind !== "file") {
10889
+ throw new PolicyError(
10890
+ `${POLICY_FILE} ${notARegularFile(entry)}; the policy is read only as a regular file at the repository root \u2014 put one there (no symbolic link, FIFO, socket, device or directory) or remove it`
10891
+ );
10892
+ }
10893
+ return parseOrThrow(textOf2(entry) ?? "", POLICY_FILE);
10876
10894
  }
10877
10895
  function loadPolicyAt(rev2, cwd) {
10878
10896
  const src = fileAt(rev2, POLICY_FILE, { cwd: cwd === void 0 ? void 0 : repoRoot(cwd) });
@@ -10886,6 +10904,7 @@ var init_policy_load = __esm({
10886
10904
  init_lazy_deps();
10887
10905
  init_policy();
10888
10906
  init_build();
10907
+ init_disk();
10889
10908
  init_narrow();
10890
10909
  init_repo_context();
10891
10910
  PolicyError = class extends Error {
@@ -10900,7 +10919,7 @@ var init_policy_load = __esm({
10900
10919
 
10901
10920
  // src/signoff.ts
10902
10921
  import { createHash as createHash3 } from "node:crypto";
10903
- import { appendFileSync, closeSync as closeSync2, constants as constants3, fstatSync as fstatSync2, lstatSync as lstatSync3, mkdirSync, openSync as openSync2, readFileSync as readFileSync6 } from "node:fs";
10922
+ import { appendFileSync, closeSync as closeSync2, constants as constants3, fstatSync as fstatSync2, lstatSync as lstatSync3, mkdirSync, openSync as openSync2, readFileSync as readFileSync5 } from "node:fs";
10904
10923
  import { dirname as dirname2, join as join9, posix as posix4 } from "node:path";
10905
10924
  function compactOobToken(want, head) {
10906
10925
  const normalizedHead = head.trim().toLowerCase();
@@ -10983,7 +11002,7 @@ function readLedger(cwd, policy) {
10983
11002
  const fd = openLedger(rel, path, constants3.O_RDONLY);
10984
11003
  let text;
10985
11004
  try {
10986
- text = readFileSync6(fd, "utf8");
11005
+ text = readFileSync5(fd, "utf8");
10987
11006
  } finally {
10988
11007
  closeSync2(fd);
10989
11008
  }
@@ -11030,25 +11049,25 @@ function applyLocalSignoffs(findings, cwd, policy, now = Date.now()) {
11030
11049
  return { findings: remaining, cleared };
11031
11050
  }
11032
11051
  function oobToken(want, oob, head) {
11052
+ const supplied = head !== void 0 && head.trim() !== "";
11053
+ const bound = supplied && FULL_OBJECT_ID.test(head.trim()) ? head.trim().toLowerCase() : null;
11033
11054
  for (const raw of oob) {
11034
11055
  const t = raw.trim();
11035
11056
  if (!t) continue;
11036
11057
  if (t.startsWith(COMPACT_OOB_PREFIX)) {
11037
- const expected = head === void 0 ? null : compactOobToken(want, head);
11058
+ const expected = bound === null ? null : compactOobToken(want, bound);
11038
11059
  if (expected !== null && t === expected) return t;
11039
11060
  continue;
11040
11061
  }
11041
11062
  const at = t.lastIndexOf("@");
11042
11063
  if (at === -1) {
11043
- if (!head && t === want) return t;
11064
+ if (!supplied && t === want) return t;
11044
11065
  continue;
11045
11066
  }
11046
11067
  const [rule, sha3] = [t.slice(0, at), t.slice(at + 1)];
11047
11068
  if (rule !== want) continue;
11048
- if (!head) return t;
11049
- const normalizedHead = head.trim().toLowerCase();
11050
- const normalizedSha = sha3.trim().toLowerCase();
11051
- if (/^[0-9a-f]+$/.test(normalizedHead) && normalizedSha === normalizedHead) return t;
11069
+ if (!supplied) return t;
11070
+ if (bound !== null && sha3.trim().toLowerCase() === bound) return t;
11052
11071
  }
11053
11072
  return null;
11054
11073
  }
@@ -11065,7 +11084,12 @@ function applyOobSignoffs(findings, oob, head) {
11065
11084
  }
11066
11085
  function oobHeadFromEnv(env = process.env) {
11067
11086
  const v = (env.TAMPERWARD_OOB_HEAD ?? "").trim().toLowerCase();
11068
- return /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(v) ? v : void 0;
11087
+ return v === "" ? void 0 : v;
11088
+ }
11089
+ function oobHeadProblem(head) {
11090
+ if (head === void 0 || head.trim() === "" || FULL_OBJECT_ID.test(head.trim())) return null;
11091
+ const shown = head.length > 72 ? `${head.slice(0, 69)}\u2026` : head;
11092
+ return `TAMPERWARD_OOB_HEAD is set to ${JSON.stringify(shown)}, which is not the full 40- or 64-character object id of the head under adjudication; no out-of-band approval is honored against a head the gate cannot identify`;
11069
11093
  }
11070
11094
  function oobFromEnv(env = process.env) {
11071
11095
  return (env.TAMPERWARD_OOB_SIGNOFF ?? "").split(",").map((s) => s.trim().replace(/^tamperward:allow:/, "")).filter(Boolean);
@@ -11459,8 +11483,13 @@ function check(opts) {
11459
11483
  const ignoredFiles = changes.filter((c) => isSuppressed(c, policy)).length;
11460
11484
  let findings = evaluate(changes, policy, void 0, view, { cwd });
11461
11485
  if (view === "worktree") findings = findings.concat(unjudgeableProtected(cwd, policy, changes));
11462
- const { findings: remaining, cleared } = layer === "local" ? applyLocalSignoffs(findings, cwd, policy) : applyOobSignoffs(findings, oobFromEnv(), oobHeadFromEnv());
11486
+ const oob = layer === "ci" ? oobFromEnv() : [];
11487
+ const oobHead = layer === "ci" ? oobHeadFromEnv() : void 0;
11488
+ const { findings: remaining, cleared } = layer === "local" ? applyLocalSignoffs(findings, cwd, policy) : applyOobSignoffs(findings, oob, oobHead);
11463
11489
  findings = remaining;
11490
+ const headProblem = oob.length > 0 ? oobHeadProblem(oobHead) : null;
11491
+ if (headProblem) process.stderr.write(`tamperward: ${headProblem}
11492
+ `);
11464
11493
  if (cleared.length) {
11465
11494
  const how = layer === "local" ? "local human sign-off (ledger)" : "out-of-band approval";
11466
11495
  process.stderr.write(`tamperward: ${cleared.length} blocking finding(s) cleared by ${how}: ${cleared.map((f) => f.rule + (f.file ? `(${f.file})` : "")).join(", ")}
@@ -11524,7 +11553,7 @@ var init_deny = __esm({
11524
11553
  });
11525
11554
 
11526
11555
  // src/session.ts
11527
- import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync7, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
11556
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync6, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
11528
11557
  import { dirname as dirname3, join as join10 } from "node:path";
11529
11558
  function writeBaseline(p, sha3) {
11530
11559
  mkdirSync2(dirname3(p), { recursive: true });
@@ -11550,8 +11579,8 @@ function turnBaseline(cwd, sessionId) {
11550
11579
  const p = baselinePath(cwd, sessionId);
11551
11580
  if (!p) return null;
11552
11581
  try {
11553
- if (existsSync6(p)) {
11554
- const v = readFileSync7(p, "utf8").trim();
11582
+ if (existsSync5(p)) {
11583
+ const v = readFileSync6(p, "utf8").trim();
11555
11584
  if (SHA.test(v)) return v;
11556
11585
  }
11557
11586
  } catch {
@@ -11594,7 +11623,7 @@ var init_session = __esm({
11594
11623
 
11595
11624
  // src/effect.ts
11596
11625
  import { createHash as createHash4 } from "node:crypto";
11597
- import { existsSync as existsSync7, lstatSync as lstatSync4, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync8, renameSync as renameSync2, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "node:fs";
11626
+ import { existsSync as existsSync6, lstatSync as lstatSync4, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync7, renameSync as renameSync2, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "node:fs";
11598
11627
  import { join as join11 } from "node:path";
11599
11628
  function ptreeFrom(value) {
11600
11629
  if (!isRecord(value)) return null;
@@ -11686,9 +11715,9 @@ function driftBetween(expected, current) {
11686
11715
  return { changed, deleted, added };
11687
11716
  }
11688
11717
  function loadTree(p) {
11689
- if (!p || !existsSync7(p)) return null;
11718
+ if (!p || !existsSync6(p)) return null;
11690
11719
  try {
11691
- return ptreeFrom(JSON.parse(readFileSync8(p, "utf8")));
11720
+ return ptreeFrom(JSON.parse(readFileSync7(p, "utf8")));
11692
11721
  } catch {
11693
11722
  return null;
11694
11723
  }
@@ -11741,7 +11770,7 @@ var init_effect = __esm({
11741
11770
  });
11742
11771
 
11743
11772
  // src/cli/watch.ts
11744
- import { appendFileSync as appendFileSync3, lstatSync as lstatSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync9, readdirSync as readdirSync2, watch, writeFileSync as writeFileSync4 } from "node:fs";
11773
+ import { appendFileSync as appendFileSync3, lstatSync as lstatSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync8, readdirSync as readdirSync2, watch, writeFileSync as writeFileSync4 } from "node:fs";
11745
11774
  import { join as join12 } from "node:path";
11746
11775
  import { createHash as createHash5 } from "node:crypto";
11747
11776
  function snap(abs) {
@@ -11760,7 +11789,7 @@ function watcherHealthPath(log) {
11760
11789
  }
11761
11790
  function readWatcherHealth(log) {
11762
11791
  try {
11763
- const value = JSON.parse(readFileSync9(watcherHealthPath(log), "utf8"));
11792
+ const value = JSON.parse(readFileSync8(watcherHealthPath(log), "utf8"));
11764
11793
  if (!isRecord(value)) return null;
11765
11794
  const { state, backend, pid, started_at } = value;
11766
11795
  if (value.version !== 1 || typeof pid !== "number" || typeof started_at !== "string") return null;
@@ -12288,7 +12317,7 @@ var init_fs_events = __esm({
12288
12317
 
12289
12318
  // src/cli/audit.ts
12290
12319
  import { createHash as createHash6, randomBytes } from "node:crypto";
12291
- import { appendFileSync as appendFileSync4, closeSync as closeSync4, existsSync as existsSync8, mkdirSync as mkdirSync5, openSync as openSync4, readSync as readSync3 } from "node:fs";
12320
+ import { appendFileSync as appendFileSync4, closeSync as closeSync4, existsSync as existsSync7, mkdirSync as mkdirSync5, openSync as openSync4, readSync as readSync3 } from "node:fs";
12292
12321
  import { dirname as dirname4, isAbsolute as isAbsolute8, join as join13, resolve as resolve4 } from "node:path";
12293
12322
  function sessionHash(sessionId) {
12294
12323
  if (!sessionId) return void 0;
@@ -12547,7 +12576,7 @@ function runStats(opts = {}) {
12547
12576
  const file = opts.file ? isAbsolute8(opts.file) ? opts.file : resolve4(cwd, opts.file) : configured && configured !== "auto" ? isAbsolute8(configured) ? configured : resolve4(cwd, configured) : defaultAuditPath(cwd);
12548
12577
  const cutoff = opts.since ? parseSince(opts.since) : null;
12549
12578
  if (!file) throw new Error("stats needs --file outside a Git repository");
12550
- if (!existsSync8(file)) {
12579
+ if (!existsSync7(file)) {
12551
12580
  if (explicitFile) throw new Error(`audit file not found: ${file}`);
12552
12581
  const empty2 = summarizeAudit([]);
12553
12582
  process.stdout.write(opts.json ? JSON.stringify(empty2) + "\n" : renderAuditStats(empty2));
@@ -12586,14 +12615,14 @@ var init_audit = __esm({
12586
12615
  });
12587
12616
 
12588
12617
  // src/cli/hook.ts
12589
- import { readFileSync as readFileSync10, appendFileSync as appendFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync9 } from "node:fs";
12618
+ import { readFileSync as readFileSync9, appendFileSync as appendFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync8 } from "node:fs";
12590
12619
  import { createHash as createHash7 } from "node:crypto";
12591
12620
  function setSnapshotCache(cache3) {
12592
12621
  snapshotCache = cache3;
12593
12622
  }
12594
12623
  function readStdin() {
12595
12624
  try {
12596
- return readFileSync10(0, "utf8");
12625
+ return readFileSync9(0, "utf8");
12597
12626
  } catch (e) {
12598
12627
  throw new HookInputError(`cannot read the hook payload on stdin: ${e instanceof Error ? e.message : String(e)}`);
12599
12628
  }
@@ -12883,10 +12912,10 @@ function turnTransientBlocks(cwd, sessionId, policy, changes) {
12883
12912
  recordObserverHealth(cwd, sessionId, telemetry.state, telemetry.reason);
12884
12913
  }
12885
12914
  const cp = cursorPath(cwd, sessionId);
12886
- if (!existsSync9(log)) return none;
12915
+ if (!existsSync8(log)) return none;
12887
12916
  let offset = 0;
12888
- if (cp && existsSync9(cp)) {
12889
- const n2 = Number(readFileSync10(cp, "utf8"));
12917
+ if (cp && existsSync8(cp)) {
12918
+ const n2 = Number(readFileSync9(cp, "utf8"));
12890
12919
  offset = Number.isFinite(n2) ? n2 : 0;
12891
12920
  }
12892
12921
  const drained = drainEvents(log, offset);
@@ -13160,13 +13189,13 @@ var init_ptree_cache = __esm({
13160
13189
  });
13161
13190
 
13162
13191
  // src/cli/hook-service.ts
13163
- import { chmodSync, existsSync as existsSync10, lstatSync as lstatSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync11, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "node:fs";
13192
+ import { chmodSync, existsSync as existsSync9, lstatSync as lstatSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync10, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "node:fs";
13164
13193
  import { createConnection as createConnection2, createServer } from "node:net";
13165
13194
  import { realpathSync as realpathSync4 } from "node:fs";
13166
13195
  import { sep } from "node:path";
13167
13196
  function readServiceState(paths) {
13168
13197
  try {
13169
- const v = JSON.parse(readFileSync11(paths.state, "utf8"));
13198
+ const v = JSON.parse(readFileSync10(paths.state, "utf8"));
13170
13199
  if (!isRecord(v)) return null;
13171
13200
  const { pid, version, root, started_at } = v;
13172
13201
  if (typeof pid !== "number" || typeof version !== "string" || typeof root !== "string" || typeof started_at !== "string") return null;
@@ -13248,7 +13277,7 @@ function removeQuietly(p) {
13248
13277
  }
13249
13278
  }
13250
13279
  function ensurePrivateDir(dir, uid) {
13251
- if (!existsSync10(dir)) mkdirSync6(dir, { recursive: true, mode: 448 });
13280
+ if (!existsSync9(dir)) mkdirSync6(dir, { recursive: true, mode: 448 });
13252
13281
  const st = lstatSync7(dir);
13253
13282
  if (!st.isDirectory()) throw new Error(`${dir} is not a directory`);
13254
13283
  if (st.uid !== uid) throw new Error(`${dir} is owned by uid ${st.uid}, not ${uid}; refusing to serve from it`);
@@ -13445,7 +13474,7 @@ async function stopHookService(paths, expectedRoot) {
13445
13474
  process.kill(st.pid, "SIGTERM");
13446
13475
  } catch {
13447
13476
  }
13448
- const gone = () => !pidAlive2(st.pid) || !existsSync10(paths.socket);
13477
+ const gone = () => !pidAlive2(st.pid) || !existsSync9(paths.socket);
13449
13478
  const until = Date.now() + STOP_WAIT_MS;
13450
13479
  while (!gone() && Date.now() < until) sleepSync(50);
13451
13480
  if (!gone()) throw new Error(`hook service pid ${st.pid} did not exit within ${STOP_WAIT_MS / 1e3}s`);
@@ -13727,7 +13756,7 @@ var init_safe_write = __esm({
13727
13756
  // src/cli/init.ts
13728
13757
  import { execFileSync as execFileSync6 } from "node:child_process";
13729
13758
  import { createHash as createHash8 } from "node:crypto";
13730
- import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync12, realpathSync as realpathSync5 } from "node:fs";
13759
+ import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync11, realpathSync as realpathSync5 } from "node:fs";
13731
13760
  import { basename as basename3, dirname as dirname5, isAbsolute as isAbsolute9, join as join15, relative as relative4, resolve as resolve6 } from "node:path";
13732
13761
  function gitConfig(cwd, key2) {
13733
13762
  try {
@@ -13821,34 +13850,34 @@ function verifierCandidates(cwd) {
13821
13850
  if (!candidates.includes(command)) candidates.push(command);
13822
13851
  };
13823
13852
  try {
13824
- const pkg = JSON.parse(readFileSync12(join15(cwd, "package.json"), "utf8"));
13853
+ const pkg = JSON.parse(readFileSync11(join15(cwd, "package.json"), "utf8"));
13825
13854
  const test = isRecord(pkg) && isRecord(pkg.scripts) ? pkg.scripts.test : void 0;
13826
13855
  if (typeof test === "string" && test.trim() !== "" && !/error:\s*no test specified/i.test(test)) add("npm test");
13827
13856
  } catch {
13828
13857
  }
13829
13858
  try {
13830
- const pyproject = readFileSync12(join15(cwd, "pyproject.toml"), "utf8");
13859
+ const pyproject = readFileSync11(join15(cwd, "pyproject.toml"), "utf8");
13831
13860
  if (/^\s*\[tool\.pytest(?:\.ini_options)?\]\s*$/m.test(pyproject)) add("pytest");
13832
13861
  } catch {
13833
13862
  }
13834
- if (existsSync11(join15(cwd, "pytest.ini"))) add("pytest");
13863
+ if (existsSync10(join15(cwd, "pytest.ini"))) add("pytest");
13835
13864
  try {
13836
- const setupCfg = readFileSync12(join15(cwd, "setup.cfg"), "utf8");
13865
+ const setupCfg = readFileSync11(join15(cwd, "setup.cfg"), "utf8");
13837
13866
  if (/^\s*\[tool:pytest\]\s*$/m.test(setupCfg)) add("pytest");
13838
13867
  } catch {
13839
13868
  }
13840
13869
  try {
13841
- const tox = readFileSync12(join15(cwd, "tox.ini"), "utf8");
13870
+ const tox = readFileSync11(join15(cwd, "tox.ini"), "utf8");
13842
13871
  if (/^\s*\[tox\]\s*$/m.test(tox)) add("tox");
13843
13872
  } catch {
13844
13873
  }
13845
13874
  try {
13846
- const cargo = readFileSync12(join15(cwd, "Cargo.toml"), "utf8");
13875
+ const cargo = readFileSync11(join15(cwd, "Cargo.toml"), "utf8");
13847
13876
  if (/^\s*\[(?:package|workspace)\]\s*(?:#.*)?$/m.test(cargo)) add("cargo test");
13848
13877
  } catch {
13849
13878
  }
13850
13879
  try {
13851
- const goMod = readFileSync12(join15(cwd, "go.mod"), "utf8");
13880
+ const goMod = readFileSync11(join15(cwd, "go.mod"), "utf8");
13852
13881
  if (/^\s*module\s+\S+\s*(?:\/\/.*)?$/m.test(goMod)) add("go test ./...");
13853
13882
  } catch {
13854
13883
  }
@@ -13891,7 +13920,7 @@ function planPolicy(cwd) {
13891
13920
  const path = join15(cwd, POLICY_FILE);
13892
13921
  const refused = refuseNonRegular(path);
13893
13922
  if (refused) return { item: "policy", path: POLICY_FILE, status: "error", detail: refused };
13894
- if (existsSync11(path)) {
13923
+ if (existsSync10(path)) {
13895
13924
  try {
13896
13925
  loadPolicy(cwd);
13897
13926
  } catch (e) {
@@ -13910,7 +13939,7 @@ function planPolicy(cwd) {
13910
13939
  function planGitignore(cwd) {
13911
13940
  const rel = ".gitignore";
13912
13941
  const path = join15(cwd, rel);
13913
- if (!existsSync11(join15(cwd, "node_modules"))) return null;
13942
+ if (!existsSync10(join15(cwd, "node_modules"))) return null;
13914
13943
  let existing = "";
13915
13944
  let kind;
13916
13945
  try {
@@ -13920,7 +13949,7 @@ function planGitignore(cwd) {
13920
13949
  }
13921
13950
  const refused = refuseNonRegular(path);
13922
13951
  if (refused) return { item: "gitignore", path: rel, status: "error", detail: refused };
13923
- if (kind === "file") existing = readFileSync12(path, "utf8");
13952
+ if (kind === "file") existing = readFileSync11(path, "utf8");
13924
13953
  try {
13925
13954
  const tracked = execFileSync6("git", ["ls-files", "--cached", "--", "node_modules"], {
13926
13955
  cwd,
@@ -13975,10 +14004,10 @@ function planClaudeHooks(cwd) {
13975
14004
  const refused = refuseNonRegular(path);
13976
14005
  if (refused) return { item: "agent", path: rel, status: "error", detail: refused };
13977
14006
  let settings = {};
13978
- if (existsSync11(path)) {
14007
+ if (existsSync10(path)) {
13979
14008
  let parsed;
13980
14009
  try {
13981
- parsed = JSON.parse(readFileSync12(path, "utf8"));
14010
+ parsed = JSON.parse(readFileSync11(path, "utf8"));
13982
14011
  } catch {
13983
14012
  return {
13984
14013
  item: "agent",
@@ -14027,7 +14056,7 @@ function planClaudeHooks(cwd) {
14027
14056
  return {
14028
14057
  item: "agent",
14029
14058
  path: rel,
14030
- status: existsSync11(path) ? "update" : "create",
14059
+ status: existsSync10(path) ? "update" : "create",
14031
14060
  detail: [
14032
14061
  needPre && "wire PreToolUse deny",
14033
14062
  needStop && "wire Stop sweep",
@@ -14071,7 +14100,7 @@ function hooksDir(cwd) {
14071
14100
  if (out3) return resolve6(cwd, out3);
14072
14101
  } catch {
14073
14102
  }
14074
- return existsSync11(join15(cwd, ".git")) ? join15(cwd, ".git", "hooks") : null;
14103
+ return existsSync10(join15(cwd, ".git")) ? join15(cwd, ".git", "hooks") : null;
14075
14104
  }
14076
14105
  function display(cwd, path) {
14077
14106
  const rel = relative4(cwd, path);
@@ -14098,11 +14127,11 @@ function planPreCommit(cwd) {
14098
14127
  const skip = (detail) => ({ item: "pre-commit", path: "(none)", status: "skip", detail });
14099
14128
  let path;
14100
14129
  let note;
14101
- if (existsSync11(join15(cwd, ".husky"))) {
14130
+ if (existsSync10(join15(cwd, ".husky"))) {
14102
14131
  path = join15(cwd, ".husky", "pre-commit");
14103
14132
  note = "husky";
14104
14133
  } else {
14105
- const managed = MANAGED_HOOKS.find((m) => m.configs.some((c) => existsSync11(join15(cwd, c))));
14134
+ const managed = MANAGED_HOOKS.find((m) => m.configs.some((c) => existsSync10(join15(cwd, c))));
14106
14135
  if (managed) {
14107
14136
  return skip(`${managed.name} manages this repository's hooks and regenerates them on install, so a line in the hooks directory would not last \u2014 ${managed.hint}`);
14108
14137
  }
@@ -14120,7 +14149,7 @@ function planPreCommit(cwd) {
14120
14149
  const rel = display(cwd, path);
14121
14150
  const refused = refuseNonRegular(path);
14122
14151
  if (refused) return { item: "pre-commit", path: rel, status: "error", detail: refused };
14123
- const existing = existsSync11(path) ? readFileSync12(path, "utf8") : null;
14152
+ const existing = existsSync10(path) ? readFileSync11(path, "utf8") : null;
14124
14153
  const hookMode = () => existingMode(path, 493) | 73;
14125
14154
  if (existing === null) {
14126
14155
  return {
@@ -14211,7 +14240,7 @@ function planCodeowners(cwd) {
14211
14240
  const path = join15(cwd, existingRel ?? rel);
14212
14241
  const refused = refuseNonRegular(path);
14213
14242
  if (refused) return { item: "codeowners", path: existingRel ?? rel, status: "error", detail: refused };
14214
- const existing = existingRel ? readFileSync12(path, "utf8") : null;
14243
+ const existing = existingRel ? readFileSync11(path, "utf8") : null;
14215
14244
  const missing = CODEOWNERS_PATHS.filter((p) => existing === null || !coveredBy(existing, p));
14216
14245
  if (existingRel !== void 0 && missing.length === 0) {
14217
14246
  return { item: "codeowners", path: existingRel, status: "ok", detail: "gate paths already have code owners" };
@@ -14251,7 +14280,7 @@ function planWorkflow(cwd, force) {
14251
14280
  };
14252
14281
  const refused = refuseNonRegular(path);
14253
14282
  if (refused) return { item: "ci", path: rel, status: "error", detail: refused };
14254
- if (!existsSync11(path)) {
14283
+ if (!existsSync10(path)) {
14255
14284
  return {
14256
14285
  item: "ci",
14257
14286
  path: rel,
@@ -14262,7 +14291,7 @@ function planWorkflow(cwd, force) {
14262
14291
  }
14263
14292
  let src;
14264
14293
  try {
14265
- src = readFileSync12(path, "utf8");
14294
+ src = readFileSync11(path, "utf8");
14266
14295
  } catch {
14267
14296
  return { item: "ci", path: rel, status: "error", detail: "exists but cannot be read \u2014 refusing to overwrite" };
14268
14297
  }
@@ -14609,10 +14638,10 @@ var init_fingerprint = __esm({
14609
14638
  // src/dependency-env.ts
14610
14639
  import { createHash as createHash10 } from "node:crypto";
14611
14640
  import {
14612
- existsSync as existsSync12,
14641
+ existsSync as existsSync11,
14613
14642
  lstatSync as lstatSync9,
14614
14643
  readdirSync as readdirSync3,
14615
- readFileSync as readFileSync13,
14644
+ readFileSync as readFileSync12,
14616
14645
  readlinkSync as readlinkSync2,
14617
14646
  realpathSync as realpathSync6,
14618
14647
  statSync as statSync3
@@ -14670,7 +14699,7 @@ function fingerprintRoot(root, cwd, h) {
14670
14699
  st = statSync3(path);
14671
14700
  if (!st.isFile()) throw new Error("not a regular file");
14672
14701
  h.update(label + "\0external-file\0" + path + "\0" + String(st.mode) + "\0");
14673
- h.update(readFileSync13(path));
14702
+ h.update(readFileSync12(path));
14674
14703
  h.update("\0");
14675
14704
  } catch (e) {
14676
14705
  throw new Unattestable(
@@ -14704,7 +14733,7 @@ function fingerprintRoot(root, cwd, h) {
14704
14733
  h.update(rel + "\0" + ident.kind + "\0" + String(ident.mode) + "\0");
14705
14734
  if (ident.kind === "file") {
14706
14735
  try {
14707
- h.update(readFileSync13(path));
14736
+ h.update(readFileSync12(path));
14708
14737
  h.update("\0");
14709
14738
  } catch (e) {
14710
14739
  throw new Unattestable(
@@ -14763,7 +14792,7 @@ function snapshot(roots, probes, cwd) {
14763
14792
  const h = createHash10("sha256");
14764
14793
  for (const probe of probes) {
14765
14794
  h.update(
14766
- "probe\0" + probe.kind + "\0" + probe.path + "\0" + (existsSync12(probe.path) ? "present" : "absent") + "\0"
14795
+ "probe\0" + probe.kind + "\0" + probe.path + "\0" + (existsSync11(probe.path) ? "present" : "absent") + "\0"
14767
14796
  );
14768
14797
  }
14769
14798
  for (const root of roots) fingerprintRoot(root, cwd, h);
@@ -14822,7 +14851,7 @@ function resolveExecutable(token, cwd, env) {
14822
14851
  if (token.includes("/") || token.includes("\\")) {
14823
14852
  const path = isAbsolute10(token) ? token : resolve7(cwd, token);
14824
14853
  try {
14825
- return existsSync12(path) ? realpathSync6(path) : null;
14854
+ return existsSync11(path) ? realpathSync6(path) : null;
14826
14855
  } catch {
14827
14856
  return null;
14828
14857
  }
@@ -14830,7 +14859,7 @@ function resolveExecutable(token, cwd, env) {
14830
14859
  for (const entry of (env.PATH ?? "").split(delimiter).filter(Boolean)) {
14831
14860
  const candidate = resolve7(entry, token);
14832
14861
  try {
14833
- if (existsSync12(candidate) && statSync3(candidate).isFile()) return realpathSync6(candidate);
14862
+ if (existsSync11(candidate) && statSync3(candidate).isFile()) return realpathSync6(candidate);
14834
14863
  } catch {
14835
14864
  }
14836
14865
  }
@@ -14853,7 +14882,7 @@ function inferredVenvFromToken(token, cwd) {
14853
14882
  const parentName = basename4(parent).toLowerCase();
14854
14883
  if (parentName !== "bin" && parentName !== "scripts") return null;
14855
14884
  const root = dirname6(parent);
14856
- return existsSync12(join16(root, "pyvenv.cfg")) ? root : null;
14885
+ return existsSync11(join16(root, "pyvenv.cfg")) ? root : null;
14857
14886
  }
14858
14887
  function unsupportedEcosystem(command, token) {
14859
14888
  const name = token ? basename4(token).toLowerCase() : "";
@@ -14869,13 +14898,13 @@ function inferredVenv(executable) {
14869
14898
  const parentName = basename4(parent).toLowerCase();
14870
14899
  if (parentName !== "bin" && parentName !== "scripts") return null;
14871
14900
  const root = dirname6(parent);
14872
- return existsSync12(join16(root, "pyvenv.cfg")) ? root : null;
14901
+ return existsSync11(join16(root, "pyvenv.cfg")) ? root : null;
14873
14902
  }
14874
14903
  function venvProblem(root) {
14875
14904
  const cfg = join16(root, "pyvenv.cfg");
14876
- if (!existsSync12(cfg)) return "VIRTUAL_ENV " + JSON.stringify(root) + " has no pyvenv.cfg";
14905
+ if (!existsSync11(cfg)) return "VIRTUAL_ENV " + JSON.stringify(root) + " has no pyvenv.cfg";
14877
14906
  try {
14878
- const body = readFileSync13(cfg, "utf8");
14907
+ const body = readFileSync12(cfg, "utf8");
14879
14908
  if (/^\s*include-system-site-packages\s*=\s*true\s*$/im.test(body)) {
14880
14909
  return "Python environment " + JSON.stringify(root) + " enables system site-packages; those external package roots are not bounded";
14881
14910
  }
@@ -14900,7 +14929,7 @@ function discoverDependencyEnvironment(cwdInput, command, env = process.env) {
14900
14929
  };
14901
14930
  try {
14902
14931
  const nodeModules = join16(cwd, "node_modules");
14903
- if (existsSync12(nodeModules)) addRoot("node_modules", nodeModules);
14932
+ if (existsSync11(nodeModules)) addRoot("node_modules", nodeModules);
14904
14933
  else probes.push({ kind: "node_modules", path: nodeModules });
14905
14934
  const token = firstExecutable(command);
14906
14935
  const executable = resolveExecutable(token, cwd, env);
@@ -14912,7 +14941,7 @@ function discoverDependencyEnvironment(cwdInput, command, env = process.env) {
14912
14941
  venv = resolve7(env.VIRTUAL_ENV);
14913
14942
  } else {
14914
14943
  venv = inferredVenv(executable) ?? inferredVenvFromToken(token, cwd);
14915
- if (!venv && /(?:^|\s)uv\s+run(?:\s|$)/.test(command) && existsSync12(join16(cwd, ".venv", "pyvenv.cfg"))) {
14944
+ if (!venv && /(?:^|\s)uv\s+run(?:\s|$)/.test(command) && existsSync11(join16(cwd, ".venv", "pyvenv.cfg"))) {
14916
14945
  venv = join16(cwd, ".venv");
14917
14946
  }
14918
14947
  }
@@ -15391,7 +15420,7 @@ if (child) {
15391
15420
  // src/verifier-backend.ts
15392
15421
  import { createHash as createHash11, randomUUID } from "node:crypto";
15393
15422
  import { spawnSync as spawnSync2 } from "node:child_process";
15394
- import { accessSync as accessSync2, constants as constants4, readFileSync as readFileSync14, realpathSync as realpathSync7, statSync as statSync4 } from "node:fs";
15423
+ import { accessSync as accessSync2, constants as constants4, readFileSync as readFileSync13, realpathSync as realpathSync7, statSync as statSync4 } from "node:fs";
15395
15424
  import { delimiter as delimiter2, dirname as dirname7, join as join18, resolve as resolve8 } from "node:path";
15396
15425
  function identity() {
15397
15426
  const uid = typeof process.getuid === "function" ? process.getuid() : null;
@@ -15436,7 +15465,7 @@ function findExecutable(name) {
15436
15465
  return null;
15437
15466
  }
15438
15467
  function fileSha256(path) {
15439
- return createHash11("sha256").update(readFileSync14(path)).digest("hex");
15468
+ return createHash11("sha256").update(readFileSync13(path)).digest("hex");
15440
15469
  }
15441
15470
  function engineClientEnv() {
15442
15471
  return {
@@ -15870,7 +15899,7 @@ var init_verifier_backend = __esm({
15870
15899
  // src/verification-state.ts
15871
15900
  import { execFileSync as execFileSync8 } from "node:child_process";
15872
15901
  import { createHash as createHash12 } from "node:crypto";
15873
- import { mkdirSync as mkdirSync8, readFileSync as readFileSync15, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
15902
+ import { mkdirSync as mkdirSync8, readFileSync as readFileSync14, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
15874
15903
  import { dirname as dirname8, join as join19 } from "node:path";
15875
15904
  function git2(args, cwd) {
15876
15905
  return execFileSync8("git", args, {
@@ -15929,7 +15958,7 @@ function interventionWiring(cwd) {
15929
15958
  return sources.map(([source, path]) => {
15930
15959
  let parsed;
15931
15960
  try {
15932
- parsed = JSON.parse(readFileSync15(path, "utf8"));
15961
+ parsed = JSON.parse(readFileSync14(path, "utf8"));
15933
15962
  } catch {
15934
15963
  return { source, present: false };
15935
15964
  }
@@ -16082,7 +16111,7 @@ function readVerificationRecord(cwd) {
16082
16111
  if (!path) return null;
16083
16112
  let raw;
16084
16113
  try {
16085
- raw = readFileSync15(path, "utf8");
16114
+ raw = readFileSync14(path, "utf8");
16086
16115
  } catch {
16087
16116
  return null;
16088
16117
  }
@@ -16131,7 +16160,7 @@ function readVerifyingMarker(cwd) {
16131
16160
  if (!path) return null;
16132
16161
  let parsed;
16133
16162
  try {
16134
- parsed = JSON.parse(readFileSync15(path, "utf8"));
16163
+ parsed = JSON.parse(readFileSync14(path, "utf8"));
16135
16164
  } catch {
16136
16165
  return null;
16137
16166
  }
@@ -16495,7 +16524,7 @@ var init_verification_receipt = __esm({
16495
16524
  // src/cli/verify.ts
16496
16525
  import { execFileSync as execFileSync9, spawnSync as spawnSync3 } from "node:child_process";
16497
16526
  import { createHash as createHash14 } from "node:crypto";
16498
- import { chmodSync as chmodSync2, cpSync, lstatSync as lstatSync10, mkdirSync as mkdirSync10, mkdtempSync as mkdtempSync3, readFileSync as readFileSync16, readlinkSync as readlinkSync3, rmSync as rmSync9, symlinkSync, writeFileSync as writeFileSync11, existsSync as existsSync13 } from "node:fs";
16527
+ import { chmodSync as chmodSync2, cpSync, lstatSync as lstatSync10, mkdirSync as mkdirSync10, mkdtempSync as mkdtempSync3, readFileSync as readFileSync15, readlinkSync as readlinkSync3, rmSync as rmSync9, symlinkSync, writeFileSync as writeFileSync11, existsSync as existsSync12 } from "node:fs";
16499
16528
  import { tmpdir as tmpdir4 } from "node:os";
16500
16529
  import { dirname as dirname10, isAbsolute as isAbsolute11, join as join21, relative as relative6, resolve as resolve9, sep as sep4 } from "node:path";
16501
16530
  function oracleAssuranceReport() {
@@ -16691,7 +16720,7 @@ function probeFilesystemCaseSensitivity(dir) {
16691
16720
  const lower = join21(dir, ".tw-case-probe-a");
16692
16721
  try {
16693
16722
  writeFileSync11(upper, "", { flag: "wx" });
16694
- return !existsSync13(lower);
16723
+ return !existsSync12(lower);
16695
16724
  } finally {
16696
16725
  rmSync9(upper, { force: true });
16697
16726
  }
@@ -16755,7 +16784,7 @@ function overlayDigest(dest, paths) {
16755
16784
  try {
16756
16785
  const st = lstatSync10(p);
16757
16786
  h.update(String(st.mode));
16758
- h.update(st.isSymbolicLink() ? readlinkSync3(p) : readFileSync16(p));
16787
+ h.update(st.isSymbolicLink() ? readlinkSync3(p) : readFileSync15(p));
16759
16788
  } catch {
16760
16789
  h.update("<gone>");
16761
16790
  }
@@ -17350,8 +17379,13 @@ function runVerifyImpl(opts) {
17350
17379
  } catch {
17351
17380
  }
17352
17381
  }
17353
- const signedOff = verdict2 === "MASKED_FAILURE" ? oobToken("verify", oobFromEnv(), oobHeadFromEnv()) : null;
17382
+ const oob = oobFromEnv();
17383
+ const oobHead = oobHeadFromEnv();
17384
+ const signedOff = verdict2 === "MASKED_FAILURE" ? oobToken("verify", oob, oobHead) : null;
17354
17385
  if (signedOff) code2 = 0;
17386
+ const headProblem = verdict2 === "MASKED_FAILURE" && oob.length > 0 ? oobHeadProblem(oobHead) : null;
17387
+ if (headProblem) process.stderr.write(`tamperward: ${headProblem}
17388
+ `);
17355
17389
  if (opts.json) {
17356
17390
  out3(
17357
17391
  JSON.stringify({
@@ -17564,7 +17598,7 @@ var init_verify = __esm({
17564
17598
  // src/cli/run.ts
17565
17599
  import { execFileSync as execFileSync10, spawn, spawnSync as spawnSync4 } from "node:child_process";
17566
17600
  import { randomBytes as randomBytes3 } from "node:crypto";
17567
- import { accessSync as accessSync3, constants as fsConstants, mkdtempSync as mkdtempSync4, readdirSync as readdirSync4, readFileSync as readFileSync17, readlinkSync as readlinkSync4, realpathSync as realpathSync8, rmSync as rmSync10, statSync as statSync5, writeFileSync as writeFileSync12 } from "node:fs";
17601
+ import { accessSync as accessSync3, constants as fsConstants, mkdtempSync as mkdtempSync4, readdirSync as readdirSync4, readFileSync as readFileSync16, readlinkSync as readlinkSync4, realpathSync as realpathSync8, rmSync as rmSync10, statSync as statSync5, writeFileSync as writeFileSync12 } from "node:fs";
17568
17602
  import { tmpdir as tmpdir5 } from "node:os";
17569
17603
  import { dirname as dirname11, join as join22, resolve as resolve10 } from "node:path";
17570
17604
  function authoritativeRunLifecyclePlatform(platform = process.platform) {
@@ -17718,7 +17752,7 @@ function runAgentSupervised(argv, cwd, budgetSecs, linuxPythonCandidates, lifecy
17718
17752
  }
17719
17753
  let state = {};
17720
17754
  try {
17721
- state = JSON.parse(readFileSync17(resultFile, "utf8"));
17755
+ state = JSON.parse(readFileSync16(resultFile, "utf8"));
17722
17756
  } catch {
17723
17757
  return {
17724
17758
  exit: 1,
@@ -17762,7 +17796,7 @@ function observerExited(pid) {
17762
17796
  if (!pidAlive4(pid)) return true;
17763
17797
  if (process.platform !== "linux") return false;
17764
17798
  try {
17765
- const stat = readFileSync17(`/proc/${pid}/stat`, "utf8");
17799
+ const stat = readFileSync16(`/proc/${pid}/stat`, "utf8");
17766
17800
  const state = stat.slice(stat.lastIndexOf(")") + 2).split(" ")[0];
17767
17801
  return state === "Z" || state === "X";
17768
17802
  } catch (e) {
@@ -17884,7 +17918,7 @@ function git4(args, cwd) {
17884
17918
  }
17885
17919
  function nowTicks() {
17886
17920
  try {
17887
- return Math.floor(parseFloat(readFileSync17("/proc/uptime", "utf8").split(" ")[0]) * 100);
17921
+ return Math.floor(parseFloat(readFileSync16("/proc/uptime", "utf8").split(" ")[0]) * 100);
17888
17922
  } catch {
17889
17923
  return Number.POSITIVE_INFINITY;
17890
17924
  }
@@ -17907,7 +17941,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
17907
17941
  const n2 = Number(pid);
17908
17942
  if (n2 === process.pid) continue;
17909
17943
  try {
17910
- const stat = readFileSync17(`/proc/${pid}/stat`, "utf8");
17944
+ const stat = readFileSync16(`/proc/${pid}/stat`, "utf8");
17911
17945
  const startTicks = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[19]);
17912
17946
  if (!Number.isFinite(startTicks) || startTicks < spawnedAfterTicks) continue;
17913
17947
  } catch {
@@ -18480,7 +18514,7 @@ raise SystemExit(0)
18480
18514
 
18481
18515
  // src/cli/doctor.ts
18482
18516
  import { execFileSync as execFileSync11 } from "node:child_process";
18483
- import { existsSync as existsSync14, readFileSync as readFileSync18, readdirSync as readdirSync5 } from "node:fs";
18517
+ import { existsSync as existsSync13, readFileSync as readFileSync17, readdirSync as readdirSync5 } from "node:fs";
18484
18518
  import { join as join23, resolve as resolve11 } from "node:path";
18485
18519
  function lifecyclePlatformCheck(platform = process.platform, linuxPython = platform === "linux" ? trustedLinuxPython() : null) {
18486
18520
  if (platform === "linux") {
@@ -18536,10 +18570,10 @@ function wiringState(status, authority) {
18536
18570
  return authority ? "BROKEN" : "WARN";
18537
18571
  }
18538
18572
  function pinsInFile(path) {
18539
- if (!existsSync14(path)) return [];
18573
+ if (!existsSync13(path)) return [];
18540
18574
  let src = "";
18541
18575
  try {
18542
- src = readFileSync18(path, "utf8");
18576
+ src = readFileSync17(path, "utf8");
18543
18577
  } catch {
18544
18578
  return [];
18545
18579
  }
@@ -18560,13 +18594,13 @@ function workflowPermissionCheck(cwd, workflowRels = [".github/workflows/tamperw
18560
18594
  const warnings = [];
18561
18595
  for (const rel of workflowRels) {
18562
18596
  const path = resolve11(cwd, rel);
18563
- if (!existsSync14(path)) {
18597
+ if (!existsSync13(path)) {
18564
18598
  broken.push(`${rel} is missing`);
18565
18599
  continue;
18566
18600
  }
18567
18601
  let doc;
18568
18602
  try {
18569
- doc = yaml.parse(readFileSync18(path, "utf8"));
18603
+ doc = yaml.parse(readFileSync17(path, "utf8"));
18570
18604
  } catch (e) {
18571
18605
  broken.push(`${rel} is not valid YAML (${e instanceof Error ? e.message : String(e)})`);
18572
18606
  continue;
@@ -18965,7 +18999,7 @@ function diagnose(opts = {}) {
18965
18999
  } else {
18966
19000
  const workflowDirRel = ".github/workflows";
18967
19001
  const workflowDir = resolve11(cwd, workflowDirRel);
18968
- if (!existsSync14(workflowDir)) {
19002
+ if (!existsSync13(workflowDir)) {
18969
19003
  return fail("ci-verifier", `${workflowDirRel}: workflow directory does not exist`);
18970
19004
  }
18971
19005
  let entries;
@@ -18985,12 +19019,12 @@ function diagnose(opts = {}) {
18985
19019
  const authorityWorkflows = [];
18986
19020
  for (const workflowRel of workflowRels) {
18987
19021
  const workflowPath = resolve11(cwd, workflowRel);
18988
- if (!existsSync14(workflowPath)) {
19022
+ if (!existsSync13(workflowPath)) {
18989
19023
  return fail("ci-verifier", `${workflowRel}: workflow does not exist`);
18990
19024
  }
18991
19025
  let doc;
18992
19026
  try {
18993
- doc = yaml.parse(readFileSync18(workflowPath, "utf8"));
19027
+ doc = yaml.parse(readFileSync17(workflowPath, "utf8"));
18994
19028
  } catch (e) {
18995
19029
  return fail(
18996
19030
  "ci-verifier",
@@ -19301,9 +19335,9 @@ var init_status2 = __esm({
19301
19335
  // src/cli/trace-verify.ts
19302
19336
  import { execFileSync as execFileSync12, spawnSync as spawnSync5 } from "node:child_process";
19303
19337
  import {
19304
- existsSync as existsSync15,
19338
+ existsSync as existsSync14,
19305
19339
  mkdtempSync as mkdtempSync5,
19306
- readFileSync as readFileSync19,
19340
+ readFileSync as readFileSync18,
19307
19341
  readdirSync as readdirSync6,
19308
19342
  realpathSync as realpathSync9,
19309
19343
  rmSync as rmSync11,
@@ -19560,7 +19594,7 @@ function materializeBase(base, cwd, dest) {
19560
19594
  }
19561
19595
  const hostModules = join24(cwd, "node_modules");
19562
19596
  const tracedModules = join24(dest, "node_modules");
19563
- if (existsSync15(hostModules) && !existsSync15(tracedModules)) {
19597
+ if (existsSync14(hostModules) && !existsSync14(tracedModules)) {
19564
19598
  symlinkSync2(resolve12(hostModules), tracedModules, "dir");
19565
19599
  }
19566
19600
  }
@@ -19617,7 +19651,7 @@ function straceOnce(root, command, budget) {
19617
19651
  }
19618
19652
  );
19619
19653
  const logs = traced.error ? [] : traceFiles(prefix);
19620
- const accesses = logs.flatMap((path) => parseStraceFileAccess(readFileSync19(path, "utf8"), TRACE_ROOT));
19654
+ const accesses = logs.flatMap((path) => parseStraceFileAccess(readFileSync18(path, "utf8"), TRACE_ROOT));
19621
19655
  const raw = {
19622
19656
  spawnError: traced.error ? traced.error.message : null,
19623
19657
  status: traced.status,
@@ -19872,7 +19906,7 @@ var init_immutable = __esm({
19872
19906
  });
19873
19907
 
19874
19908
  // src/runtimes.ts
19875
- import { existsSync as existsSync16, readFileSync as readFileSync20 } from "node:fs";
19909
+ import { existsSync as existsSync15, readFileSync as readFileSync19 } from "node:fs";
19876
19910
  import { join as join25 } from "node:path";
19877
19911
  function isRecord6(x) {
19878
19912
  return typeof x === "object" && x !== null && !Array.isArray(x);
@@ -19902,8 +19936,8 @@ function claudeSettingsBeyondTamperward(cwd) {
19902
19936
  const path = join25(cwd, ".claude", "settings.json");
19903
19937
  let raw;
19904
19938
  try {
19905
- if (!existsSync16(path)) return false;
19906
- raw = readFileSync20(path, "utf8");
19939
+ if (!existsSync15(path)) return false;
19940
+ raw = readFileSync19(path, "utf8");
19907
19941
  } catch {
19908
19942
  return false;
19909
19943
  }
@@ -19929,7 +19963,7 @@ function detectRuntimes(cwd) {
19929
19963
  for (const rt of KNOWN_RUNTIMES) {
19930
19964
  const marker = rt.markers.find((m) => {
19931
19965
  try {
19932
- return existsSync16(join25(cwd, m));
19966
+ return existsSync15(join25(cwd, m));
19933
19967
  } catch {
19934
19968
  return false;
19935
19969
  }
@@ -20954,7 +20988,7 @@ var init_adapter3 = __esm({
20954
20988
 
20955
20989
  // src/adapters/copilot-sdk/changes.ts
20956
20990
  import { execFileSync as execFileSync13 } from "node:child_process";
20957
- import { mkdtempSync as mkdtempSync6, writeFileSync as writeFileSync13, readFileSync as readFileSync21, rmSync as rmSync12, existsSync as existsSync17, realpathSync as realpathSync10 } from "node:fs";
20991
+ import { mkdtempSync as mkdtempSync6, writeFileSync as writeFileSync13, readFileSync as readFileSync20, rmSync as rmSync12, existsSync as existsSync16, realpathSync as realpathSync10 } from "node:fs";
20958
20992
  import { tmpdir as tmpdir7 } from "node:os";
20959
20993
  import { basename as basename5, dirname as dirname12, isAbsolute as isAbsolute16, relative as relative11, resolve as resolve15, join as join26 } from "node:path";
20960
20994
  function asStr4(v) {
@@ -20973,7 +21007,7 @@ function canonicalContainedTarget(abs, root) {
20973
21007
  }
20974
21008
  let dir = abs;
20975
21009
  const tail = [];
20976
- while (!existsSync17(dir)) {
21010
+ while (!existsSync16(dir)) {
20977
21011
  const parent = dirname12(dir);
20978
21012
  if (parent === dir) break;
20979
21013
  tail.unshift(basename5(dir));
@@ -21048,7 +21082,7 @@ function reconstructAfterViaGit(rawDiff, display2, before, isCreate) {
21048
21082
  if (statPath !== TARGET) throw new Error(`write diff reconstructs a different file (${statPath}) than the bound target`);
21049
21083
  git8(["--check"]);
21050
21084
  git8([]);
21051
- return op === "delete" ? null : readFileSync21(targetPath, "utf8");
21085
+ return op === "delete" ? null : readFileSync20(targetPath, "utf8");
21052
21086
  } catch (e) {
21053
21087
  throw new Error(`could not reconstruct the write to ${display2} from its diff via git apply: ${e instanceof Error ? e.message : String(e)}`);
21054
21088
  } finally {
@@ -21801,7 +21835,7 @@ var init_runtime_qualification = __esm({
21801
21835
 
21802
21836
  // src/cli/runtime.ts
21803
21837
  import { execFileSync as execFileSync14 } from "node:child_process";
21804
- import { existsSync as existsSync18, mkdirSync as mkdirSync11, readFileSync as readFileSync22, writeFileSync as writeFileSync14 } from "node:fs";
21838
+ import { existsSync as existsSync17, mkdirSync as mkdirSync11, readFileSync as readFileSync21, writeFileSync as writeFileSync14 } from "node:fs";
21805
21839
  import { join as join27, dirname as dirname13 } from "node:path";
21806
21840
  function storePath(cwd) {
21807
21841
  const dir = gitDir(cwd);
@@ -21809,9 +21843,9 @@ function storePath(cwd) {
21809
21843
  }
21810
21844
  function readStore(cwd) {
21811
21845
  const path = storePath(cwd);
21812
- if (!path || !existsSync18(path)) return null;
21846
+ if (!path || !existsSync17(path)) return null;
21813
21847
  try {
21814
- const parsed = JSON.parse(readFileSync22(path, "utf8"));
21848
+ const parsed = JSON.parse(readFileSync21(path, "utf8"));
21815
21849
  if (parsed && typeof parsed === "object" && parsed.records && typeof parsed.records === "object") return parsed;
21816
21850
  } catch {
21817
21851
  return null;
@@ -21980,9 +22014,9 @@ function resolveHookConfigHash(canonicalId, cwd) {
21980
22014
  "github-copilot-cli": join27(cwd, ".github", "hooks", "tamperward.json")
21981
22015
  };
21982
22016
  const file = files[canonicalId];
21983
- if (!file || !existsSync18(file)) return null;
22017
+ if (!file || !existsSync17(file)) return null;
21984
22018
  try {
21985
- const raw = readFileSync22(file, "utf8");
22019
+ const raw = readFileSync21(file, "utf8");
21986
22020
  let hooks = raw;
21987
22021
  try {
21988
22022
  const parsed = JSON.parse(raw);
@@ -22261,7 +22295,7 @@ var init_runtime = __esm({
22261
22295
 
22262
22296
  // src/cli/onboard.ts
22263
22297
  import { execFileSync as execFileSync15 } from "node:child_process";
22264
- import { lstatSync as lstatSync11, mkdtempSync as mkdtempSync7, readFileSync as readFileSync23, realpathSync as realpathSync11, rmSync as rmSync13 } from "node:fs";
22298
+ import { lstatSync as lstatSync11, mkdtempSync as mkdtempSync7, readFileSync as readFileSync22, realpathSync as realpathSync11, rmSync as rmSync13 } from "node:fs";
22265
22299
  import { tmpdir as tmpdir8 } from "node:os";
22266
22300
  import { join as join28, relative as relative12, resolve as resolve16, sep as sep6 } from "node:path";
22267
22301
  import { createInterface } from "node:readline";
@@ -22334,7 +22368,7 @@ function writeVerifyCommand(cwd, command) {
22334
22368
  return `${POLICY_FILE} is not a regular file; refusing to follow or replace a symlink or special file`;
22335
22369
  }
22336
22370
  const mode = existingMode(path, 420);
22337
- const original = kind === "file" ? readFileSync23(path, "utf8") : null;
22371
+ const original = kind === "file" ? readFileSync22(path, "utf8") : null;
22338
22372
  let next;
22339
22373
  try {
22340
22374
  const doc = parseDocument(original && original.trim() ? original : "version: 1\n");
@@ -22989,7 +23023,7 @@ var init_capture = __esm({
22989
23023
 
22990
23024
  // src/research/manifest.ts
22991
23025
  import { createHash as createHash16 } from "node:crypto";
22992
- import { readFileSync as readFileSync24 } from "node:fs";
23026
+ import { readFileSync as readFileSync23 } from "node:fs";
22993
23027
  import { dirname as dirname14, resolve as resolve18 } from "node:path";
22994
23028
  function taskFrom(raw, index, manifestDir) {
22995
23029
  const where = `task #${index + 1}`;
@@ -23023,7 +23057,7 @@ function readManifest(path) {
23023
23057
  const abs = resolve18(path);
23024
23058
  let bytes;
23025
23059
  try {
23026
- bytes = readFileSync24(abs);
23060
+ bytes = readFileSync23(abs);
23027
23061
  } catch (e) {
23028
23062
  throw new ResearchError(`cannot read task manifest ${abs}: ${e instanceof Error ? e.message : String(e)}`);
23029
23063
  }
@@ -23442,7 +23476,7 @@ var init_record = __esm({
23442
23476
  // src/research/run.ts
23443
23477
  import { randomUUID as randomUUID2 } from "node:crypto";
23444
23478
  import { execFileSync as execFileSync16 } from "node:child_process";
23445
- import { closeSync as closeSync5, existsSync as existsSync19, linkSync, mkdirSync as mkdirSync12, openSync as openSync5, readFileSync as readFileSync25, renameSync as renameSync4, rmSync as rmSync14, unlinkSync, writeFileSync as writeFileSync15, writeSync } from "node:fs";
23479
+ import { closeSync as closeSync5, existsSync as existsSync18, linkSync, mkdirSync as mkdirSync12, openSync as openSync5, readFileSync as readFileSync24, renameSync as renameSync4, rmSync as rmSync14, unlinkSync, writeFileSync as writeFileSync15, writeSync } from "node:fs";
23446
23480
  import { hostname } from "node:os";
23447
23481
  import { join as join29, resolve as resolve19 } from "node:path";
23448
23482
  function git7(args, cwd) {
@@ -23456,7 +23490,7 @@ function researchLockPath(ledger) {
23456
23490
  }
23457
23491
  function readLockOwner(path) {
23458
23492
  try {
23459
- const parsed = JSON.parse(readFileSync25(path, "utf8"));
23493
+ const parsed = JSON.parse(readFileSync24(path, "utf8"));
23460
23494
  if (!isRecord(parsed) || typeof parsed.pid !== "number" || !Number.isInteger(parsed.pid) || parsed.pid <= 0) return null;
23461
23495
  if (typeof parsed.host !== "string" || typeof parsed.started_at !== "string" || typeof parsed.token !== "string") return null;
23462
23496
  return { pid: parsed.pid, host: parsed.host, started_at: parsed.started_at, token: parsed.token };
@@ -23479,7 +23513,7 @@ function lockHolder(path) {
23479
23513
  function acquireResearchLock(ledger, breakStaleLock = false) {
23480
23514
  mkdirSync12(ledger, { recursive: true });
23481
23515
  const path = researchLockPath(ledger);
23482
- if (breakStaleLock && existsSync19(path)) {
23516
+ if (breakStaleLock && existsSync18(path)) {
23483
23517
  const owner2 = readLockOwner(path);
23484
23518
  if (!owner2) {
23485
23519
  throw new ResearchError(`research output ${ledger} has an unreadable lock at ${path}; verify no run is active, then remove it deliberately`);
@@ -23541,7 +23575,7 @@ function acquireResearchLock(ledger, breakStaleLock = false) {
23541
23575
  if (released) return;
23542
23576
  released = true;
23543
23577
  try {
23544
- if (readFileSync25(path, "utf8") === text) unlinkSync(path);
23578
+ if (readFileSync24(path, "utf8") === text) unlinkSync(path);
23545
23579
  } catch {
23546
23580
  }
23547
23581
  }
@@ -23550,7 +23584,7 @@ function acquireResearchLock(ledger, breakStaleLock = false) {
23550
23584
  function resumableRecord(path, expected) {
23551
23585
  let raw;
23552
23586
  try {
23553
- raw = JSON.parse(readFileSync25(path, "utf8"));
23587
+ raw = JSON.parse(readFileSync24(path, "utf8"));
23554
23588
  } catch (e) {
23555
23589
  throw new ResearchError(`ledger record ${path} is malformed (${errorMessage(e).split("\n")[0]}); remove it to regenerate the pair, or use a new --out`);
23556
23590
  }
@@ -23852,7 +23886,7 @@ function runResearch(opts) {
23852
23886
  try {
23853
23887
  for (let pair = 1; pair <= pairs; pair++) {
23854
23888
  const path = pairRecordPath(ledger, task.id, pair);
23855
- if (!existsSync19(path)) continue;
23889
+ if (!existsSync18(path)) continue;
23856
23890
  const existing = resumableRecord(path, {
23857
23891
  task: task.id,
23858
23892
  pair,
@@ -23972,7 +24006,7 @@ var init_run2 = __esm({
23972
24006
  });
23973
24007
 
23974
24008
  // src/research/ledger-files.ts
23975
- import { closeSync as closeSync6, constants as constants5, fstatSync as fstatSync3, lstatSync as lstatSync12, openSync as openSync6, readFileSync as readFileSync26, readdirSync as readdirSync7, realpathSync as realpathSync12 } from "node:fs";
24009
+ import { closeSync as closeSync6, constants as constants5, fstatSync as fstatSync3, lstatSync as lstatSync12, openSync as openSync6, readFileSync as readFileSync25, readdirSync as readdirSync7, realpathSync as realpathSync12 } from "node:fs";
23976
24010
  import { join as join30, resolve as resolve20 } from "node:path";
23977
24011
  function lstatOrNull(path) {
23978
24012
  try {
@@ -24015,7 +24049,7 @@ function readRegularFile(path, realPairsDir, name) {
24015
24049
  if (!opened.isFile() || !sameInode || opened.nlink !== 1) {
24016
24050
  throw new ResearchError(`ledger record ${path} changed between listing and reading; refusing to read it`);
24017
24051
  }
24018
- return readFileSync26(fd);
24052
+ return readFileSync25(fd);
24019
24053
  } finally {
24020
24054
  closeSync6(fd);
24021
24055
  }
@@ -24188,7 +24222,7 @@ var init_summarize = __esm({
24188
24222
 
24189
24223
  // src/research/init.ts
24190
24224
  import { createHash as createHash17 } from "node:crypto";
24191
- import { existsSync as existsSync20, mkdirSync as mkdirSync13, readFileSync as readFileSync27, writeFileSync as writeFileSync16 } from "node:fs";
24225
+ import { existsSync as existsSync19, mkdirSync as mkdirSync13, readFileSync as readFileSync26, writeFileSync as writeFileSync16 } from "node:fs";
24192
24226
  import { basename as basename6, dirname as dirname15, resolve as resolve21 } from "node:path";
24193
24227
  function idFrom(repo) {
24194
24228
  const clean = repo.replace(/[\\/]$/, "").split(/[\\/]/).pop() || "task";
@@ -24200,7 +24234,7 @@ function createResearchManifest(opts) {
24200
24234
  if (!opts.prompt) throw new ResearchError("research init requires --prompt");
24201
24235
  if (!opts.verifyCommand) throw new ResearchError("research init requires --verify-command");
24202
24236
  const out3 = resolve21(opts.out);
24203
- if (existsSync20(out3)) throw new ResearchError(`research init refuses to overwrite ${out3}; choose a new path`);
24237
+ if (existsSync19(out3)) throw new ResearchError(`research init refuses to overwrite ${out3}; choose a new path`);
24204
24238
  const id = opts.id ?? idFrom(opts.repo);
24205
24239
  if (!/^[A-Za-z0-9._-]+$/.test(id)) throw new ResearchError(`research init id must match [A-Za-z0-9._-] (got "${id}")`);
24206
24240
  if (opts.verifyBudget !== void 0 && (!Number.isFinite(opts.verifyBudget) || opts.verifyBudget <= 0)) {
@@ -24240,7 +24274,7 @@ function runResearchInit(opts) {
24240
24274
  }
24241
24275
  }
24242
24276
  function requireHash(path) {
24243
- return createHash17("sha256").update(readFileSync27(path)).digest("hex");
24277
+ return createHash17("sha256").update(readFileSync26(path)).digest("hex");
24244
24278
  }
24245
24279
  var init_init2 = __esm({
24246
24280
  "src/research/init.ts"() {
@@ -24308,7 +24342,7 @@ var init_report2 = __esm({
24308
24342
  import { createHash as createHash18 } from "node:crypto";
24309
24343
  import { isDeepStrictEqual } from "node:util";
24310
24344
  import { gzipSync, gunzipSync } from "node:zlib";
24311
- import { mkdirSync as mkdirSync14, readFileSync as readFileSync28, writeFileSync as writeFileSync17 } from "node:fs";
24345
+ import { mkdirSync as mkdirSync14, readFileSync as readFileSync27, writeFileSync as writeFileSync17 } from "node:fs";
24312
24346
  import { dirname as dirname16, resolve as resolve22 } from "node:path";
24313
24347
  function octal(value, width) {
24314
24348
  return value.toString(8).padStart(width - 1, "0") + "\0";
@@ -24414,7 +24448,7 @@ function jsonBytes(value) {
24414
24448
  function validateResearchBundle(path) {
24415
24449
  let files;
24416
24450
  try {
24417
- files = parseTar(gunzipSync(readFileSync28(resolve22(path))));
24451
+ files = parseTar(gunzipSync(readFileSync27(resolve22(path))));
24418
24452
  } catch (e) {
24419
24453
  throw new ResearchError(`cannot read research bundle ${path}: ${e instanceof Error ? e.message : String(e)}`);
24420
24454
  }
@@ -24484,7 +24518,7 @@ function createResearchBundle(opts) {
24484
24518
  const { records, entries } = packagedRecords(ledger);
24485
24519
  const summary = summarizeRecords(records);
24486
24520
  if (opts.manifest) {
24487
- const manifest = readFileSync28(resolve22(opts.manifest));
24521
+ const manifest = readFileSync27(resolve22(opts.manifest));
24488
24522
  const sha3 = createHash18("sha256").update(manifest).digest("hex");
24489
24523
  if (sha3 !== summary.manifest_sha256) throw new ResearchError(`manifest sha256 ${sha3} does not match ledger ${summary.manifest_sha256}`);
24490
24524
  entries.push({ name: "manifest.json", bytes: manifest });
@@ -24655,7 +24689,7 @@ var init_research = __esm({
24655
24689
  });
24656
24690
 
24657
24691
  // src/cli/receipt.ts
24658
- import { appendFileSync as appendFileSync6, readFileSync as readFileSync29, writeFileSync as writeFileSync18 } from "node:fs";
24692
+ import { appendFileSync as appendFileSync6, readFileSync as readFileSync28, writeFileSync as writeFileSync18 } from "node:fs";
24659
24693
  function parseReceipt(args) {
24660
24694
  const [sub, ...rest] = args;
24661
24695
  const exportOpts = {};
@@ -24782,7 +24816,7 @@ function ciAdjudicate(cwd, opts) {
24782
24816
  if (opts.ciResult !== void 0) {
24783
24817
  let doc;
24784
24818
  try {
24785
- doc = JSON.parse(readFileSync29(opts.ciResult, "utf8"));
24819
+ doc = JSON.parse(readFileSync28(opts.ciResult, "utf8"));
24786
24820
  } catch (e) {
24787
24821
  return {
24788
24822
  ci: { verdict: "CANNOT_VERIFY", binding, binding_error: `unreadable CI verify result: ${e instanceof Error ? e.message : String(e)}` },
@@ -24793,7 +24827,12 @@ function ciAdjudicate(cwd, opts) {
24793
24827
  if (check2.reason !== void 0) {
24794
24828
  return { ci: { verdict: "CANNOT_VERIFY", binding, binding_error: check2.reason }, exit: 2 };
24795
24829
  }
24796
- const signedOff = check2.verdict === "MASKED_FAILURE" && oobToken("verify", oobFromEnv(), oobHeadFromEnv()) !== null;
24830
+ const oob = oobFromEnv();
24831
+ const oobHead = oobHeadFromEnv();
24832
+ const signedOff = check2.verdict === "MASKED_FAILURE" && oobToken("verify", oob, oobHead) !== null;
24833
+ const headProblem = check2.verdict === "MASKED_FAILURE" && oob.length > 0 ? oobHeadProblem(oobHead) : null;
24834
+ if (headProblem) process.stderr.write(`tamperward: ${headProblem}
24835
+ `);
24797
24836
  return {
24798
24837
  ci: {
24799
24838
  verdict: check2.verdict,
@@ -24923,7 +24962,7 @@ function loadClaimedReceipt(path) {
24923
24962
  }
24924
24963
  let raw;
24925
24964
  try {
24926
- raw = readFileSync29(path, "utf8");
24965
+ raw = readFileSync28(path, "utf8");
24927
24966
  } catch (e) {
24928
24967
  return { disposition: "MALFORMED", detail: `receipt file could not be read (${e instanceof Error ? e.message : String(e)})` };
24929
24968
  }
@@ -25784,7 +25823,7 @@ var init_main = __esm({
25784
25823
  // src/cli/index.ts
25785
25824
  init_hook_client();
25786
25825
  init_exit();
25787
- import { readFileSync as readFileSync30 } from "node:fs";
25826
+ import { readFileSync as readFileSync29 } from "node:fs";
25788
25827
  function loadMain() {
25789
25828
  return Promise.resolve().then(() => (init_main(), main_exports));
25790
25829
  }
@@ -25797,7 +25836,7 @@ async function launch(argv) {
25797
25836
  if (kind && hookServiceEnabled()) {
25798
25837
  let raw = null;
25799
25838
  try {
25800
- raw = readFileSync30(0, "utf8");
25839
+ raw = readFileSync29(0, "utf8");
25801
25840
  } catch {
25802
25841
  raw = null;
25803
25842
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tamperward",
3
- "version": "2.39.7",
3
+ "version": "2.39.9",
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",