tamperward 2.39.11 → 2.39.13

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 +207 -122
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -788,9 +788,54 @@ function readStateFile(abs) {
788
788
  if (e.kind === "absent") return null;
789
789
  if (e.kind === "file") return textOf2(e) ?? "";
790
790
  throw new StateFileError(
791
- `${abs} ${notARegularFile(e)}; the gate reads its session state only as a regular file \u2014 remove what stands there so the state can be re-established`
791
+ `${abs} ${notARegularFile(e)}; the gate reads its state files only as regular files \u2014 remove what stands there so the state can be re-established`
792
792
  );
793
793
  }
794
+ function openRegular(abs, refused) {
795
+ let st;
796
+ try {
797
+ st = lstatSync2(abs);
798
+ } catch (e) {
799
+ const code2 = errCode(e);
800
+ if (code2 === "ENOENT" || code2 === "ENOTDIR") return null;
801
+ throw new StateFileError(`${abs} cannot be read (${errText(e)})${refused}`);
802
+ }
803
+ if (!st.isFile()) throw new StateFileError(`${abs} ${notARegularFile(classify(abs, st))}${refused}`);
804
+ let fd;
805
+ try {
806
+ fd = openSync(abs, O_READ);
807
+ } catch (e) {
808
+ const code2 = errCode(e);
809
+ if (code2 === "ENOENT") return null;
810
+ throw new StateFileError(
811
+ `${abs} ${code2 === "ELOOP" || code2 === "ENXIO" ? `changed shape while it was being opened (${errText(e)})` : `cannot be read (${errText(e)})`}${refused}`
812
+ );
813
+ }
814
+ let now;
815
+ try {
816
+ now = fstatSync(fd);
817
+ } catch (e) {
818
+ closeSync(fd);
819
+ throw new StateFileError(`${abs} cannot be read (${errText(e)})${refused}`);
820
+ }
821
+ if (now.isFile()) return fd;
822
+ closeSync(fd);
823
+ throw new StateFileError(`${abs} is a ${now.isDirectory() ? "directory" : irregularName(now)}${refused}`);
824
+ }
825
+ function stateDirectory(gitDir3) {
826
+ const dir = join3(gitDir3, "tamperward");
827
+ const st = lstatSync2(dir, { throwIfNoEntry: false });
828
+ if (st === void 0 || st.isDirectory()) return dir;
829
+ if (st.isSymbolicLink()) {
830
+ let target = "";
831
+ try {
832
+ target = readlinkSync(dir);
833
+ } catch {
834
+ }
835
+ throw new StateFileError(`${dir} is a symbolic link to ${escapeControl(target)}${STATE_DIR_REFUSED}`);
836
+ }
837
+ throw new StateFileError(`${dir} is a ${st.isFile() ? "regular file" : irregularName(st)}${STATE_DIR_REFUSED}`);
838
+ }
794
839
  function multiplyLinked(st) {
795
840
  return st.nlink === 1 ? null : `has ${st.nlink} names \u2014 a hard link is the same file under another name, the session marker's or the policy's, say`;
796
841
  }
@@ -861,7 +906,7 @@ function unjudgeableProtected(cwd, policy, changes) {
861
906
  }
862
907
  return out3;
863
908
  }
864
- var READ_CAP, NO_META, O_READ, StateFileError, O_APPEND_REGULAR, APPEND_REFUSED;
909
+ var READ_CAP, NO_META, O_READ, StateFileError, STATE_DIR_REFUSED, O_APPEND_REGULAR, APPEND_REFUSED;
865
910
  var init_disk = __esm({
866
911
  "src/disk.ts"() {
867
912
  "use strict";
@@ -872,6 +917,7 @@ var init_disk = __esm({
872
917
  O_READ = constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0);
873
918
  StateFileError = class extends Error {
874
919
  };
920
+ STATE_DIR_REFUSED = "; the gate keeps its state only in a directory of its own under the git directory \u2014 replace what stands there with a directory, or remove it";
875
921
  O_APPEND_REGULAR = constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0);
876
922
  APPEND_REFUSED = "; the gate appends its records only to a regular file with one name \u2014 remove what stands there so the record can be kept";
877
923
  }
@@ -11614,7 +11660,13 @@ function writeBaseline(p, sha3) {
11614
11660
  function baselinePath(cwd, sessionId) {
11615
11661
  const gd = gitDir(cwd);
11616
11662
  if (!gd) return null;
11617
- return join10(gd, "tamperward", `session-${sessionId.replace(UNSAFE, "")}`);
11663
+ const name = `session-${sessionId.replace(UNSAFE, "")}`;
11664
+ try {
11665
+ return join10(stateDirectory(gd), name);
11666
+ } catch (e) {
11667
+ if (e instanceof StateFileError) throw new BaselineWriteError(join10(gd, "tamperward", name), e);
11668
+ throw e;
11669
+ }
11618
11670
  }
11619
11671
  function turnBaseline(cwd, sessionId) {
11620
11672
  if (!sessionId) return null;
@@ -11681,7 +11733,7 @@ function statePath(cwd, sessionId, kind) {
11681
11733
  if (!sessionId) return null;
11682
11734
  const gd = gitDir2(cwd);
11683
11735
  if (!gd) return null;
11684
- const dir = join11(gd, "tamperward");
11736
+ const dir = stateDirectory(gd);
11685
11737
  return join11(dir, `${kind}-${sessionId.replace(UNSAFE2, "")}.json`);
11686
11738
  }
11687
11739
  function ptreePath(cwd, sessionId) {
@@ -11867,7 +11919,7 @@ function snap(abs) {
11867
11919
  }
11868
11920
  function defaultEventLog(cwd) {
11869
11921
  if (process.env.TAMPERWARD_FSEVENTS) return process.env.TAMPERWARD_FSEVENTS;
11870
- return join12(gitDir(cwd) ?? join12(cwd, ".git"), "tamperward", "fsevents.jsonl");
11922
+ return join12(stateDirectory(gitDir(cwd) ?? join12(cwd, ".git")), "fsevents.jsonl");
11871
11923
  }
11872
11924
  function watcherHealthPath(log) {
11873
11925
  return `${log}.health.json`;
@@ -12411,7 +12463,7 @@ var init_fs_events = __esm({
12411
12463
 
12412
12464
  // src/cli/audit.ts
12413
12465
  import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
12414
- import { closeSync as closeSync4, existsSync as existsSync5, mkdirSync as mkdirSync5, openSync as openSync4, readSync as readSync3 } from "node:fs";
12466
+ import { closeSync as closeSync4, mkdirSync as mkdirSync5, readSync as readSync3 } from "node:fs";
12415
12467
  import { dirname as dirname4, isAbsolute as isAbsolute8, join as join13, resolve as resolve4 } from "node:path";
12416
12468
  function sessionHash(sessionId) {
12417
12469
  if (!sessionId) return void 0;
@@ -12425,19 +12477,19 @@ function configuredAuditPath(cwd, env = process.env) {
12425
12477
  if (!value) return null;
12426
12478
  if (value === "auto") {
12427
12479
  const ctx = repoContext(cwd);
12428
- return ctx ? join13(ctx.gitDir, "tamperward", "audit.jsonl") : null;
12480
+ return ctx ? join13(stateDirectory(ctx.gitDir), "audit.jsonl") : null;
12429
12481
  }
12430
12482
  return isAbsolute8(value) ? value : resolve4(cwd, value);
12431
12483
  }
12432
12484
  function defaultAuditPath(cwd) {
12433
12485
  const ctx = repoContext(cwd);
12434
- return ctx ? join13(ctx.gitDir, "tamperward", "audit.jsonl") : null;
12486
+ return ctx ? join13(stateDirectory(ctx.gitDir), "audit.jsonl") : null;
12435
12487
  }
12436
12488
  function recordAuditFindings(findings, context) {
12437
12489
  if (findings.length === 0) return;
12438
- const path = configuredAuditPath(context.cwd);
12439
- if (!path) return;
12440
12490
  try {
12491
+ const path = configuredAuditPath(context.cwd);
12492
+ if (!path) return;
12441
12493
  mkdirSync5(dirname4(path), { recursive: true });
12442
12494
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
12443
12495
  const session = sessionHash(context.sessionId);
@@ -12531,7 +12583,8 @@ function parseAuditLine(line, lineNumber) {
12531
12583
  return parseAuditEvent(value, lineNumber);
12532
12584
  }
12533
12585
  function forEachAuditLine(file, onLine, maxLineBytes = MAX_AUDIT_LINE_BYTES) {
12534
- const fd = openSync4(file, "r");
12586
+ const fd = openRegular(file, STATS_READ_REFUSED);
12587
+ if (fd === null) return false;
12535
12588
  try {
12536
12589
  const chunk = Buffer.allocUnsafe(64 * 1024);
12537
12590
  let pending = [];
@@ -12571,6 +12624,7 @@ function forEachAuditLine(file, onLine, maxLineBytes = MAX_AUDIT_LINE_BYTES) {
12571
12624
  } finally {
12572
12625
  closeSync4(fd);
12573
12626
  }
12627
+ return true;
12574
12628
  }
12575
12629
  function parseSince(value, nowMs = Date.now()) {
12576
12630
  const relative13 = /^(\d+)(m|h|d)$/.exec(value);
@@ -12633,11 +12687,6 @@ function createAuditAggregator() {
12633
12687
  }
12634
12688
  };
12635
12689
  }
12636
- function summarizeAudit(events) {
12637
- const aggregator = createAuditAggregator();
12638
- for (const event of events) aggregator.add(event);
12639
- return aggregator.finish();
12640
- }
12641
12690
  function renderAuditStats(summary) {
12642
12691
  const out3 = [
12643
12692
  "TamperWard audit stats",
@@ -12670,23 +12719,18 @@ function runStats(opts = {}) {
12670
12719
  const file = opts.file ? isAbsolute8(opts.file) ? opts.file : resolve4(cwd, opts.file) : configured && configured !== "auto" ? isAbsolute8(configured) ? configured : resolve4(cwd, configured) : defaultAuditPath(cwd);
12671
12720
  const cutoff = opts.since ? parseSince(opts.since) : null;
12672
12721
  if (!file) throw new Error("stats needs --file outside a Git repository");
12673
- if (!existsSync5(file)) {
12674
- if (explicitFile) throw new Error(`audit file not found: ${file}`);
12675
- const empty2 = summarizeAudit([]);
12676
- process.stdout.write(opts.json ? JSON.stringify(empty2) + "\n" : renderAuditStats(empty2));
12677
- return 0;
12678
- }
12679
12722
  const aggregator = createAuditAggregator();
12680
- forEachAuditLine(file, (line, lineNumber) => {
12723
+ const read = forEachAuditLine(file, (line, lineNumber) => {
12681
12724
  const event = parseAuditLine(line, lineNumber);
12682
12725
  if (cutoff !== null && Date.parse(event.timestamp) < cutoff) return;
12683
12726
  aggregator.add(event);
12684
12727
  });
12728
+ if (!read && explicitFile) throw new Error(`audit file not found: ${file}`);
12685
12729
  const summary = aggregator.finish();
12686
12730
  process.stdout.write(opts.json ? JSON.stringify(summary) + "\n" : renderAuditStats(summary));
12687
12731
  return 0;
12688
12732
  }
12689
- var AUDIT_SCHEMA_VERSION, AUDIT_LOG_ENV, MAX_AUDIT_LINE_BYTES, ALLOWED_KEYS;
12733
+ var AUDIT_SCHEMA_VERSION, AUDIT_LOG_ENV, MAX_AUDIT_LINE_BYTES, ALLOWED_KEYS, STATS_READ_REFUSED;
12690
12734
  var init_audit = __esm({
12691
12735
  "src/cli/audit.ts"() {
12692
12736
  "use strict";
@@ -12706,11 +12750,12 @@ var init_audit = __esm({
12706
12750
  "decision",
12707
12751
  "session"
12708
12752
  ]);
12753
+ STATS_READ_REFUSED = "; stats reads the audit log only as a regular file \u2014 remove what stands there so the log can be read";
12709
12754
  }
12710
12755
  });
12711
12756
 
12712
12757
  // src/cli/hook.ts
12713
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync6 } from "node:fs";
12758
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync5 } from "node:fs";
12714
12759
  import { createHash as createHash7 } from "node:crypto";
12715
12760
  function setSnapshotCache(cache3) {
12716
12761
  snapshotCache = cache3;
@@ -13007,7 +13052,7 @@ function turnTransientBlocks(cwd, sessionId, policy, changes) {
13007
13052
  recordObserverHealth(cwd, sessionId, telemetry.state, telemetry.reason);
13008
13053
  }
13009
13054
  const cp = cursorPath(cwd, sessionId);
13010
- if (!existsSync6(log)) return none;
13055
+ if (!existsSync5(log)) return none;
13011
13056
  let offset = 0;
13012
13057
  const cursorText = cp ? readStateFile(cp) : null;
13013
13058
  if (cursorText !== null) {
@@ -13285,7 +13330,7 @@ var init_ptree_cache = __esm({
13285
13330
  });
13286
13331
 
13287
13332
  // src/cli/hook-service.ts
13288
- import { chmodSync, existsSync as existsSync7, lstatSync as lstatSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync7, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "node:fs";
13333
+ import { chmodSync, existsSync as existsSync6, lstatSync as lstatSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync7, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "node:fs";
13289
13334
  import { createConnection as createConnection2, createServer } from "node:net";
13290
13335
  import { realpathSync as realpathSync4 } from "node:fs";
13291
13336
  import { sep } from "node:path";
@@ -13373,7 +13418,7 @@ function removeQuietly(p) {
13373
13418
  }
13374
13419
  }
13375
13420
  function ensurePrivateDir(dir, uid) {
13376
- if (!existsSync7(dir)) mkdirSync6(dir, { recursive: true, mode: 448 });
13421
+ if (!existsSync6(dir)) mkdirSync6(dir, { recursive: true, mode: 448 });
13377
13422
  const st = lstatSync8(dir);
13378
13423
  if (!st.isDirectory()) throw new Error(`${dir} is not a directory`);
13379
13424
  if (st.uid !== uid) throw new Error(`${dir} is owned by uid ${st.uid}, not ${uid}; refusing to serve from it`);
@@ -13570,7 +13615,7 @@ async function stopHookService(paths, expectedRoot) {
13570
13615
  process.kill(st.pid, "SIGTERM");
13571
13616
  } catch {
13572
13617
  }
13573
- const gone = () => !pidAlive2(st.pid) || !existsSync7(paths.socket);
13618
+ const gone = () => !pidAlive2(st.pid) || !existsSync6(paths.socket);
13574
13619
  const until = Date.now() + STOP_WAIT_MS;
13575
13620
  while (!gone() && Date.now() < until) sleepSync(50);
13576
13621
  if (!gone()) throw new Error(`hook service pid ${st.pid} did not exit within ${STOP_WAIT_MS / 1e3}s`);
@@ -13807,7 +13852,7 @@ var init_verifier_limits = __esm({
13807
13852
  // src/cli/init.ts
13808
13853
  import { execFileSync as execFileSync6 } from "node:child_process";
13809
13854
  import { createHash as createHash8 } from "node:crypto";
13810
- import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync8, realpathSync as realpathSync5 } from "node:fs";
13855
+ import { existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync8, realpathSync as realpathSync5 } from "node:fs";
13811
13856
  import { basename as basename3, dirname as dirname5, isAbsolute as isAbsolute9, join as join15, relative as relative4, resolve as resolve6 } from "node:path";
13812
13857
  function gitConfig(cwd, key2) {
13813
13858
  try {
@@ -13911,7 +13956,7 @@ function verifierCandidates(cwd) {
13911
13956
  if (/^\s*\[tool\.pytest(?:\.ini_options)?\]\s*$/m.test(pyproject)) add("pytest");
13912
13957
  } catch {
13913
13958
  }
13914
- if (existsSync8(join15(cwd, "pytest.ini"))) add("pytest");
13959
+ if (existsSync7(join15(cwd, "pytest.ini"))) add("pytest");
13915
13960
  try {
13916
13961
  const setupCfg = readFileSync8(join15(cwd, "setup.cfg"), "utf8");
13917
13962
  if (/^\s*\[tool:pytest\]\s*$/m.test(setupCfg)) add("pytest");
@@ -13971,7 +14016,7 @@ function planPolicy(cwd) {
13971
14016
  const path = join15(cwd, POLICY_FILE);
13972
14017
  const refused = refuseNonRegular(path);
13973
14018
  if (refused) return { item: "policy", path: POLICY_FILE, status: "error", detail: refused };
13974
- if (existsSync8(path)) {
14019
+ if (existsSync7(path)) {
13975
14020
  try {
13976
14021
  loadPolicy(cwd);
13977
14022
  } catch (e) {
@@ -13990,7 +14035,7 @@ function planPolicy(cwd) {
13990
14035
  function planGitignore(cwd) {
13991
14036
  const rel = ".gitignore";
13992
14037
  const path = join15(cwd, rel);
13993
- if (!existsSync8(join15(cwd, "node_modules"))) return null;
14038
+ if (!existsSync7(join15(cwd, "node_modules"))) return null;
13994
14039
  let existing = "";
13995
14040
  let kind;
13996
14041
  try {
@@ -14055,7 +14100,7 @@ function planClaudeHooks(cwd) {
14055
14100
  const refused = refuseNonRegular(path);
14056
14101
  if (refused) return { item: "agent", path: rel, status: "error", detail: refused };
14057
14102
  let settings = {};
14058
- if (existsSync8(path)) {
14103
+ if (existsSync7(path)) {
14059
14104
  let parsed;
14060
14105
  try {
14061
14106
  parsed = JSON.parse(readFileSync8(path, "utf8"));
@@ -14107,7 +14152,7 @@ function planClaudeHooks(cwd) {
14107
14152
  return {
14108
14153
  item: "agent",
14109
14154
  path: rel,
14110
- status: existsSync8(path) ? "update" : "create",
14155
+ status: existsSync7(path) ? "update" : "create",
14111
14156
  detail: [
14112
14157
  needPre && "wire PreToolUse deny",
14113
14158
  needStop && "wire Stop sweep",
@@ -14151,7 +14196,7 @@ function hooksDir(cwd) {
14151
14196
  if (out3) return resolve6(cwd, out3);
14152
14197
  } catch {
14153
14198
  }
14154
- return existsSync8(join15(cwd, ".git")) ? join15(cwd, ".git", "hooks") : null;
14199
+ return existsSync7(join15(cwd, ".git")) ? join15(cwd, ".git", "hooks") : null;
14155
14200
  }
14156
14201
  function display(cwd, path) {
14157
14202
  const rel = relative4(cwd, path);
@@ -14178,11 +14223,11 @@ function planPreCommit(cwd) {
14178
14223
  const skip = (detail) => ({ item: "pre-commit", path: "(none)", status: "skip", detail });
14179
14224
  let path;
14180
14225
  let note;
14181
- if (existsSync8(join15(cwd, ".husky"))) {
14226
+ if (existsSync7(join15(cwd, ".husky"))) {
14182
14227
  path = join15(cwd, ".husky", "pre-commit");
14183
14228
  note = "husky";
14184
14229
  } else {
14185
- const managed = MANAGED_HOOKS.find((m) => m.configs.some((c) => existsSync8(join15(cwd, c))));
14230
+ const managed = MANAGED_HOOKS.find((m) => m.configs.some((c) => existsSync7(join15(cwd, c))));
14186
14231
  if (managed) {
14187
14232
  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}`);
14188
14233
  }
@@ -14200,7 +14245,7 @@ function planPreCommit(cwd) {
14200
14245
  const rel = display(cwd, path);
14201
14246
  const refused = refuseNonRegular(path);
14202
14247
  if (refused) return { item: "pre-commit", path: rel, status: "error", detail: refused };
14203
- const existing = existsSync8(path) ? readFileSync8(path, "utf8") : null;
14248
+ const existing = existsSync7(path) ? readFileSync8(path, "utf8") : null;
14204
14249
  const hookMode = () => existingMode(path, 493) | 73;
14205
14250
  if (existing === null) {
14206
14251
  return {
@@ -14331,7 +14376,7 @@ function planWorkflow(cwd, force) {
14331
14376
  };
14332
14377
  const refused = refuseNonRegular(path);
14333
14378
  if (refused) return { item: "ci", path: rel, status: "error", detail: refused };
14334
- if (!existsSync8(path)) {
14379
+ if (!existsSync7(path)) {
14335
14380
  return {
14336
14381
  item: "ci",
14337
14382
  path: rel,
@@ -14689,7 +14734,7 @@ var init_fingerprint = __esm({
14689
14734
  // src/dependency-env.ts
14690
14735
  import { createHash as createHash10 } from "node:crypto";
14691
14736
  import {
14692
- existsSync as existsSync9,
14737
+ existsSync as existsSync8,
14693
14738
  lstatSync as lstatSync9,
14694
14739
  readdirSync as readdirSync3,
14695
14740
  readFileSync as readFileSync9,
@@ -14843,7 +14888,7 @@ function snapshot(roots, probes, cwd) {
14843
14888
  const h = createHash10("sha256");
14844
14889
  for (const probe of probes) {
14845
14890
  h.update(
14846
- "probe\0" + probe.kind + "\0" + probe.path + "\0" + (existsSync9(probe.path) ? "present" : "absent") + "\0"
14891
+ "probe\0" + probe.kind + "\0" + probe.path + "\0" + (existsSync8(probe.path) ? "present" : "absent") + "\0"
14847
14892
  );
14848
14893
  }
14849
14894
  for (const root of roots) fingerprintRoot(root, cwd, h);
@@ -14902,7 +14947,7 @@ function resolveExecutable(token, cwd, env) {
14902
14947
  if (token.includes("/") || token.includes("\\")) {
14903
14948
  const path = isAbsolute10(token) ? token : resolve7(cwd, token);
14904
14949
  try {
14905
- return existsSync9(path) ? realpathSync6(path) : null;
14950
+ return existsSync8(path) ? realpathSync6(path) : null;
14906
14951
  } catch {
14907
14952
  return null;
14908
14953
  }
@@ -14910,7 +14955,7 @@ function resolveExecutable(token, cwd, env) {
14910
14955
  for (const entry of (env.PATH ?? "").split(delimiter).filter(Boolean)) {
14911
14956
  const candidate = resolve7(entry, token);
14912
14957
  try {
14913
- if (existsSync9(candidate) && statSync3(candidate).isFile()) return realpathSync6(candidate);
14958
+ if (existsSync8(candidate) && statSync3(candidate).isFile()) return realpathSync6(candidate);
14914
14959
  } catch {
14915
14960
  }
14916
14961
  }
@@ -14933,7 +14978,7 @@ function inferredVenvFromToken(token, cwd) {
14933
14978
  const parentName = basename4(parent).toLowerCase();
14934
14979
  if (parentName !== "bin" && parentName !== "scripts") return null;
14935
14980
  const root = dirname6(parent);
14936
- return existsSync9(join16(root, "pyvenv.cfg")) ? root : null;
14981
+ return existsSync8(join16(root, "pyvenv.cfg")) ? root : null;
14937
14982
  }
14938
14983
  function unsupportedEcosystem(command, token) {
14939
14984
  const name = token ? basename4(token).toLowerCase() : "";
@@ -14949,11 +14994,11 @@ function inferredVenv(executable) {
14949
14994
  const parentName = basename4(parent).toLowerCase();
14950
14995
  if (parentName !== "bin" && parentName !== "scripts") return null;
14951
14996
  const root = dirname6(parent);
14952
- return existsSync9(join16(root, "pyvenv.cfg")) ? root : null;
14997
+ return existsSync8(join16(root, "pyvenv.cfg")) ? root : null;
14953
14998
  }
14954
14999
  function venvProblem(root) {
14955
15000
  const cfg = join16(root, "pyvenv.cfg");
14956
- if (!existsSync9(cfg)) return "VIRTUAL_ENV " + JSON.stringify(root) + " has no pyvenv.cfg";
15001
+ if (!existsSync8(cfg)) return "VIRTUAL_ENV " + JSON.stringify(root) + " has no pyvenv.cfg";
14957
15002
  try {
14958
15003
  const body = readFileSync9(cfg, "utf8");
14959
15004
  if (/^\s*include-system-site-packages\s*=\s*true\s*$/im.test(body)) {
@@ -14980,7 +15025,7 @@ function discoverDependencyEnvironment(cwdInput, command, env = process.env) {
14980
15025
  };
14981
15026
  try {
14982
15027
  const nodeModules = join16(cwd, "node_modules");
14983
- if (existsSync9(nodeModules)) addRoot("node_modules", nodeModules);
15028
+ if (existsSync8(nodeModules)) addRoot("node_modules", nodeModules);
14984
15029
  else probes.push({ kind: "node_modules", path: nodeModules });
14985
15030
  const token = firstExecutable(command);
14986
15031
  const executable = resolveExecutable(token, cwd, env);
@@ -14992,7 +15037,7 @@ function discoverDependencyEnvironment(cwdInput, command, env = process.env) {
14992
15037
  venv = resolve7(env.VIRTUAL_ENV);
14993
15038
  } else {
14994
15039
  venv = inferredVenv(executable) ?? inferredVenvFromToken(token, cwd);
14995
- if (!venv && /(?:^|\s)uv\s+run(?:\s|$)/.test(command) && existsSync9(join16(cwd, ".venv", "pyvenv.cfg"))) {
15040
+ if (!venv && /(?:^|\s)uv\s+run(?:\s|$)/.test(command) && existsSync8(join16(cwd, ".venv", "pyvenv.cfg"))) {
14996
15041
  venv = join16(cwd, ".venv");
14997
15042
  }
14998
15043
  }
@@ -15950,7 +15995,7 @@ var init_verifier_backend = __esm({
15950
15995
  // src/verification-state.ts
15951
15996
  import { execFileSync as execFileSync8 } from "node:child_process";
15952
15997
  import { createHash as createHash12 } from "node:crypto";
15953
- import { mkdirSync as mkdirSync8, readFileSync as readFileSync11, rmSync as rmSync7, writeFileSync as writeFileSync8 } from "node:fs";
15998
+ import { mkdirSync as mkdirSync8, readFileSync as readFileSync11, rmSync as rmSync7 } from "node:fs";
15954
15999
  import { dirname as dirname8, join as join19 } from "node:path";
15955
16000
  function git2(args, cwd) {
15956
16001
  return execFileSync8("git", args, {
@@ -16067,7 +16112,7 @@ function computeBinding(cwd, inputs, precomputed) {
16067
16112
  }
16068
16113
  function stateDir(cwd) {
16069
16114
  const ctx = repoContext(cwd);
16070
- return ctx ? join19(ctx.gitDir, "tamperward") : null;
16115
+ return ctx ? stateDirectory(ctx.gitDir) : null;
16071
16116
  }
16072
16117
  function verificationRecordPath(cwd) {
16073
16118
  const dir = stateDir(cwd);
@@ -16094,11 +16139,17 @@ function recordVerification(cwd, inputs, treeFingerprintValue) {
16094
16139
  binding
16095
16140
  };
16096
16141
  mkdirSync8(dirname8(path), { recursive: true });
16097
- writeFileSync8(path, JSON.stringify(record) + "\n");
16142
+ atomicReplaceFile(path, JSON.stringify(record) + "\n", 438);
16098
16143
  return true;
16099
16144
  }
16100
16145
  function invalidateVerificationRecordIfCurrent(cwd) {
16101
- const record = readVerificationRecord(cwd);
16146
+ let record;
16147
+ try {
16148
+ record = readVerificationRecord(cwd);
16149
+ } catch (e) {
16150
+ if (e instanceof StateFileError) return false;
16151
+ throw e;
16152
+ }
16102
16153
  if (!record) return false;
16103
16154
  let live;
16104
16155
  try {
@@ -16160,12 +16211,8 @@ function parseVerificationRecord(value) {
16160
16211
  function readVerificationRecord(cwd) {
16161
16212
  const path = verificationRecordPath(cwd);
16162
16213
  if (!path) return null;
16163
- let raw;
16164
- try {
16165
- raw = readFileSync11(path, "utf8");
16166
- } catch {
16167
- return null;
16168
- }
16214
+ const raw = readStateFile(path);
16215
+ if (raw === null) return null;
16169
16216
  let parsed;
16170
16217
  try {
16171
16218
  parsed = JSON.parse(raw);
@@ -16183,25 +16230,25 @@ function pidAlive3(pid) {
16183
16230
  }
16184
16231
  }
16185
16232
  function beginVerifying(cwd) {
16186
- const path = verifyingMarkerPath(cwd);
16187
- if (!path) return false;
16188
16233
  try {
16234
+ const path = verifyingMarkerPath(cwd);
16235
+ if (!path) return false;
16189
16236
  const marker = {
16190
16237
  schema_version: VERIFICATION_STATE_SCHEMA_VERSION,
16191
16238
  pid: process.pid,
16192
16239
  started_at: (/* @__PURE__ */ new Date()).toISOString()
16193
16240
  };
16194
16241
  mkdirSync8(dirname8(path), { recursive: true });
16195
- writeFileSync8(path, JSON.stringify(marker) + "\n");
16242
+ atomicReplaceFile(path, JSON.stringify(marker) + "\n", 438);
16196
16243
  return true;
16197
16244
  } catch {
16198
16245
  return false;
16199
16246
  }
16200
16247
  }
16201
16248
  function endVerifying(cwd) {
16202
- const path = verifyingMarkerPath(cwd);
16203
- if (!path) return;
16204
16249
  try {
16250
+ const path = verifyingMarkerPath(cwd);
16251
+ if (!path) return;
16205
16252
  rmSync7(path, { force: true });
16206
16253
  } catch {
16207
16254
  }
@@ -16209,9 +16256,11 @@ function endVerifying(cwd) {
16209
16256
  function readVerifyingMarker(cwd) {
16210
16257
  const path = verifyingMarkerPath(cwd);
16211
16258
  if (!path) return null;
16259
+ const raw = readStateFile(path);
16260
+ if (raw === null) return null;
16212
16261
  let parsed;
16213
16262
  try {
16214
- parsed = JSON.parse(readFileSync11(path, "utf8"));
16263
+ parsed = JSON.parse(raw);
16215
16264
  } catch {
16216
16265
  return null;
16217
16266
  }
@@ -16238,10 +16287,22 @@ function environmentInputDivergences(recorded, live) {
16238
16287
  return ENVIRONMENT_INPUTS.filter((input) => recorded[input] !== live[input]);
16239
16288
  }
16240
16289
  function evaluateVerificationState(cwd) {
16241
- if (readVerifyingMarker(cwd)) {
16290
+ let marker;
16291
+ let record;
16292
+ try {
16293
+ marker = readVerifyingMarker(cwd);
16294
+ record = marker ? null : readVerificationRecord(cwd);
16295
+ } catch (e) {
16296
+ if (!(e instanceof StateFileError)) throw e;
16297
+ return {
16298
+ state: "UNVERIFIED",
16299
+ reason: "the verification store cannot be read: something other than a regular file stands at a record path",
16300
+ detail: e.message
16301
+ };
16302
+ }
16303
+ if (marker) {
16242
16304
  return { state: "VERIFYING", reason: "a verification is in progress" };
16243
16305
  }
16244
- const record = readVerificationRecord(cwd);
16245
16306
  if (!record) {
16246
16307
  return {
16247
16308
  state: "UNVERIFIED",
@@ -16296,6 +16357,8 @@ var init_verification_state = __esm({
16296
16357
  init_repo_context();
16297
16358
  init_wiring();
16298
16359
  init_dependency_env();
16360
+ init_disk();
16361
+ init_safe_write();
16299
16362
  VERIFICATION_STATE_SCHEMA_VERSION = 1;
16300
16363
  BINDING_INPUTS = [
16301
16364
  "tree",
@@ -16345,7 +16408,7 @@ var init_verification_state = __esm({
16345
16408
 
16346
16409
  // src/verification-receipt.ts
16347
16410
  import { createHash as createHash13 } from "node:crypto";
16348
- import { mkdirSync as mkdirSync9, rmSync as rmSync8, writeFileSync as writeFileSync9 } from "node:fs";
16411
+ import { mkdirSync as mkdirSync9, rmSync as rmSync8 } from "node:fs";
16349
16412
  import { dirname as dirname9, join as join20 } from "node:path";
16350
16413
  function isRecord5(v) {
16351
16414
  return typeof v === "object" && v !== null && !Array.isArray(v);
@@ -16385,23 +16448,23 @@ function receiptFromRecord(record) {
16385
16448
  }
16386
16449
  function receiptPath(cwd) {
16387
16450
  const ctx = repoContext(cwd);
16388
- return ctx ? join20(ctx.gitDir, "tamperward", "verification-receipt.json") : null;
16451
+ return ctx ? join20(stateDirectory(ctx.gitDir), "verification-receipt.json") : null;
16389
16452
  }
16390
16453
  function storeReceipt(cwd, receipt) {
16391
- const path = receiptPath(cwd);
16392
- if (!path) return false;
16393
16454
  try {
16455
+ const path = receiptPath(cwd);
16456
+ if (!path) return false;
16394
16457
  mkdirSync9(dirname9(path), { recursive: true });
16395
- writeFileSync9(path, JSON.stringify(receipt) + "\n");
16458
+ atomicReplaceFile(path, JSON.stringify(receipt) + "\n", 438);
16396
16459
  return true;
16397
16460
  } catch {
16398
16461
  return false;
16399
16462
  }
16400
16463
  }
16401
16464
  function removeStoredReceipt(cwd) {
16402
- const path = receiptPath(cwd);
16403
- if (!path) return;
16404
16465
  try {
16466
+ const path = receiptPath(cwd);
16467
+ if (!path) return;
16405
16468
  rmSync8(path, { force: true });
16406
16469
  } catch {
16407
16470
  }
@@ -16563,6 +16626,8 @@ var RECEIPT_SCHEMA_VERSION, RECEIPT_STAGE_RESULTS, RECEIPT_INTEGRITY_RESULTS;
16563
16626
  var init_verification_receipt = __esm({
16564
16627
  "src/verification-receipt.ts"() {
16565
16628
  "use strict";
16629
+ init_disk();
16630
+ init_safe_write();
16566
16631
  init_repo_context();
16567
16632
  init_verification_state();
16568
16633
  init_machine_output();
@@ -16575,7 +16640,7 @@ var init_verification_receipt = __esm({
16575
16640
  // src/cli/verify.ts
16576
16641
  import { execFileSync as execFileSync9, spawnSync as spawnSync3 } from "node:child_process";
16577
16642
  import { createHash as createHash14 } from "node:crypto";
16578
- import { chmodSync as chmodSync2, cpSync, lstatSync as lstatSync10, mkdirSync as mkdirSync10, mkdtempSync as mkdtempSync3, readFileSync as readFileSync12, readlinkSync as readlinkSync3, rmSync as rmSync9, symlinkSync, writeFileSync as writeFileSync10, existsSync as existsSync10 } from "node:fs";
16643
+ import { chmodSync as chmodSync2, cpSync, lstatSync as lstatSync10, mkdirSync as mkdirSync10, mkdtempSync as mkdtempSync3, readFileSync as readFileSync12, readlinkSync as readlinkSync3, rmSync as rmSync9, symlinkSync, writeFileSync as writeFileSync8, existsSync as existsSync9 } from "node:fs";
16579
16644
  import { tmpdir as tmpdir4 } from "node:os";
16580
16645
  import { dirname as dirname10, isAbsolute as isAbsolute11, join as join21, relative as relative6, resolve as resolve9, sep as sep4 } from "node:path";
16581
16646
  function oracleAssuranceReport() {
@@ -16770,8 +16835,8 @@ function probeFilesystemCaseSensitivity(dir) {
16770
16835
  const upper = join21(dir, ".tw-case-probe-A");
16771
16836
  const lower = join21(dir, ".tw-case-probe-a");
16772
16837
  try {
16773
- writeFileSync10(upper, "", { flag: "wx" });
16774
- return !existsSync10(lower);
16838
+ writeFileSync8(upper, "", { flag: "wx" });
16839
+ return !existsSync9(lower);
16775
16840
  } finally {
16776
16841
  rmSync9(upper, { force: true });
16777
16842
  }
@@ -16873,7 +16938,7 @@ function overlayPristine(cwd, base, dest, policy, cmd, dependencyRoot, caseSensi
16873
16938
  safeSymlink(target, out3, dest, `the base's ${e.path}`);
16874
16939
  restoredLinks.push({ path: e.path, out: out3, target });
16875
16940
  } else {
16876
- writeFileSync10(out3, content);
16941
+ writeFileSync8(out3, content);
16877
16942
  chmodSync2(out3, parseInt(e.mode.slice(-4), 8) & 511);
16878
16943
  }
16879
16944
  restored.push(e.path);
@@ -16898,7 +16963,7 @@ function suiteEnv(scratch) {
16898
16963
  }
16899
16964
  const userRc = join21(scratch, "npmrc-user");
16900
16965
  const globalRc = join21(scratch, "npmrc-global");
16901
- for (const f of [userRc, globalRc]) writeFileSync10(f, "", { mode: 292 });
16966
+ for (const f of [userRc, globalRc]) writeFileSync8(f, "", { mode: 292 });
16902
16967
  env.npm_config_userconfig = userRc;
16903
16968
  env.npm_config_globalconfig = globalRc;
16904
16969
  env.npm_config_node_options = " ";
@@ -17649,7 +17714,7 @@ var init_verify = __esm({
17649
17714
  // src/cli/run.ts
17650
17715
  import { execFileSync as execFileSync10, spawn, spawnSync as spawnSync4 } from "node:child_process";
17651
17716
  import { randomBytes as randomBytes3 } from "node:crypto";
17652
- import { accessSync as accessSync3, constants as fsConstants, mkdtempSync as mkdtempSync4, readdirSync as readdirSync4, readFileSync as readFileSync13, readlinkSync as readlinkSync4, realpathSync as realpathSync8, rmSync as rmSync10, statSync as statSync5, writeFileSync as writeFileSync11 } from "node:fs";
17717
+ import { accessSync as accessSync3, constants as fsConstants, mkdtempSync as mkdtempSync4, readdirSync as readdirSync4, readFileSync as readFileSync13, readlinkSync as readlinkSync4, realpathSync as realpathSync8, rmSync as rmSync10, statSync as statSync5, writeFileSync as writeFileSync9 } from "node:fs";
17653
17718
  import { tmpdir as tmpdir5 } from "node:os";
17654
17719
  import { dirname as dirname11, join as join22, resolve as resolve10 } from "node:path";
17655
17720
  function authoritativeRunLifecyclePlatform(platform = process.platform) {
@@ -17746,7 +17811,7 @@ function runAgentSupervised(argv, cwd, budgetSecs, linuxPythonCandidates, lifecy
17746
17811
  failure: trustedPython.reason ?? "trusted Linux python3 interpreter is unavailable"
17747
17812
  };
17748
17813
  }
17749
- writeFileSync11(agentEnvFile, JSON.stringify(process.env), { mode: 384 });
17814
+ writeFileSync9(agentEnvFile, JSON.stringify(process.env), { mode: 384 });
17750
17815
  executable = trustedPython.path;
17751
17816
  args = [
17752
17817
  "-I",
@@ -17857,8 +17922,7 @@ function observerExited(pid) {
17857
17922
  function supervisedObserverLog(cwd) {
17858
17923
  const gd = gitDir(cwd) ?? join22(cwd, ".git");
17859
17924
  return join22(
17860
- gd,
17861
- "tamperward",
17925
+ stateDirectory(gd),
17862
17926
  `run-observer-${process.pid}-${Date.now()}-${randomBytes3(4).toString("hex")}.jsonl`
17863
17927
  );
17864
17928
  }
@@ -18565,7 +18629,7 @@ raise SystemExit(0)
18565
18629
 
18566
18630
  // src/cli/doctor.ts
18567
18631
  import { execFileSync as execFileSync11 } from "node:child_process";
18568
- import { existsSync as existsSync11, readFileSync as readFileSync14, readdirSync as readdirSync5 } from "node:fs";
18632
+ import { existsSync as existsSync10, readFileSync as readFileSync14, readdirSync as readdirSync5 } from "node:fs";
18569
18633
  import { join as join23, resolve as resolve11 } from "node:path";
18570
18634
  function lifecyclePlatformCheck(platform = process.platform, linuxPython = platform === "linux" ? trustedLinuxPython() : null) {
18571
18635
  if (platform === "linux") {
@@ -18621,7 +18685,7 @@ function wiringState(status, authority) {
18621
18685
  return authority ? "BROKEN" : "WARN";
18622
18686
  }
18623
18687
  function pinsInFile(path) {
18624
- if (!existsSync11(path)) return [];
18688
+ if (!existsSync10(path)) return [];
18625
18689
  let src = "";
18626
18690
  try {
18627
18691
  src = readFileSync14(path, "utf8");
@@ -18645,7 +18709,7 @@ function workflowPermissionCheck(cwd, workflowRels = [".github/workflows/tamperw
18645
18709
  const warnings = [];
18646
18710
  for (const rel of workflowRels) {
18647
18711
  const path = resolve11(cwd, rel);
18648
- if (!existsSync11(path)) {
18712
+ if (!existsSync10(path)) {
18649
18713
  broken.push(`${rel} is missing`);
18650
18714
  continue;
18651
18715
  }
@@ -19050,7 +19114,7 @@ function diagnose(opts = {}) {
19050
19114
  } else {
19051
19115
  const workflowDirRel = ".github/workflows";
19052
19116
  const workflowDir = resolve11(cwd, workflowDirRel);
19053
- if (!existsSync11(workflowDir)) {
19117
+ if (!existsSync10(workflowDir)) {
19054
19118
  return fail("ci-verifier", `${workflowDirRel}: workflow directory does not exist`);
19055
19119
  }
19056
19120
  let entries;
@@ -19070,7 +19134,7 @@ function diagnose(opts = {}) {
19070
19134
  const authorityWorkflows = [];
19071
19135
  for (const workflowRel of workflowRels) {
19072
19136
  const workflowPath = resolve11(cwd, workflowRel);
19073
- if (!existsSync11(workflowPath)) {
19137
+ if (!existsSync10(workflowPath)) {
19074
19138
  return fail("ci-verifier", `${workflowRel}: workflow does not exist`);
19075
19139
  }
19076
19140
  let doc;
@@ -19304,7 +19368,10 @@ function renderStatus(model, colour) {
19304
19368
  if (v.state === "STALE" && v.reason) rows.push(["Reason", v.reason]);
19305
19369
  else if (v.state === "BROKEN") rows.push(["Reason", v.detail ?? v.reason ?? "authority wiring is invalid"]);
19306
19370
  else if (v.state === "VERIFYING") rows.push(["Reason", v.reason ?? "a verification is in progress"]);
19307
- else if (v.state === "UNVERIFIED") rows.push(["Next", "run `tamperward verify` to establish a verified baseline"]);
19371
+ else if (v.state === "UNVERIFIED" && v.detail) {
19372
+ rows.push(["Reason", v.detail]);
19373
+ rows.push(["Next", "remove what stands at that path, then run `tamperward verify`"]);
19374
+ } else if (v.state === "UNVERIFIED") rows.push(["Next", "run `tamperward verify` to establish a verified baseline"]);
19308
19375
  if (rows.length) {
19309
19376
  lines.push("");
19310
19377
  const width = Math.max(...rows.map(([k]) => k.length));
@@ -19386,7 +19453,7 @@ var init_status2 = __esm({
19386
19453
  // src/cli/trace-verify.ts
19387
19454
  import { execFileSync as execFileSync12, spawnSync as spawnSync5 } from "node:child_process";
19388
19455
  import {
19389
- existsSync as existsSync12,
19456
+ existsSync as existsSync11,
19390
19457
  mkdtempSync as mkdtempSync5,
19391
19458
  readFileSync as readFileSync15,
19392
19459
  readdirSync as readdirSync6,
@@ -19645,7 +19712,7 @@ function materializeBase(base, cwd, dest) {
19645
19712
  }
19646
19713
  const hostModules = join24(cwd, "node_modules");
19647
19714
  const tracedModules = join24(dest, "node_modules");
19648
- if (existsSync12(hostModules) && !existsSync12(tracedModules)) {
19715
+ if (existsSync11(hostModules) && !existsSync11(tracedModules)) {
19649
19716
  symlinkSync2(resolve12(hostModules), tracedModules, "dir");
19650
19717
  }
19651
19718
  }
@@ -19957,7 +20024,7 @@ var init_immutable = __esm({
19957
20024
  });
19958
20025
 
19959
20026
  // src/runtimes.ts
19960
- import { existsSync as existsSync13, readFileSync as readFileSync16 } from "node:fs";
20027
+ import { existsSync as existsSync12, readFileSync as readFileSync16 } from "node:fs";
19961
20028
  import { join as join25 } from "node:path";
19962
20029
  function isRecord6(x) {
19963
20030
  return typeof x === "object" && x !== null && !Array.isArray(x);
@@ -19987,7 +20054,7 @@ function claudeSettingsBeyondTamperward(cwd) {
19987
20054
  const path = join25(cwd, ".claude", "settings.json");
19988
20055
  let raw;
19989
20056
  try {
19990
- if (!existsSync13(path)) return false;
20057
+ if (!existsSync12(path)) return false;
19991
20058
  raw = readFileSync16(path, "utf8");
19992
20059
  } catch {
19993
20060
  return false;
@@ -20014,7 +20081,7 @@ function detectRuntimes(cwd) {
20014
20081
  for (const rt of KNOWN_RUNTIMES) {
20015
20082
  const marker = rt.markers.find((m) => {
20016
20083
  try {
20017
- return existsSync13(join25(cwd, m));
20084
+ return existsSync12(join25(cwd, m));
20018
20085
  } catch {
20019
20086
  return false;
20020
20087
  }
@@ -21039,7 +21106,7 @@ var init_adapter3 = __esm({
21039
21106
 
21040
21107
  // src/adapters/copilot-sdk/changes.ts
21041
21108
  import { execFileSync as execFileSync13 } from "node:child_process";
21042
- import { mkdtempSync as mkdtempSync6, writeFileSync as writeFileSync12, readFileSync as readFileSync17, rmSync as rmSync12, existsSync as existsSync14, realpathSync as realpathSync10 } from "node:fs";
21109
+ import { mkdtempSync as mkdtempSync6, writeFileSync as writeFileSync10, readFileSync as readFileSync17, rmSync as rmSync12, existsSync as existsSync13, realpathSync as realpathSync10 } from "node:fs";
21043
21110
  import { tmpdir as tmpdir7 } from "node:os";
21044
21111
  import { basename as basename5, dirname as dirname12, isAbsolute as isAbsolute16, relative as relative11, resolve as resolve15, join as join26 } from "node:path";
21045
21112
  function asStr4(v) {
@@ -21058,7 +21125,7 @@ function canonicalContainedTarget(abs, root) {
21058
21125
  }
21059
21126
  let dir = abs;
21060
21127
  const tail = [];
21061
- while (!existsSync14(dir)) {
21128
+ while (!existsSync13(dir)) {
21062
21129
  const parent = dirname12(dir);
21063
21130
  if (parent === dir) break;
21064
21131
  tail.unshift(basename5(dir));
@@ -21116,9 +21183,9 @@ function reconstructAfterViaGit(rawDiff, display2, before, isCreate) {
21116
21183
  const dir = mkdtempSync6(join26(tmpdir7(), "hf-sdk-apply-"));
21117
21184
  try {
21118
21185
  const targetPath = join26(dir, TARGET);
21119
- if (op !== "create") writeFileSync12(targetPath, before ?? "");
21186
+ if (op !== "create") writeFileSync10(targetPath, before ?? "");
21120
21187
  const patchPath = join26(dir, "change.patch");
21121
- writeFileSync12(patchPath, patch);
21188
+ writeFileSync10(patchPath, patch);
21122
21189
  const git8 = (extra) => execFileSync13("git", ["apply", ...extra, patchPath], {
21123
21190
  cwd: dir,
21124
21191
  encoding: "utf8",
@@ -21886,17 +21953,19 @@ var init_runtime_qualification = __esm({
21886
21953
 
21887
21954
  // src/cli/runtime.ts
21888
21955
  import { execFileSync as execFileSync14 } from "node:child_process";
21889
- import { existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync18, writeFileSync as writeFileSync13 } from "node:fs";
21956
+ import { existsSync as existsSync14, mkdirSync as mkdirSync11, readFileSync as readFileSync18 } from "node:fs";
21890
21957
  import { join as join27, dirname as dirname13 } from "node:path";
21891
21958
  function storePath(cwd) {
21892
21959
  const dir = gitDir(cwd);
21893
- return dir ? join27(dir, "tamperward", "runtime-qualification.json") : null;
21960
+ return dir ? join27(stateDirectory(dir), "runtime-qualification.json") : null;
21894
21961
  }
21895
21962
  function readStore(cwd) {
21896
21963
  const path = storePath(cwd);
21897
- if (!path || !existsSync15(path)) return null;
21964
+ if (!path) return null;
21965
+ const raw = readStateFile(path);
21966
+ if (raw === null) return null;
21898
21967
  try {
21899
- const parsed = JSON.parse(readFileSync18(path, "utf8"));
21968
+ const parsed = JSON.parse(raw);
21900
21969
  if (parsed && typeof parsed === "object" && parsed.records && typeof parsed.records === "object") return parsed;
21901
21970
  } catch {
21902
21971
  return null;
@@ -22027,7 +22096,7 @@ function writeRecord(cwd, id, report2) {
22027
22096
  store.updated_at = report2.timestamp;
22028
22097
  try {
22029
22098
  mkdirSync11(dirname13(path), { recursive: true });
22030
- writeFileSync13(path, JSON.stringify(store, null, 2) + "\n", { mode: 384 });
22099
+ atomicReplaceFile(path, JSON.stringify(store, null, 2) + "\n", 384);
22031
22100
  return true;
22032
22101
  } catch {
22033
22102
  return false;
@@ -22065,7 +22134,7 @@ function resolveHookConfigHash(canonicalId, cwd) {
22065
22134
  "github-copilot-cli": join27(cwd, ".github", "hooks", "tamperward.json")
22066
22135
  };
22067
22136
  const file = files[canonicalId];
22068
- if (!file || !existsSync15(file)) return null;
22137
+ if (!file || !existsSync14(file)) return null;
22069
22138
  try {
22070
22139
  const raw = readFileSync18(file, "utf8");
22071
22140
  let hooks = raw;
@@ -22207,7 +22276,13 @@ function emit2(report2, opts, cwd) {
22207
22276
  renderText3(report2, cwd);
22208
22277
  }
22209
22278
  function storedQualification(adapter, cwd, opts = {}) {
22210
- const raw = readStore(cwd)?.records[adapter.name];
22279
+ let raw;
22280
+ try {
22281
+ raw = readStore(cwd)?.records[adapter.name];
22282
+ } catch (e) {
22283
+ if (e instanceof StateFileError) return { state: "rejected", reason: e.message };
22284
+ throw e;
22285
+ }
22211
22286
  if (raw === void 0) return { state: "none" };
22212
22287
  const validated = validateStoredReport(raw);
22213
22288
  if ("reason" in validated) return { state: "rejected", reason: validated.reason };
@@ -22246,10 +22321,18 @@ function runRuntime(sub, opts) {
22246
22321
  }
22247
22322
  const { binding, assessments } = buildQualification(target, opts, cwd);
22248
22323
  const report2 = reportFrom("verify", binding, assessments);
22249
- const wrote = writeRecord(cwd, target.name, report2);
22324
+ let wrote = false;
22325
+ let refused;
22326
+ try {
22327
+ wrote = writeRecord(cwd, target.name, report2);
22328
+ } catch (e) {
22329
+ if (!(e instanceof StateFileError)) throw e;
22330
+ refused = e.message;
22331
+ }
22250
22332
  if (!wrote) {
22251
- const dest = storePath(cwd);
22252
- const reason = `could not persist the qualification to ${dest ?? "the git-local store (no repository found)"}; nothing was recorded, so \`tamperward runtime status\` will report UNQUALIFIED`;
22333
+ const dir = gitDir(cwd);
22334
+ const dest = dir ? join27(dir, "tamperward", "runtime-qualification.json") : null;
22335
+ const reason = `could not persist the qualification to ${dest ?? "the git-local store (no repository found)"}${refused ? ` (${refused})` : ""}; nothing was recorded, so \`tamperward runtime status\` will report UNQUALIFIED`;
22253
22336
  const failure = {
22254
22337
  ...report2,
22255
22338
  recorded: false,
@@ -22319,6 +22402,8 @@ var HONESTY_NOTE, CAP_ID_SET, STATE_SEVERITY, AGGREGATE_SEVERITY, RUNTIME_SUBCOM
22319
22402
  var init_runtime = __esm({
22320
22403
  "src/cli/runtime.ts"() {
22321
22404
  "use strict";
22405
+ init_disk();
22406
+ init_safe_write();
22322
22407
  init_build();
22323
22408
  init_wiring();
22324
22409
  init_machine_output();
@@ -23527,7 +23612,7 @@ var init_record = __esm({
23527
23612
  // src/research/run.ts
23528
23613
  import { randomUUID as randomUUID2 } from "node:crypto";
23529
23614
  import { execFileSync as execFileSync16 } from "node:child_process";
23530
- import { closeSync as closeSync5, existsSync as existsSync16, linkSync, mkdirSync as mkdirSync12, openSync as openSync5, readFileSync as readFileSync21, renameSync as renameSync4, rmSync as rmSync14, unlinkSync, writeFileSync as writeFileSync14, writeSync as writeSync2 } from "node:fs";
23615
+ import { closeSync as closeSync5, existsSync as existsSync15, linkSync, mkdirSync as mkdirSync12, openSync as openSync4, readFileSync as readFileSync21, renameSync as renameSync4, rmSync as rmSync14, unlinkSync, writeFileSync as writeFileSync11, writeSync as writeSync2 } from "node:fs";
23531
23616
  import { hostname } from "node:os";
23532
23617
  import { join as join29, resolve as resolve19 } from "node:path";
23533
23618
  function git7(args, cwd) {
@@ -23564,7 +23649,7 @@ function lockHolder(path) {
23564
23649
  function acquireResearchLock(ledger, breakStaleLock = false) {
23565
23650
  mkdirSync12(ledger, { recursive: true });
23566
23651
  const path = researchLockPath(ledger);
23567
- if (breakStaleLock && existsSync16(path)) {
23652
+ if (breakStaleLock && existsSync15(path)) {
23568
23653
  const owner2 = readLockOwner(path);
23569
23654
  if (!owner2) {
23570
23655
  throw new ResearchError(`research output ${ledger} has an unreadable lock at ${path}; verify no run is active, then remove it deliberately`);
@@ -23607,7 +23692,7 @@ function acquireResearchLock(ledger, breakStaleLock = false) {
23607
23692
  const text = JSON.stringify(owner) + "\n";
23608
23693
  let fd = -1;
23609
23694
  try {
23610
- fd = openSync5(path, "wx", 384);
23695
+ fd = openSync4(path, "wx", 384);
23611
23696
  writeSync2(fd, text);
23612
23697
  closeSync5(fd);
23613
23698
  fd = -1;
@@ -23672,7 +23757,7 @@ function resumableRecord(path, expected) {
23672
23757
  }
23673
23758
  function writeRecordAtomically(path, text) {
23674
23759
  const tmp = `${path}.${process.pid}.tmp`;
23675
- writeFileSync14(tmp, text);
23760
+ writeFileSync11(tmp, text);
23676
23761
  renameSync4(tmp, path);
23677
23762
  }
23678
23763
  function cloneArgs(repo, ws3) {
@@ -23937,7 +24022,7 @@ function runResearch(opts) {
23937
24022
  try {
23938
24023
  for (let pair = 1; pair <= pairs; pair++) {
23939
24024
  const path = pairRecordPath(ledger, task.id, pair);
23940
- if (!existsSync16(path)) continue;
24025
+ if (!existsSync15(path)) continue;
23941
24026
  const existing = resumableRecord(path, {
23942
24027
  task: task.id,
23943
24028
  pair,
@@ -24057,7 +24142,7 @@ var init_run2 = __esm({
24057
24142
  });
24058
24143
 
24059
24144
  // src/research/ledger-files.ts
24060
- import { closeSync as closeSync6, constants as constants5, fstatSync as fstatSync3, lstatSync as lstatSync12, openSync as openSync6, readFileSync as readFileSync22, readdirSync as readdirSync7, realpathSync as realpathSync12 } from "node:fs";
24145
+ import { closeSync as closeSync6, constants as constants5, fstatSync as fstatSync3, lstatSync as lstatSync12, openSync as openSync5, readFileSync as readFileSync22, readdirSync as readdirSync7, realpathSync as realpathSync12 } from "node:fs";
24061
24146
  import { join as join30, resolve as resolve20 } from "node:path";
24062
24147
  function lstatOrNull(path) {
24063
24148
  try {
@@ -24090,7 +24175,7 @@ function readRegularFile(path, realPairsDir, name) {
24090
24175
  const flags = constants5.O_RDONLY | (constants5.O_NOFOLLOW ?? 0);
24091
24176
  let fd;
24092
24177
  try {
24093
- fd = openSync6(path, flags);
24178
+ fd = openSync5(path, flags);
24094
24179
  } catch (e) {
24095
24180
  throw new ResearchError(`cannot open ledger record ${path}: ${errorMessage(e)}`);
24096
24181
  }
@@ -24273,7 +24358,7 @@ var init_summarize = __esm({
24273
24358
 
24274
24359
  // src/research/init.ts
24275
24360
  import { createHash as createHash17 } from "node:crypto";
24276
- import { existsSync as existsSync17, mkdirSync as mkdirSync13, readFileSync as readFileSync23, writeFileSync as writeFileSync15 } from "node:fs";
24361
+ import { existsSync as existsSync16, mkdirSync as mkdirSync13, readFileSync as readFileSync23, writeFileSync as writeFileSync12 } from "node:fs";
24277
24362
  import { basename as basename6, dirname as dirname15, resolve as resolve21 } from "node:path";
24278
24363
  function idFrom(repo) {
24279
24364
  const clean = repo.replace(/[\\/]$/, "").split(/[\\/]/).pop() || "task";
@@ -24285,7 +24370,7 @@ function createResearchManifest(opts) {
24285
24370
  if (!opts.prompt) throw new ResearchError("research init requires --prompt");
24286
24371
  if (!opts.verifyCommand) throw new ResearchError("research init requires --verify-command");
24287
24372
  const out3 = resolve21(opts.out);
24288
- if (existsSync17(out3)) throw new ResearchError(`research init refuses to overwrite ${out3}; choose a new path`);
24373
+ if (existsSync16(out3)) throw new ResearchError(`research init refuses to overwrite ${out3}; choose a new path`);
24289
24374
  const id = opts.id ?? idFrom(opts.repo);
24290
24375
  if (!/^[A-Za-z0-9._-]+$/.test(id)) throw new ResearchError(`research init id must match [A-Za-z0-9._-] (got "${id}")`);
24291
24376
  if (opts.verifyBudget !== void 0 && (!Number.isFinite(opts.verifyBudget) || opts.verifyBudget <= 0)) {
@@ -24303,7 +24388,7 @@ function createResearchManifest(opts) {
24303
24388
  };
24304
24389
  const text = JSON.stringify(doc, null, 2) + "\n";
24305
24390
  mkdirSync13(dirname15(out3), { recursive: true });
24306
- writeFileSync15(out3, text, { flag: "wx", mode: 384 });
24391
+ writeFileSync12(out3, text, { flag: "wx", mode: 384 });
24307
24392
  const parsed = readManifest(out3);
24308
24393
  if (parsed.tasks.length !== 1 || parsed.tasks[0].id !== id) throw new ResearchError(`research init wrote an invalid manifest ${out3}`);
24309
24394
  return out3;
@@ -24393,7 +24478,7 @@ var init_report2 = __esm({
24393
24478
  import { createHash as createHash18 } from "node:crypto";
24394
24479
  import { isDeepStrictEqual } from "node:util";
24395
24480
  import { gzipSync, gunzipSync } from "node:zlib";
24396
- import { mkdirSync as mkdirSync14, readFileSync as readFileSync24, writeFileSync as writeFileSync16 } from "node:fs";
24481
+ import { mkdirSync as mkdirSync14, readFileSync as readFileSync24, writeFileSync as writeFileSync13 } from "node:fs";
24397
24482
  import { dirname as dirname16, resolve as resolve22 } from "node:path";
24398
24483
  function octal(value, width) {
24399
24484
  return value.toString(8).padStart(width - 1, "0") + "\0";
@@ -24593,7 +24678,7 @@ function createResearchBundle(opts) {
24593
24678
  entries.push({ name: "provenance.json", bytes: jsonBytes(provenance) });
24594
24679
  const out3 = resolve22(opts.out);
24595
24680
  mkdirSync14(dirname16(out3), { recursive: true });
24596
- writeFileSync16(out3, gzipSync(makeTar(entries)), { flag: "wx", mode: 384 });
24681
+ writeFileSync13(out3, gzipSync(makeTar(entries)), { flag: "wx", mode: 384 });
24597
24682
  return out3;
24598
24683
  }
24599
24684
  function runResearchBundle(opts) {
@@ -24740,7 +24825,7 @@ var init_research = __esm({
24740
24825
  });
24741
24826
 
24742
24827
  // src/cli/receipt.ts
24743
- import { appendFileSync as appendFileSync3, readFileSync as readFileSync25, writeFileSync as writeFileSync17 } from "node:fs";
24828
+ import { appendFileSync as appendFileSync3, readFileSync as readFileSync25, writeFileSync as writeFileSync14 } from "node:fs";
24744
24829
  function parseReceipt(args) {
24745
24830
  const [sub, ...rest] = args;
24746
24831
  const exportOpts = {};
@@ -24842,7 +24927,7 @@ function runReceiptExport(opts = {}) {
24842
24927
  const serialized = JSON.stringify(receipt) + "\n";
24843
24928
  if (opts.out) {
24844
24929
  try {
24845
- writeFileSync17(opts.out, serialized);
24930
+ writeFileSync14(opts.out, serialized);
24846
24931
  } catch (e) {
24847
24932
  process.stderr.write(`tamperward: could not write receipt to ${opts.out} (${e instanceof Error ? e.message : String(e)})
24848
24933
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tamperward",
3
- "version": "2.39.11",
3
+ "version": "2.39.13",
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",