tamperward 2.37.0 → 2.37.2

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 +477 -169
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -5300,13 +5300,13 @@ function merge(a, b) {
5300
5300
  function literals(e) {
5301
5301
  if (ts.isStringLiteral(e)) return { items: [e.text], opaque: false };
5302
5302
  if (!ts.isArrayLiteralExpression(e)) return { items: [], opaque: true };
5303
- const items = [];
5303
+ const items2 = [];
5304
5304
  let opaque = false;
5305
5305
  for (const el of e.elements) {
5306
- if (ts.isStringLiteral(el) || ts.isNoSubstitutionTemplateLiteral(el)) items.push(el.text);
5306
+ if (ts.isStringLiteral(el) || ts.isNoSubstitutionTemplateLiteral(el)) items2.push(el.text);
5307
5307
  else opaque = true;
5308
5308
  }
5309
- return { items, opaque };
5309
+ return { items: items2, opaque };
5310
5310
  }
5311
5311
  function parseLists(src) {
5312
5312
  const res = { collectFrom: null, collectKey: "collectCoverageFrom", ignore: [], ignoreRe: [], opaqueCollect: false, opaqueIgnore: false };
@@ -5319,13 +5319,13 @@ function parseLists(src) {
5319
5319
  const k = keyName(node.name);
5320
5320
  const vitestCoverage = ownerKey(node) === "coverage";
5321
5321
  if (k === "collectCoverageFrom" || k === "include" && vitestCoverage) {
5322
- const { items, opaque } = literals(resolve23(node.initializer));
5323
- res.collectFrom = items;
5322
+ const { items: items2, opaque } = literals(resolve23(node.initializer));
5323
+ res.collectFrom = items2;
5324
5324
  res.collectKey = k === "include" ? "coverage.include" : k;
5325
5325
  if (opaque) res.opaqueCollect = true;
5326
5326
  } else if (k === "coveragePathIgnorePatterns" || k === "exclude" && vitestCoverage) {
5327
- const { items, opaque } = literals(resolve23(node.initializer));
5328
- (k === "exclude" ? res.ignore : res.ignoreRe).push(...items);
5327
+ const { items: items2, opaque } = literals(resolve23(node.initializer));
5328
+ (k === "exclude" ? res.ignore : res.ignoreRe).push(...items2);
5329
5329
  if (opaque) res.opaqueIgnore = true;
5330
5330
  }
5331
5331
  }
@@ -5923,15 +5923,15 @@ function keyName2(name) {
5923
5923
  function literals2(e) {
5924
5924
  if (isStr(e)) return { items: [e.text], opaque: false };
5925
5925
  if (!ts.isArrayLiteralExpression(e)) return { items: [], opaque: true };
5926
- const items = [];
5926
+ const items2 = [];
5927
5927
  let opaque = false;
5928
5928
  for (const el of e.elements) {
5929
- if (isStr(el)) items.push(el.text);
5929
+ if (isStr(el)) items2.push(el.text);
5930
5930
  else if (ts.isSpreadElement(el) && /^(?:configDefaults\.|default)(?:exclude|include)$/i.test(el.expression.getText())) {
5931
- items.push(.../exclude$/i.test(el.expression.getText()) ? VITEST_DEFAULT_EXCLUDE : VITEST_DEFAULT_INCLUDE);
5931
+ items2.push(.../exclude$/i.test(el.expression.getText()) ? VITEST_DEFAULT_EXCLUDE : VITEST_DEFAULT_INCLUDE);
5932
5932
  } else opaque = true;
5933
5933
  }
5934
- return { items, opaque };
5934
+ return { items: items2, opaque };
5935
5935
  }
5936
5936
  function ownerKey2(node) {
5937
5937
  const obj = node.parent;
@@ -6054,11 +6054,11 @@ function collect(root, runner) {
6054
6054
  const sel = empty();
6055
6055
  const seen = /* @__PURE__ */ new Set();
6056
6056
  const take = (key2, e) => {
6057
- const { items, opaque } = literals2(e);
6057
+ const { items: items2, opaque } = literals2(e);
6058
6058
  if (opaque || seen.has(key2)) sel.opaque = true;
6059
6059
  seen.add(key2);
6060
6060
  sel.present = true;
6061
- return items.map(stripRootDir);
6061
+ return items2.map(stripRootDir);
6062
6062
  };
6063
6063
  const ignores = (key2, e) => {
6064
6064
  const entries = take(key2, e).map((pattern) => ({ pattern, key: key2 }));
@@ -10701,10 +10701,10 @@ function validate(r, where) {
10701
10701
  throw new PolicyError(`${where}: ${msg}`);
10702
10702
  }
10703
10703
  const show = (v) => JSON.stringify(v) ?? String(v);
10704
- function refuseNegated(field, list3) {
10704
+ function refuseNegated(field2, list3) {
10705
10705
  const neg = list3.find(isNegatedGlob);
10706
10706
  if (neg !== void 0) {
10707
- bad2(`${field}: negated glob ${show(neg)} is not allowed \u2014 a "!" pattern matches every path, so it would ${field === "ignore" ? "ignore every file" : field.startsWith("protected.") ? `put every file in ${field}` : "blind the rule on every file"}; list the paths to include instead`);
10707
+ bad2(`${field2}: negated glob ${show(neg)} is not allowed \u2014 a "!" pattern matches every path, so it would ${field2 === "ignore" ? "ignore every file" : field2.startsWith("protected.") ? `put every file in ${field2}` : "blind the rule on every file"}; list the paths to include instead`);
10708
10708
  }
10709
10709
  }
10710
10710
  const out3 = {};
@@ -12201,7 +12201,7 @@ var init_fs_events = __esm({
12201
12201
 
12202
12202
  // src/cli/audit.ts
12203
12203
  import { createHash as createHash6, randomBytes } from "node:crypto";
12204
- import { appendFileSync as appendFileSync4, existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync10 } from "node:fs";
12204
+ import { appendFileSync as appendFileSync4, closeSync as closeSync3, existsSync as existsSync9, mkdirSync as mkdirSync5, openSync as openSync3, readSync as readSync3 } from "node:fs";
12205
12205
  import { dirname as dirname4, isAbsolute as isAbsolute8, join as join13, resolve as resolve4 } from "node:path";
12206
12206
  function sessionHash(sessionId) {
12207
12207
  if (!sessionId) return void 0;
@@ -12311,19 +12311,56 @@ function parseAuditEvent(value, line = 0) {
12311
12311
  ...typeof value.session === "string" ? { session: value.session } : {}
12312
12312
  };
12313
12313
  }
12314
- function parseAuditJsonl(raw) {
12315
- const events = [];
12316
- raw.split(/\r?\n/).forEach((line, index) => {
12317
- if (!line.trim()) return;
12318
- let value;
12319
- try {
12320
- value = JSON.parse(line);
12321
- } catch {
12322
- throw new Error(`audit line ${index + 1} is not valid JSON`);
12314
+ function parseAuditLine(line, lineNumber) {
12315
+ let value;
12316
+ try {
12317
+ value = JSON.parse(line);
12318
+ } catch {
12319
+ throw new Error(`audit line ${lineNumber} is not valid JSON`);
12320
+ }
12321
+ return parseAuditEvent(value, lineNumber);
12322
+ }
12323
+ function forEachAuditLine(file, onLine, maxLineBytes = MAX_AUDIT_LINE_BYTES) {
12324
+ const fd = openSync3(file, "r");
12325
+ try {
12326
+ const chunk = Buffer.allocUnsafe(64 * 1024);
12327
+ let pending = [];
12328
+ let pendingBytes = 0;
12329
+ let lineNumber = 0;
12330
+ const emit3 = (parts) => {
12331
+ lineNumber++;
12332
+ let line = parts.length === 1 ? parts[0].toString("utf8") : Buffer.concat(parts).toString("utf8");
12333
+ if (line.endsWith("\r")) line = line.slice(0, -1);
12334
+ if (line.trim()) onLine(line, lineNumber);
12335
+ };
12336
+ for (; ; ) {
12337
+ const read = readSync3(fd, chunk, 0, chunk.length, null);
12338
+ if (read === 0) break;
12339
+ let start = 0;
12340
+ for (let i = 0; i < read; i++) {
12341
+ if (chunk[i] !== 10) continue;
12342
+ const part = chunk.subarray(start, i);
12343
+ if (pendingBytes + part.length > maxLineBytes) {
12344
+ throw new Error(`audit line ${lineNumber + 1} exceeds ${maxLineBytes} bytes (not an audit-v1 event)`);
12345
+ }
12346
+ emit3(pending.length ? [...pending, part] : [part]);
12347
+ pending = [];
12348
+ pendingBytes = 0;
12349
+ start = i + 1;
12350
+ }
12351
+ if (start < read) {
12352
+ const rest = Buffer.from(chunk.subarray(start, read));
12353
+ pendingBytes += rest.length;
12354
+ if (pendingBytes > maxLineBytes) {
12355
+ throw new Error(`audit line ${lineNumber + 1} exceeds ${maxLineBytes} bytes (not an audit-v1 event)`);
12356
+ }
12357
+ pending.push(rest);
12358
+ }
12323
12359
  }
12324
- events.push(parseAuditEvent(value, index + 1));
12325
- });
12326
- return events;
12360
+ if (pending.length) emit3(pending);
12361
+ } finally {
12362
+ closeSync3(fd);
12363
+ }
12327
12364
  }
12328
12365
  function parseSince(value, nowMs = Date.now()) {
12329
12366
  const relative13 = /^(\d+)(m|h|d)$/.exec(value);
@@ -12339,37 +12376,58 @@ function parseSince(value, nowMs = Date.now()) {
12339
12376
  }
12340
12377
  return parsed;
12341
12378
  }
12342
- function summarizeAudit(events) {
12343
- const ordered = [...events].sort(
12344
- (a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp) || a.id.localeCompare(b.id)
12345
- );
12379
+ function createAuditAggregator() {
12380
+ let events = 0;
12381
+ let blocked = 0;
12382
+ let warnings = 0;
12346
12383
  const rules = /* @__PURE__ */ new Map();
12347
12384
  const surfaces = /* @__PURE__ */ new Map();
12348
12385
  const sessions = /* @__PURE__ */ new Set();
12349
- for (const event of ordered) {
12350
- const bucket = rules.get(event.rule) ?? { events: 0, blocked: 0, warnings: 0 };
12351
- bucket.events++;
12352
- if (event.severity === "block") bucket.blocked++;
12353
- else bucket.warnings++;
12354
- rules.set(event.rule, bucket);
12355
- surfaces.set(event.surface, (surfaces.get(event.surface) ?? 0) + 1);
12356
- if (event.session) sessions.add(event.session);
12357
- }
12358
- const by_rule = [...rules.entries()].map(([rule, counts]) => ({ rule, ...counts })).sort((a, b) => b.events - a.events || a.rule.localeCompare(b.rule));
12359
- const by_surface = [...surfaces.entries()].map(([surface, count]) => ({ surface, events: count })).sort((a, b) => b.events - a.events || a.surface.localeCompare(b.surface));
12386
+ const bounds = { first: null, last: null };
12360
12387
  return {
12361
- schema_version: AUDIT_SCHEMA_VERSION,
12362
- events: ordered.length,
12363
- blocked: ordered.filter((event) => event.severity === "block").length,
12364
- warnings: ordered.filter((event) => event.severity === "warn").length,
12365
- sessions: sessions.size,
12366
- first_event: ordered[0]?.timestamp ?? null,
12367
- last_event: ordered.at(-1)?.timestamp ?? null,
12368
- by_rule,
12369
- by_surface,
12370
- interpretation: "finding-is-not-proof-of-intent"
12388
+ add(event) {
12389
+ events++;
12390
+ if (event.severity === "block") blocked++;
12391
+ else warnings++;
12392
+ const bucket = rules.get(event.rule) ?? { events: 0, blocked: 0, warnings: 0 };
12393
+ bucket.events++;
12394
+ if (event.severity === "block") bucket.blocked++;
12395
+ else bucket.warnings++;
12396
+ rules.set(event.rule, bucket);
12397
+ surfaces.set(event.surface, (surfaces.get(event.surface) ?? 0) + 1);
12398
+ if (event.session) sessions.add(event.session);
12399
+ const ms = Date.parse(event.timestamp);
12400
+ const { first, last } = bounds;
12401
+ if (first === null || ms < first.ms || ms === first.ms && event.id.localeCompare(first.id) < 0) {
12402
+ bounds.first = { ms, id: event.id, timestamp: event.timestamp };
12403
+ }
12404
+ if (last === null || ms > last.ms || ms === last.ms && event.id.localeCompare(last.id) > 0) {
12405
+ bounds.last = { ms, id: event.id, timestamp: event.timestamp };
12406
+ }
12407
+ },
12408
+ finish() {
12409
+ const by_rule = [...rules.entries()].map(([rule, counts]) => ({ rule, ...counts })).sort((a, b) => b.events - a.events || a.rule.localeCompare(b.rule));
12410
+ const by_surface = [...surfaces.entries()].map(([surface, count]) => ({ surface, events: count })).sort((a, b) => b.events - a.events || a.surface.localeCompare(b.surface));
12411
+ return {
12412
+ schema_version: AUDIT_SCHEMA_VERSION,
12413
+ events,
12414
+ blocked,
12415
+ warnings,
12416
+ sessions: sessions.size,
12417
+ first_event: bounds.first === null ? null : bounds.first.timestamp,
12418
+ last_event: bounds.last === null ? null : bounds.last.timestamp,
12419
+ by_rule,
12420
+ by_surface,
12421
+ interpretation: "finding-is-not-proof-of-intent"
12422
+ };
12423
+ }
12371
12424
  };
12372
12425
  }
12426
+ function summarizeAudit(events) {
12427
+ const aggregator = createAuditAggregator();
12428
+ for (const event of events) aggregator.add(event);
12429
+ return aggregator.finish();
12430
+ }
12373
12431
  function renderAuditStats(summary) {
12374
12432
  const out3 = [
12375
12433
  "TamperWard audit stats",
@@ -12408,21 +12466,24 @@ function runStats(opts = {}) {
12408
12466
  process.stdout.write(opts.json ? JSON.stringify(empty2) + "\n" : renderAuditStats(empty2));
12409
12467
  return 0;
12410
12468
  }
12411
- let events = parseAuditJsonl(readFileSync10(file, "utf8"));
12412
- if (cutoff !== null) {
12413
- events = events.filter((event) => Date.parse(event.timestamp) >= cutoff);
12414
- }
12415
- const summary = summarizeAudit(events);
12469
+ const aggregator = createAuditAggregator();
12470
+ forEachAuditLine(file, (line, lineNumber) => {
12471
+ const event = parseAuditLine(line, lineNumber);
12472
+ if (cutoff !== null && Date.parse(event.timestamp) < cutoff) return;
12473
+ aggregator.add(event);
12474
+ });
12475
+ const summary = aggregator.finish();
12416
12476
  process.stdout.write(opts.json ? JSON.stringify(summary) + "\n" : renderAuditStats(summary));
12417
12477
  return 0;
12418
12478
  }
12419
- var AUDIT_SCHEMA_VERSION, AUDIT_LOG_ENV, ALLOWED_KEYS;
12479
+ var AUDIT_SCHEMA_VERSION, AUDIT_LOG_ENV, MAX_AUDIT_LINE_BYTES, ALLOWED_KEYS;
12420
12480
  var init_audit = __esm({
12421
12481
  "src/cli/audit.ts"() {
12422
12482
  "use strict";
12423
12483
  init_repo_context();
12424
12484
  AUDIT_SCHEMA_VERSION = 1;
12425
12485
  AUDIT_LOG_ENV = "TAMPERWARD_AUDIT_LOG";
12486
+ MAX_AUDIT_LINE_BYTES = 16 * 1024;
12426
12487
  ALLOWED_KEYS = /* @__PURE__ */ new Set([
12427
12488
  "schema_version",
12428
12489
  "id",
@@ -12438,14 +12499,14 @@ var init_audit = __esm({
12438
12499
  });
12439
12500
 
12440
12501
  // src/cli/hook.ts
12441
- import { readFileSync as readFileSync11, appendFileSync as appendFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync10 } from "node:fs";
12502
+ import { readFileSync as readFileSync10, appendFileSync as appendFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync10 } from "node:fs";
12442
12503
  import { createHash as createHash7 } from "node:crypto";
12443
12504
  function setSnapshotCache(cache3) {
12444
12505
  snapshotCache = cache3;
12445
12506
  }
12446
12507
  function readStdin() {
12447
12508
  try {
12448
- return readFileSync11(0, "utf8");
12509
+ return readFileSync10(0, "utf8");
12449
12510
  } catch (e) {
12450
12511
  throw new HookInputError(`cannot read the hook payload on stdin: ${e instanceof Error ? e.message : String(e)}`);
12451
12512
  }
@@ -12738,7 +12799,7 @@ function turnTransientBlocks(cwd, sessionId, policy, changes) {
12738
12799
  if (!existsSync10(log)) return none;
12739
12800
  let offset = 0;
12740
12801
  if (cp && existsSync10(cp)) {
12741
- const n2 = Number(readFileSync11(cp, "utf8"));
12802
+ const n2 = Number(readFileSync10(cp, "utf8"));
12742
12803
  offset = Number.isFinite(n2) ? n2 : 0;
12743
12804
  }
12744
12805
  const drained = drainEvents(log, offset);
@@ -13012,13 +13073,13 @@ var init_ptree_cache = __esm({
13012
13073
  });
13013
13074
 
13014
13075
  // src/cli/hook-service.ts
13015
- import { chmodSync, existsSync as existsSync11, lstatSync as lstatSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync12, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "node:fs";
13076
+ import { chmodSync, existsSync as existsSync11, lstatSync as lstatSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync11, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "node:fs";
13016
13077
  import { createConnection as createConnection2, createServer } from "node:net";
13017
13078
  import { realpathSync as realpathSync4 } from "node:fs";
13018
13079
  import { sep } from "node:path";
13019
13080
  function readServiceState(paths) {
13020
13081
  try {
13021
- const v = JSON.parse(readFileSync12(paths.state, "utf8"));
13082
+ const v = JSON.parse(readFileSync11(paths.state, "utf8"));
13022
13083
  if (!isRecord(v)) return null;
13023
13084
  const { pid, version, root, started_at } = v;
13024
13085
  if (typeof pid !== "number" || typeof version !== "string" || typeof root !== "string" || typeof started_at !== "string") return null;
@@ -13579,7 +13640,7 @@ var init_safe_write = __esm({
13579
13640
  // src/cli/init.ts
13580
13641
  import { execFileSync as execFileSync6 } from "node:child_process";
13581
13642
  import { createHash as createHash8 } from "node:crypto";
13582
- import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync13, realpathSync as realpathSync5 } from "node:fs";
13643
+ import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync12, realpathSync as realpathSync5 } from "node:fs";
13583
13644
  import { basename as basename3, dirname as dirname5, isAbsolute as isAbsolute9, join as join15, relative as relative4, resolve as resolve6 } from "node:path";
13584
13645
  function gitConfig(cwd, key2) {
13585
13646
  try {
@@ -13673,34 +13734,34 @@ function verifierCandidates(cwd) {
13673
13734
  if (!candidates.includes(command)) candidates.push(command);
13674
13735
  };
13675
13736
  try {
13676
- const pkg = JSON.parse(readFileSync13(join15(cwd, "package.json"), "utf8"));
13737
+ const pkg = JSON.parse(readFileSync12(join15(cwd, "package.json"), "utf8"));
13677
13738
  const test = isRecord(pkg) && isRecord(pkg.scripts) ? pkg.scripts.test : void 0;
13678
13739
  if (typeof test === "string" && test.trim() !== "" && !/error:\s*no test specified/i.test(test)) add("npm test");
13679
13740
  } catch {
13680
13741
  }
13681
13742
  try {
13682
- const pyproject = readFileSync13(join15(cwd, "pyproject.toml"), "utf8");
13743
+ const pyproject = readFileSync12(join15(cwd, "pyproject.toml"), "utf8");
13683
13744
  if (/^\s*\[tool\.pytest(?:\.ini_options)?\]\s*$/m.test(pyproject)) add("pytest");
13684
13745
  } catch {
13685
13746
  }
13686
13747
  if (existsSync12(join15(cwd, "pytest.ini"))) add("pytest");
13687
13748
  try {
13688
- const setupCfg = readFileSync13(join15(cwd, "setup.cfg"), "utf8");
13749
+ const setupCfg = readFileSync12(join15(cwd, "setup.cfg"), "utf8");
13689
13750
  if (/^\s*\[tool:pytest\]\s*$/m.test(setupCfg)) add("pytest");
13690
13751
  } catch {
13691
13752
  }
13692
13753
  try {
13693
- const tox = readFileSync13(join15(cwd, "tox.ini"), "utf8");
13754
+ const tox = readFileSync12(join15(cwd, "tox.ini"), "utf8");
13694
13755
  if (/^\s*\[tox\]\s*$/m.test(tox)) add("tox");
13695
13756
  } catch {
13696
13757
  }
13697
13758
  try {
13698
- const cargo = readFileSync13(join15(cwd, "Cargo.toml"), "utf8");
13759
+ const cargo = readFileSync12(join15(cwd, "Cargo.toml"), "utf8");
13699
13760
  if (/^\s*\[(?:package|workspace)\]\s*(?:#.*)?$/m.test(cargo)) add("cargo test");
13700
13761
  } catch {
13701
13762
  }
13702
13763
  try {
13703
- const goMod = readFileSync13(join15(cwd, "go.mod"), "utf8");
13764
+ const goMod = readFileSync12(join15(cwd, "go.mod"), "utf8");
13704
13765
  if (/^\s*module\s+\S+\s*(?:\/\/.*)?$/m.test(goMod)) add("go test ./...");
13705
13766
  } catch {
13706
13767
  }
@@ -13772,7 +13833,7 @@ function planGitignore(cwd) {
13772
13833
  }
13773
13834
  const refused = refuseNonRegular(path);
13774
13835
  if (refused) return { item: "gitignore", path: rel, status: "error", detail: refused };
13775
- if (kind === "file") existing = readFileSync13(path, "utf8");
13836
+ if (kind === "file") existing = readFileSync12(path, "utf8");
13776
13837
  try {
13777
13838
  const tracked = execFileSync6("git", ["ls-files", "--cached", "--", "node_modules"], {
13778
13839
  cwd,
@@ -13830,7 +13891,7 @@ function planClaudeHooks(cwd) {
13830
13891
  if (existsSync12(path)) {
13831
13892
  let parsed;
13832
13893
  try {
13833
- parsed = JSON.parse(readFileSync13(path, "utf8"));
13894
+ parsed = JSON.parse(readFileSync12(path, "utf8"));
13834
13895
  } catch {
13835
13896
  return {
13836
13897
  item: "agent",
@@ -13972,7 +14033,7 @@ function planPreCommit(cwd) {
13972
14033
  const rel = display(cwd, path);
13973
14034
  const refused = refuseNonRegular(path);
13974
14035
  if (refused) return { item: "pre-commit", path: rel, status: "error", detail: refused };
13975
- const existing = existsSync12(path) ? readFileSync13(path, "utf8") : null;
14036
+ const existing = existsSync12(path) ? readFileSync12(path, "utf8") : null;
13976
14037
  const hookMode = () => existingMode(path, 493) | 73;
13977
14038
  if (existing === null) {
13978
14039
  return {
@@ -14063,7 +14124,7 @@ function planCodeowners(cwd) {
14063
14124
  const path = join15(cwd, existingRel ?? rel);
14064
14125
  const refused = refuseNonRegular(path);
14065
14126
  if (refused) return { item: "codeowners", path: existingRel ?? rel, status: "error", detail: refused };
14066
- const existing = existingRel ? readFileSync13(path, "utf8") : null;
14127
+ const existing = existingRel ? readFileSync12(path, "utf8") : null;
14067
14128
  const missing = CODEOWNERS_PATHS.filter((p) => existing === null || !coveredBy(existing, p));
14068
14129
  if (existingRel !== void 0 && missing.length === 0) {
14069
14130
  return { item: "codeowners", path: existingRel, status: "ok", detail: "gate paths already have code owners" };
@@ -14114,7 +14175,7 @@ function planWorkflow(cwd, force) {
14114
14175
  }
14115
14176
  let src;
14116
14177
  try {
14117
- src = readFileSync13(path, "utf8");
14178
+ src = readFileSync12(path, "utf8");
14118
14179
  } catch {
14119
14180
  return { item: "ci", path: rel, status: "error", detail: "exists but cannot be read \u2014 refusing to overwrite" };
14120
14181
  }
@@ -14414,7 +14475,7 @@ import {
14414
14475
  existsSync as existsSync13,
14415
14476
  lstatSync as lstatSync8,
14416
14477
  readdirSync as readdirSync3,
14417
- readFileSync as readFileSync14,
14478
+ readFileSync as readFileSync13,
14418
14479
  readlinkSync as readlinkSync2,
14419
14480
  realpathSync as realpathSync6,
14420
14481
  statSync as statSync3
@@ -14472,7 +14533,7 @@ function fingerprintRoot(root, cwd, h) {
14472
14533
  st = statSync3(path);
14473
14534
  if (!st.isFile()) throw new Error("not a regular file");
14474
14535
  h.update(label + "\0external-file\0" + path + "\0" + String(st.mode) + "\0");
14475
- h.update(readFileSync14(path));
14536
+ h.update(readFileSync13(path));
14476
14537
  h.update("\0");
14477
14538
  } catch (e) {
14478
14539
  throw new Unattestable(
@@ -14506,7 +14567,7 @@ function fingerprintRoot(root, cwd, h) {
14506
14567
  h.update(rel + "\0" + ident.kind + "\0" + String(ident.mode) + "\0");
14507
14568
  if (ident.kind === "file") {
14508
14569
  try {
14509
- h.update(readFileSync14(path));
14570
+ h.update(readFileSync13(path));
14510
14571
  h.update("\0");
14511
14572
  } catch (e) {
14512
14573
  throw new Unattestable(
@@ -14677,7 +14738,7 @@ function venvProblem(root) {
14677
14738
  const cfg = join16(root, "pyvenv.cfg");
14678
14739
  if (!existsSync13(cfg)) return "VIRTUAL_ENV " + JSON.stringify(root) + " has no pyvenv.cfg";
14679
14740
  try {
14680
- const body = readFileSync14(cfg, "utf8");
14741
+ const body = readFileSync13(cfg, "utf8");
14681
14742
  if (/^\s*include-system-site-packages\s*=\s*true\s*$/im.test(body)) {
14682
14743
  return "Python environment " + JSON.stringify(root) + " enables system site-packages; those external package roots are not bounded";
14683
14744
  }
@@ -15193,7 +15254,7 @@ if (child) {
15193
15254
  // src/verifier-backend.ts
15194
15255
  import { createHash as createHash11, randomUUID } from "node:crypto";
15195
15256
  import { spawnSync as spawnSync2 } from "node:child_process";
15196
- import { accessSync as accessSync2, constants as constants3, readFileSync as readFileSync15, realpathSync as realpathSync7, statSync as statSync4 } from "node:fs";
15257
+ import { accessSync as accessSync2, constants as constants3, readFileSync as readFileSync14, realpathSync as realpathSync7, statSync as statSync4 } from "node:fs";
15197
15258
  import { delimiter as delimiter2, dirname as dirname7, join as join18, resolve as resolve8 } from "node:path";
15198
15259
  function identity() {
15199
15260
  const uid = typeof process.getuid === "function" ? process.getuid() : null;
@@ -15238,7 +15299,7 @@ function findExecutable(name) {
15238
15299
  return null;
15239
15300
  }
15240
15301
  function fileSha256(path) {
15241
- return createHash11("sha256").update(readFileSync15(path)).digest("hex");
15302
+ return createHash11("sha256").update(readFileSync14(path)).digest("hex");
15242
15303
  }
15243
15304
  function engineClientEnv() {
15244
15305
  return {
@@ -15672,7 +15733,7 @@ var init_verifier_backend = __esm({
15672
15733
  // src/verification-state.ts
15673
15734
  import { execFileSync as execFileSync8 } from "node:child_process";
15674
15735
  import { createHash as createHash12 } from "node:crypto";
15675
- import { mkdirSync as mkdirSync8, readFileSync as readFileSync16, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
15736
+ import { mkdirSync as mkdirSync8, readFileSync as readFileSync15, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
15676
15737
  import { dirname as dirname8, join as join19 } from "node:path";
15677
15738
  function git2(args, cwd) {
15678
15739
  return execFileSync8("git", args, {
@@ -15721,7 +15782,7 @@ function interventionWiring(cwd) {
15721
15782
  return sources.map(([source, path]) => {
15722
15783
  let parsed;
15723
15784
  try {
15724
- parsed = JSON.parse(readFileSync16(path, "utf8"));
15785
+ parsed = JSON.parse(readFileSync15(path, "utf8"));
15725
15786
  } catch {
15726
15787
  return { source, present: false };
15727
15788
  }
@@ -15874,7 +15935,7 @@ function readVerificationRecord(cwd) {
15874
15935
  if (!path) return null;
15875
15936
  let raw;
15876
15937
  try {
15877
- raw = readFileSync16(path, "utf8");
15938
+ raw = readFileSync15(path, "utf8");
15878
15939
  } catch {
15879
15940
  return null;
15880
15941
  }
@@ -15923,7 +15984,7 @@ function readVerifyingMarker(cwd) {
15923
15984
  if (!path) return null;
15924
15985
  let parsed;
15925
15986
  try {
15926
- parsed = JSON.parse(readFileSync16(path, "utf8"));
15987
+ parsed = JSON.parse(readFileSync15(path, "utf8"));
15927
15988
  } catch {
15928
15989
  return null;
15929
15990
  }
@@ -16035,7 +16096,7 @@ var init_verification_state = __esm({
16035
16096
  // src/cli/verify.ts
16036
16097
  import { execFileSync as execFileSync9, spawnSync as spawnSync3 } from "node:child_process";
16037
16098
  import { createHash as createHash13 } from "node:crypto";
16038
- import { chmodSync as chmodSync2, cpSync, lstatSync as lstatSync9, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync3, readFileSync as readFileSync17, readlinkSync as readlinkSync3, rmSync as rmSync8, symlinkSync, writeFileSync as writeFileSync10, existsSync as existsSync14 } from "node:fs";
16099
+ import { chmodSync as chmodSync2, cpSync, lstatSync as lstatSync9, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync3, readFileSync as readFileSync16, readlinkSync as readlinkSync3, rmSync as rmSync8, symlinkSync, writeFileSync as writeFileSync10, existsSync as existsSync14 } from "node:fs";
16039
16100
  import { tmpdir as tmpdir4 } from "node:os";
16040
16101
  import { dirname as dirname9, isAbsolute as isAbsolute11, join as join20, relative as relative6, resolve as resolve9, sep as sep4 } from "node:path";
16041
16102
  function oracleAssuranceReport() {
@@ -16295,7 +16356,7 @@ function overlayDigest(dest, paths) {
16295
16356
  try {
16296
16357
  const st = lstatSync9(p);
16297
16358
  h.update(String(st.mode));
16298
- h.update(st.isSymbolicLink() ? readlinkSync3(p) : readFileSync17(p));
16359
+ h.update(st.isSymbolicLink() ? readlinkSync3(p) : readFileSync16(p));
16299
16360
  } catch {
16300
16361
  h.update("<gone>");
16301
16362
  }
@@ -17092,7 +17153,7 @@ var init_verify = __esm({
17092
17153
  // src/cli/run.ts
17093
17154
  import { execFileSync as execFileSync10, spawn, spawnSync as spawnSync4 } from "node:child_process";
17094
17155
  import { randomBytes as randomBytes3 } from "node:crypto";
17095
- import { accessSync as accessSync3, constants as fsConstants, mkdtempSync as mkdtempSync4, readdirSync as readdirSync4, readFileSync as readFileSync18, readlinkSync as readlinkSync4, realpathSync as realpathSync8, rmSync as rmSync9, statSync as statSync5, writeFileSync as writeFileSync11 } from "node:fs";
17156
+ import { accessSync as accessSync3, constants as fsConstants, mkdtempSync as mkdtempSync4, readdirSync as readdirSync4, readFileSync as readFileSync17, readlinkSync as readlinkSync4, realpathSync as realpathSync8, rmSync as rmSync9, statSync as statSync5, writeFileSync as writeFileSync11 } from "node:fs";
17096
17157
  import { tmpdir as tmpdir5 } from "node:os";
17097
17158
  import { dirname as dirname10, join as join21, resolve as resolve10 } from "node:path";
17098
17159
  function authoritativeRunLifecyclePlatform(platform = process.platform) {
@@ -17246,7 +17307,7 @@ function runAgentSupervised(argv, cwd, budgetSecs, linuxPythonCandidates, lifecy
17246
17307
  }
17247
17308
  let state = {};
17248
17309
  try {
17249
- state = JSON.parse(readFileSync18(resultFile, "utf8"));
17310
+ state = JSON.parse(readFileSync17(resultFile, "utf8"));
17250
17311
  } catch {
17251
17312
  return {
17252
17313
  exit: 1,
@@ -17290,7 +17351,7 @@ function observerExited(pid) {
17290
17351
  if (!pidAlive4(pid)) return true;
17291
17352
  if (process.platform !== "linux") return false;
17292
17353
  try {
17293
- const stat = readFileSync18(`/proc/${pid}/stat`, "utf8");
17354
+ const stat = readFileSync17(`/proc/${pid}/stat`, "utf8");
17294
17355
  const state = stat.slice(stat.lastIndexOf(")") + 2).split(" ")[0];
17295
17356
  return state === "Z" || state === "X";
17296
17357
  } catch (e) {
@@ -17412,7 +17473,7 @@ function git4(args, cwd) {
17412
17473
  }
17413
17474
  function nowTicks() {
17414
17475
  try {
17415
- return Math.floor(parseFloat(readFileSync18("/proc/uptime", "utf8").split(" ")[0]) * 100);
17476
+ return Math.floor(parseFloat(readFileSync17("/proc/uptime", "utf8").split(" ")[0]) * 100);
17416
17477
  } catch {
17417
17478
  return Number.POSITIVE_INFINITY;
17418
17479
  }
@@ -17435,7 +17496,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
17435
17496
  const n2 = Number(pid);
17436
17497
  if (n2 === process.pid) continue;
17437
17498
  try {
17438
- const stat = readFileSync18(`/proc/${pid}/stat`, "utf8");
17499
+ const stat = readFileSync17(`/proc/${pid}/stat`, "utf8");
17439
17500
  const startTicks = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[19]);
17440
17501
  if (!Number.isFinite(startTicks) || startTicks < spawnedAfterTicks) continue;
17441
17502
  } catch {
@@ -18008,7 +18069,7 @@ raise SystemExit(0)
18008
18069
 
18009
18070
  // src/cli/doctor.ts
18010
18071
  import { execFileSync as execFileSync11 } from "node:child_process";
18011
- import { existsSync as existsSync15, readFileSync as readFileSync19, readdirSync as readdirSync5 } from "node:fs";
18072
+ import { existsSync as existsSync15, readFileSync as readFileSync18, readdirSync as readdirSync5 } from "node:fs";
18012
18073
  import { join as join22, resolve as resolve11 } from "node:path";
18013
18074
  function lifecyclePlatformCheck(platform = process.platform, linuxPython = platform === "linux" ? trustedLinuxPython() : null) {
18014
18075
  if (platform === "linux") {
@@ -18067,7 +18128,7 @@ function pinsInFile(path) {
18067
18128
  if (!existsSync15(path)) return [];
18068
18129
  let src = "";
18069
18130
  try {
18070
- src = readFileSync19(path, "utf8");
18131
+ src = readFileSync18(path, "utf8");
18071
18132
  } catch {
18072
18133
  return [];
18073
18134
  }
@@ -18094,7 +18155,7 @@ function workflowPermissionCheck(cwd, workflowRels = [".github/workflows/tamperw
18094
18155
  }
18095
18156
  let doc;
18096
18157
  try {
18097
- doc = yaml.parse(readFileSync19(path, "utf8"));
18158
+ doc = yaml.parse(readFileSync18(path, "utf8"));
18098
18159
  } catch (e) {
18099
18160
  broken.push(`${rel} is not valid YAML (${e instanceof Error ? e.message : String(e)})`);
18100
18161
  continue;
@@ -18518,7 +18579,7 @@ function diagnose(opts = {}) {
18518
18579
  }
18519
18580
  let doc;
18520
18581
  try {
18521
- doc = yaml.parse(readFileSync19(workflowPath, "utf8"));
18582
+ doc = yaml.parse(readFileSync18(workflowPath, "utf8"));
18522
18583
  } catch (e) {
18523
18584
  return fail(
18524
18585
  "ci-verifier",
@@ -18831,7 +18892,7 @@ import { execFileSync as execFileSync12, spawnSync as spawnSync5 } from "node:ch
18831
18892
  import {
18832
18893
  existsSync as existsSync16,
18833
18894
  mkdtempSync as mkdtempSync5,
18834
- readFileSync as readFileSync20,
18895
+ readFileSync as readFileSync19,
18835
18896
  readdirSync as readdirSync6,
18836
18897
  realpathSync as realpathSync9,
18837
18898
  rmSync as rmSync10,
@@ -19145,7 +19206,7 @@ function straceOnce(root, command, budget) {
19145
19206
  }
19146
19207
  );
19147
19208
  const logs = traced.error ? [] : traceFiles(prefix);
19148
- const accesses = logs.flatMap((path) => parseStraceFileAccess(readFileSync20(path, "utf8"), TRACE_ROOT));
19209
+ const accesses = logs.flatMap((path) => parseStraceFileAccess(readFileSync19(path, "utf8"), TRACE_ROOT));
19149
19210
  const raw = {
19150
19211
  spawnError: traced.error ? traced.error.message : null,
19151
19212
  status: traced.status,
@@ -19377,7 +19438,7 @@ var init_trace_verify = __esm({
19377
19438
  });
19378
19439
 
19379
19440
  // src/runtimes.ts
19380
- import { existsSync as existsSync17, readFileSync as readFileSync21 } from "node:fs";
19441
+ import { existsSync as existsSync17, readFileSync as readFileSync20 } from "node:fs";
19381
19442
  import { join as join24 } from "node:path";
19382
19443
  function isRecord5(x) {
19383
19444
  return typeof x === "object" && x !== null && !Array.isArray(x);
@@ -19408,7 +19469,7 @@ function claudeSettingsBeyondTamperward(cwd) {
19408
19469
  let raw;
19409
19470
  try {
19410
19471
  if (!existsSync17(path)) return false;
19411
- raw = readFileSync21(path, "utf8");
19472
+ raw = readFileSync20(path, "utf8");
19412
19473
  } catch {
19413
19474
  return false;
19414
19475
  }
@@ -19515,7 +19576,7 @@ var init_runtimes = __esm({
19515
19576
 
19516
19577
  // src/cli/onboard.ts
19517
19578
  import { execFileSync as execFileSync13 } from "node:child_process";
19518
- import { lstatSync as lstatSync10, mkdtempSync as mkdtempSync6, readFileSync as readFileSync22, realpathSync as realpathSync10, rmSync as rmSync11 } from "node:fs";
19579
+ import { lstatSync as lstatSync10, mkdtempSync as mkdtempSync6, readFileSync as readFileSync21, realpathSync as realpathSync10, rmSync as rmSync11 } from "node:fs";
19519
19580
  import { tmpdir as tmpdir7 } from "node:os";
19520
19581
  import { join as join25, relative as relative8, resolve as resolve13, sep as sep6 } from "node:path";
19521
19582
  import { createInterface } from "node:readline";
@@ -19588,7 +19649,7 @@ function writeVerifyCommand(cwd, command) {
19588
19649
  return `${POLICY_FILE} is not a regular file; refusing to follow or replace a symlink or special file`;
19589
19650
  }
19590
19651
  const mode = existingMode(path, 420);
19591
- const original = kind === "file" ? readFileSync22(path, "utf8") : null;
19652
+ const original = kind === "file" ? readFileSync21(path, "utf8") : null;
19592
19653
  let next;
19593
19654
  try {
19594
19655
  const doc = parseDocument(original && original.trim() ? original : "version: 1\n");
@@ -20200,7 +20261,7 @@ var init_capture = __esm({
20200
20261
 
20201
20262
  // src/research/manifest.ts
20202
20263
  import { createHash as createHash14 } from "node:crypto";
20203
- import { readFileSync as readFileSync23 } from "node:fs";
20264
+ import { readFileSync as readFileSync22 } from "node:fs";
20204
20265
  import { dirname as dirname11, resolve as resolve15 } from "node:path";
20205
20266
  function taskFrom(raw, index, manifestDir) {
20206
20267
  const where = `task #${index + 1}`;
@@ -20234,7 +20295,7 @@ function readManifest(path) {
20234
20295
  const abs = resolve15(path);
20235
20296
  let bytes;
20236
20297
  try {
20237
- bytes = readFileSync23(abs);
20298
+ bytes = readFileSync22(abs);
20238
20299
  } catch (e) {
20239
20300
  throw new ResearchError(`cannot read task manifest ${abs}: ${e instanceof Error ? e.message : String(e)}`);
20240
20301
  }
@@ -20516,7 +20577,52 @@ function pairRecordFrom(raw, where = "record") {
20516
20577
  }
20517
20578
  };
20518
20579
  }
20519
- var SHA_RE, SHA256_RE;
20580
+ function onlyFields(raw, allowed, where) {
20581
+ if (!isRecord(raw)) return;
20582
+ for (const key2 of Object.keys(raw)) {
20583
+ if (!allowed.includes(key2)) bad(`${where}.${key2} is not a field of the v1 pair record; refusing to carry it as portable evidence`);
20584
+ }
20585
+ }
20586
+ function portableObject(raw, spec2, where) {
20587
+ if (!isRecord(raw)) bad(`${where} is not an object`);
20588
+ for (const [key2, value] of Object.entries(raw)) {
20589
+ const field2 = spec2.get(key2);
20590
+ const at = `${where}.${key2}`;
20591
+ if (field2 === void 0) bad(`${at} is not a field of the v1 run document; refusing to carry it as portable evidence`);
20592
+ if (field2 === "scalar") {
20593
+ if (value !== null && typeof value === "object") bad(`${at} is not a scalar; refusing to carry it as portable evidence`);
20594
+ } else if ("items" in field2) {
20595
+ if (!Array.isArray(value)) bad(`${at} is not an array`);
20596
+ value.forEach((item, i) => portableObject(item, field2.items, `${at}[${i}]`));
20597
+ } else {
20598
+ portableObject(value, field2.fields, at);
20599
+ }
20600
+ }
20601
+ }
20602
+ function portablePairRecordFrom(raw, where = "record") {
20603
+ onlyFields(raw, PAIR_FIELDS, where);
20604
+ if (isRecord(raw)) {
20605
+ onlyFields(raw.adapter, ADAPTER_FIELDS, `${where}.adapter`);
20606
+ onlyFields(raw.arms, ARMS_FIELDS, `${where}.arms`);
20607
+ if (isRecord(raw.arms)) {
20608
+ for (const arm of ARMS_FIELDS) {
20609
+ const trajectory = raw.arms[arm];
20610
+ const at = `${where}.arms.${arm}`;
20611
+ onlyFields(trajectory, TRAJECTORY_FIELDS, at);
20612
+ if (isRecord(trajectory)) {
20613
+ onlyFields(trajectory.agent, AGENT_FIELDS, `${at}.agent`);
20614
+ onlyFields(trajectory.treatment, TREATMENT_FIELDS, `${at}.treatment`);
20615
+ if (isRecord(trajectory.treatment) && trajectory.treatment.envelope !== null && trajectory.treatment.envelope !== void 0) {
20616
+ portableObject(trajectory.treatment.envelope, RUN_DOCUMENT_FIELDS, `${at}.treatment.envelope`);
20617
+ }
20618
+ onlyFields(trajectory.outcome, OUTCOME_FIELDS, `${at}.outcome`);
20619
+ }
20620
+ }
20621
+ }
20622
+ }
20623
+ return pairRecordFrom(raw, where);
20624
+ }
20625
+ var SHA_RE, SHA256_RE, PAIR_FIELDS, ADAPTER_FIELDS, ARMS_FIELDS, TRAJECTORY_FIELDS, AGENT_FIELDS, TREATMENT_FIELDS, OUTCOME_FIELDS, fields, items, RUN_DOCUMENT_FIELDS;
20520
20626
  var init_record = __esm({
20521
20627
  "src/research/record.ts"() {
20522
20628
  "use strict";
@@ -20526,13 +20632,89 @@ var init_record = __esm({
20526
20632
  init_derive();
20527
20633
  SHA_RE = /^[0-9a-f]{40,64}$/;
20528
20634
  SHA256_RE = /^[0-9a-f]{64}$/;
20635
+ PAIR_FIELDS = [
20636
+ "schema_version",
20637
+ "command",
20638
+ "document",
20639
+ "task",
20640
+ "pair",
20641
+ "adapter",
20642
+ "model",
20643
+ "tamperward_version",
20644
+ "agent_argv",
20645
+ "agent_budget",
20646
+ "manifest_sha256",
20647
+ "verify_command",
20648
+ "arms"
20649
+ ];
20650
+ ADAPTER_FIELDS = ["name", "layers"];
20651
+ ARMS_FIELDS = ["ungated", "gated"];
20652
+ TRAJECTORY_FIELDS = [
20653
+ "arm",
20654
+ "workspace",
20655
+ "base",
20656
+ "head",
20657
+ "started_at",
20658
+ "finished_at",
20659
+ "agent",
20660
+ "treatment",
20661
+ "outcome",
20662
+ "released_green",
20663
+ "measured",
20664
+ "unmeasurable"
20665
+ ];
20666
+ AGENT_FIELDS = ["exit_code", "signal", "timed_out", "failure"];
20667
+ TREATMENT_FIELDS = ["verdict", "exit_code", "complete", "disposition", "envelope"];
20668
+ OUTCOME_FIELDS = [
20669
+ "verify_verdict",
20670
+ "visible_exit",
20671
+ "pristine_exit",
20672
+ "visible_green",
20673
+ "pristine_green",
20674
+ "masked_failure",
20675
+ "surviving_protected_mutations",
20676
+ "warn_findings",
20677
+ "rules",
20678
+ "honest_completion"
20679
+ ];
20680
+ fields = (entries) => ({ fields: new Map(entries) });
20681
+ items = (entries) => ({ items: new Map(entries) });
20682
+ RUN_DOCUMENT_FIELDS = /* @__PURE__ */ new Map([
20683
+ ["schema_version", "scalar"],
20684
+ ["verdict", "scalar"],
20685
+ ["exit_code", "scalar"],
20686
+ ["complete", "scalar"],
20687
+ ["base", "scalar"],
20688
+ ["head", "scalar"],
20689
+ ["reason", "scalar"],
20690
+ ["agent", fields([["exit_code", "scalar"], ["timed_out", "scalar"], ["lifecycle_owned", "scalar"], ["budget_secs", "scalar"]])],
20691
+ ["checks", fields([["diff", "scalar"], ["worktree", "scalar"], ["verify", "scalar"]])],
20692
+ ["verifier_backend", fields([
20693
+ ["kind", "scalar"],
20694
+ ["trust", "scalar"],
20695
+ ["available", "scalar"],
20696
+ ["image", "scalar"],
20697
+ ["engine", "scalar"],
20698
+ ["reason", "scalar"],
20699
+ ["resources", fields([["memory_bytes", "scalar"], ["memory_swap_bytes", "scalar"], ["cpus", "scalar"], ["pids", "scalar"]])]
20700
+ ])],
20701
+ ["dependency_environment", fields([
20702
+ ["status", "scalar"],
20703
+ ["fingerprint", "scalar"],
20704
+ ["reason", "scalar"],
20705
+ ["image", "scalar"],
20706
+ ["roots", items([["kind", "scalar"], ["path", "scalar"]])],
20707
+ ["diagnostics", fields([["full_snapshots", "scalar"], ["reused_snapshots", "scalar"], ["total_ms", "scalar"]])]
20708
+ ])],
20709
+ ["observer", fields([["enabled", "scalar"], ["blocking", "scalar"]])]
20710
+ ]);
20529
20711
  }
20530
20712
  });
20531
20713
 
20532
20714
  // src/research/run.ts
20533
20715
  import { randomUUID as randomUUID2 } from "node:crypto";
20534
20716
  import { execFileSync as execFileSync14 } from "node:child_process";
20535
- import { closeSync as closeSync3, existsSync as existsSync18, linkSync, mkdirSync as mkdirSync10, openSync as openSync3, readFileSync as readFileSync24, renameSync as renameSync4, rmSync as rmSync12, unlinkSync, writeFileSync as writeFileSync12, writeSync } from "node:fs";
20717
+ import { closeSync as closeSync4, existsSync as existsSync18, linkSync, mkdirSync as mkdirSync10, openSync as openSync4, readFileSync as readFileSync23, renameSync as renameSync4, rmSync as rmSync12, unlinkSync, writeFileSync as writeFileSync12, writeSync } from "node:fs";
20536
20718
  import { hostname } from "node:os";
20537
20719
  import { join as join26, resolve as resolve16 } from "node:path";
20538
20720
  function git7(args, cwd) {
@@ -20546,7 +20728,7 @@ function researchLockPath(ledger) {
20546
20728
  }
20547
20729
  function readLockOwner(path) {
20548
20730
  try {
20549
- const parsed = JSON.parse(readFileSync24(path, "utf8"));
20731
+ const parsed = JSON.parse(readFileSync23(path, "utf8"));
20550
20732
  if (!isRecord(parsed) || typeof parsed.pid !== "number" || !Number.isInteger(parsed.pid) || parsed.pid <= 0) return null;
20551
20733
  if (typeof parsed.host !== "string" || typeof parsed.started_at !== "string" || typeof parsed.token !== "string") return null;
20552
20734
  return { pid: parsed.pid, host: parsed.host, started_at: parsed.started_at, token: parsed.token };
@@ -20612,12 +20794,12 @@ function acquireResearchLock(ledger, breakStaleLock = false) {
20612
20794
  const text = JSON.stringify(owner) + "\n";
20613
20795
  let fd = -1;
20614
20796
  try {
20615
- fd = openSync3(path, "wx", 384);
20797
+ fd = openSync4(path, "wx", 384);
20616
20798
  writeSync(fd, text);
20617
- closeSync3(fd);
20799
+ closeSync4(fd);
20618
20800
  fd = -1;
20619
20801
  } catch (e) {
20620
- if (fd !== -1) closeSync3(fd);
20802
+ if (fd !== -1) closeSync4(fd);
20621
20803
  if (e.code === "EEXIST") {
20622
20804
  throw new ResearchError(
20623
20805
  `research output ${ledger} is already locked by ${lockHolder(path)}; refusing concurrent writers. Verify the owner is not running, then remove ${path} deliberately or rerun with --break-lock`
@@ -20631,7 +20813,7 @@ function acquireResearchLock(ledger, breakStaleLock = false) {
20631
20813
  if (released) return;
20632
20814
  released = true;
20633
20815
  try {
20634
- if (readFileSync24(path, "utf8") === text) unlinkSync(path);
20816
+ if (readFileSync23(path, "utf8") === text) unlinkSync(path);
20635
20817
  } catch {
20636
20818
  }
20637
20819
  }
@@ -20640,7 +20822,7 @@ function acquireResearchLock(ledger, breakStaleLock = false) {
20640
20822
  function resumableRecord(path, expected) {
20641
20823
  let raw;
20642
20824
  try {
20643
- raw = JSON.parse(readFileSync24(path, "utf8"));
20825
+ raw = JSON.parse(readFileSync23(path, "utf8"));
20644
20826
  } catch (e) {
20645
20827
  throw new ResearchError(`ledger record ${path} is malformed (${errorMessage(e).split("\n")[0]}); remove it to regenerate the pair, or use a new --out`);
20646
20828
  }
@@ -21061,27 +21243,95 @@ var init_run2 = __esm({
21061
21243
  }
21062
21244
  });
21063
21245
 
21064
- // src/research/summarize.ts
21065
- import { readdirSync as readdirSync7, readFileSync as readFileSync25 } from "node:fs";
21246
+ // src/research/ledger-files.ts
21247
+ import { closeSync as closeSync5, constants as constants4, fstatSync as fstatSync2, lstatSync as lstatSync11, openSync as openSync5, readFileSync as readFileSync24, readdirSync as readdirSync7, realpathSync as realpathSync11 } from "node:fs";
21066
21248
  import { join as join27, resolve as resolve17 } from "node:path";
21067
- function readLedger2(dir) {
21068
- const pairsDir = join27(resolve17(dir), "pairs");
21249
+ function lstatOrNull(path) {
21250
+ try {
21251
+ return lstatSync11(path);
21252
+ } catch {
21253
+ return null;
21254
+ }
21255
+ }
21256
+ function kindOf(st) {
21257
+ if (st.isSymbolicLink()) return "a symlink";
21258
+ if (st.isDirectory()) return "a directory";
21259
+ if (st.isFIFO()) return "a FIFO";
21260
+ if (st.isSocket()) return "a socket";
21261
+ if (st.isCharacterDevice() || st.isBlockDevice()) return "a device";
21262
+ return "not a regular file";
21263
+ }
21264
+ function readRegularFile(path, realPairsDir, name) {
21265
+ const listed = lstatOrNull(path);
21266
+ if (listed === null) throw new ResearchError(`ledger record ${path} vanished while the ledger was being read`);
21267
+ if (!listed.isFile()) {
21268
+ throw new ResearchError(`ledger record ${path} is ${kindOf(listed)}; only a regular file placed directly in pairs/ is read`);
21269
+ }
21270
+ if (listed.nlink !== 1) {
21271
+ throw new ResearchError(`ledger record ${path} has ${listed.nlink} links; a hard link can name a file outside pairs/, so only a singly linked regular file is read`);
21272
+ }
21273
+ const real = realpathSync11.native(path);
21274
+ if (real !== join27(realPairsDir, name)) {
21275
+ throw new ResearchError(`ledger record ${path} resolves outside the pairs directory (${real}); refusing to read it`);
21276
+ }
21277
+ const flags = constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0);
21278
+ let fd;
21279
+ try {
21280
+ fd = openSync5(path, flags);
21281
+ } catch (e) {
21282
+ throw new ResearchError(`cannot open ledger record ${path}: ${errorMessage(e)}`);
21283
+ }
21284
+ try {
21285
+ const opened = fstatSync2(fd);
21286
+ const sameInode = process.platform === "win32" || opened.dev === listed.dev && opened.ino === listed.ino;
21287
+ if (!opened.isFile() || !sameInode || opened.nlink !== 1) {
21288
+ throw new ResearchError(`ledger record ${path} changed between listing and reading; refusing to read it`);
21289
+ }
21290
+ return readFileSync24(fd);
21291
+ } finally {
21292
+ closeSync5(fd);
21293
+ }
21294
+ }
21295
+ function readPairFiles(ledger) {
21296
+ const root = resolve17(ledger);
21297
+ const pairsDir = join27(root, "pairs");
21298
+ const dirStat = lstatOrNull(pairsDir);
21299
+ if (dirStat !== null && dirStat.isSymbolicLink()) {
21300
+ throw new ResearchError(`ledger pairs directory ${pairsDir} is a symlink; only a real directory is read`);
21301
+ }
21302
+ if (dirStat === null || !dirStat.isDirectory()) throw new ResearchError(`ledger ${root} has no pairs directory`);
21303
+ const realPairsDir = realpathSync11.native(pairsDir);
21069
21304
  let names;
21070
21305
  try {
21071
21306
  names = readdirSync7(pairsDir).filter((n2) => n2.endsWith(".json")).sort();
21072
21307
  } catch (e) {
21073
21308
  throw new ResearchError(`cannot read ledger ${pairsDir}: ${errorMessage(e)}`);
21074
21309
  }
21075
- if (names.length === 0) throw new ResearchError(`ledger ${pairsDir} holds no pair records`);
21076
- return names.map((n2) => {
21077
- const path = join27(pairsDir, n2);
21310
+ return names.map((name) => {
21311
+ const path = join27(pairsDir, name);
21312
+ return { name, path, bytes: readRegularFile(path, realPairsDir, name) };
21313
+ });
21314
+ }
21315
+ var init_ledger_files = __esm({
21316
+ "src/research/ledger-files.ts"() {
21317
+ "use strict";
21318
+ init_narrow();
21319
+ init_adapter();
21320
+ }
21321
+ });
21322
+
21323
+ // src/research/summarize.ts
21324
+ function readLedger2(dir) {
21325
+ const files = readPairFiles(dir);
21326
+ if (files.length === 0) throw new ResearchError(`ledger ${dir} holds no pair records`);
21327
+ return files.map((f) => {
21078
21328
  let raw;
21079
21329
  try {
21080
- raw = JSON.parse(readFileSync25(path, "utf8"));
21330
+ raw = JSON.parse(f.bytes.toString("utf8"));
21081
21331
  } catch (e) {
21082
- throw new ResearchError(`ledger record ${path} is not valid JSON: ${errorMessage(e)}`);
21332
+ throw new ResearchError(`ledger record ${f.path} is not valid JSON: ${errorMessage(e)}`);
21083
21333
  }
21084
- return pairRecordFrom(raw, path);
21334
+ return pairRecordFrom(raw, f.path);
21085
21335
  });
21086
21336
  }
21087
21337
  function weakened(t) {
@@ -21202,6 +21452,7 @@ var init_summarize = __esm({
21202
21452
  "use strict";
21203
21453
  init_machine_output();
21204
21454
  init_narrow();
21455
+ init_ledger_files();
21205
21456
  init_adapter();
21206
21457
  init_record();
21207
21458
  }
@@ -21209,7 +21460,7 @@ var init_summarize = __esm({
21209
21460
 
21210
21461
  // src/research/init.ts
21211
21462
  import { createHash as createHash15 } from "node:crypto";
21212
- import { existsSync as existsSync19, mkdirSync as mkdirSync11, readFileSync as readFileSync26, writeFileSync as writeFileSync13 } from "node:fs";
21463
+ import { existsSync as existsSync19, mkdirSync as mkdirSync11, readFileSync as readFileSync25, writeFileSync as writeFileSync13 } from "node:fs";
21213
21464
  import { basename as basename5, dirname as dirname12, resolve as resolve18 } from "node:path";
21214
21465
  function idFrom(repo) {
21215
21466
  const clean = repo.replace(/[\\/]$/, "").split(/[\\/]/).pop() || "task";
@@ -21261,7 +21512,7 @@ function runResearchInit(opts) {
21261
21512
  }
21262
21513
  }
21263
21514
  function requireHash(path) {
21264
- return createHash15("sha256").update(readFileSync26(path)).digest("hex");
21515
+ return createHash15("sha256").update(readFileSync25(path)).digest("hex");
21265
21516
  }
21266
21517
  var init_init2 = __esm({
21267
21518
  "src/research/init.ts"() {
@@ -21328,63 +21579,105 @@ var init_report2 = __esm({
21328
21579
  // src/research/bundle.ts
21329
21580
  import { createHash as createHash16 } from "node:crypto";
21330
21581
  import { gzipSync, gunzipSync } from "node:zlib";
21331
- import { existsSync as existsSync20, mkdirSync as mkdirSync12, readdirSync as readdirSync8, readFileSync as readFileSync27, statSync as statSync6, writeFileSync as writeFileSync14 } from "node:fs";
21332
- import { dirname as dirname13, join as join28, resolve as resolve19 } from "node:path";
21582
+ import { mkdirSync as mkdirSync12, readFileSync as readFileSync26, writeFileSync as writeFileSync14 } from "node:fs";
21583
+ import { dirname as dirname13, resolve as resolve19 } from "node:path";
21333
21584
  function octal(value, width) {
21334
21585
  return value.toString(8).padStart(width - 1, "0") + "\0";
21335
21586
  }
21336
- function tarEntry(name, bytes) {
21587
+ function tarHeader(name, size) {
21337
21588
  const header = Buffer.alloc(BLOCK, 0);
21338
- const safe = name.replace(/^\/+/, "").slice(0, 99);
21339
- header.write(safe, 0, "utf8");
21589
+ header.write(name, 0, "utf8");
21340
21590
  header.write(octal(420, 8), 100, "ascii");
21341
21591
  header.write(octal(0, 8), 108, "ascii");
21342
21592
  header.write(octal(0, 8), 116, "ascii");
21343
- header.write(octal(bytes.length, 12), 124, "ascii");
21593
+ header.write(octal(size, 12), 124, "ascii");
21344
21594
  header.write(octal(0, 12), 136, "ascii");
21345
21595
  header.fill(32, 148, 156);
21346
- header[156] = 48;
21596
+ header[TYPE_OFFSET] = 48;
21347
21597
  header.write("ustar", 257, "ascii");
21348
21598
  header.write("00", 263, "ascii");
21349
21599
  const checksum = header.reduce((sum, b) => sum + b, 0);
21350
21600
  header.write(octal(checksum, 8), 148, "ascii");
21601
+ return header;
21602
+ }
21603
+ function tarEntry(name, bytes) {
21604
+ if (name.startsWith("/") || Buffer.byteLength(name, "utf8") > TAR_NAME_MAX) {
21605
+ throw new ResearchError(`archive entry name ${name} cannot be stored byte for byte in a ustar header`);
21606
+ }
21351
21607
  const padding = Buffer.alloc((BLOCK - bytes.length % BLOCK) % BLOCK, 0);
21352
- return Buffer.concat([header, bytes, padding]);
21608
+ return Buffer.concat([tarHeader(name, bytes.length), bytes, padding]);
21353
21609
  }
21354
21610
  function makeTar(entries) {
21355
21611
  return Buffer.concat([...entries.map((e) => tarEntry(e.name, e.bytes)), Buffer.alloc(BLOCK * 2, 0)]);
21356
21612
  }
21613
+ function field(header, start, length) {
21614
+ const raw = header.subarray(start, start + length);
21615
+ const end = raw.indexOf(0);
21616
+ return raw.subarray(0, end === -1 ? raw.length : end).toString("utf8");
21617
+ }
21357
21618
  function parseTar(bytes) {
21358
21619
  const files = /* @__PURE__ */ new Map();
21359
- for (let offset = 0; offset + BLOCK <= bytes.length; ) {
21620
+ let offset = 0;
21621
+ for (; ; ) {
21622
+ if (offset + BLOCK > bytes.length) throw new ResearchError("research bundle is missing its end-of-archive blocks");
21360
21623
  const header = bytes.subarray(offset, offset + BLOCK);
21361
- if (header.every((b) => b === 0)) break;
21362
- const name = header.subarray(0, 100).toString("utf8").replace(/\0.*$/, "");
21363
- const sizeText = header.subarray(124, 136).toString("ascii").replace(/\0.*$/, "").trim();
21364
- const size = Number.parseInt(sizeText || "0", 8);
21624
+ if (header.every((b) => b === 0)) {
21625
+ const second = bytes.subarray(offset + BLOCK, offset + 2 * BLOCK);
21626
+ if (second.length !== BLOCK || !second.every((b) => b === 0) || offset + 2 * BLOCK !== bytes.length) {
21627
+ throw new ResearchError("research bundle carries bytes after its end-of-archive blocks");
21628
+ }
21629
+ return files;
21630
+ }
21631
+ const name = field(header, 0, NAME_FIELD);
21632
+ const prefix = field(header, PREFIX_OFFSET, PREFIX_FIELD);
21633
+ if (prefix) {
21634
+ throw new ResearchError(`research bundle entry ${prefix}/${name} uses a ustar prefix; this bundle format stores every name in the name field`);
21635
+ }
21636
+ const type = header[TYPE_OFFSET];
21637
+ if (type !== 48) {
21638
+ throw new ResearchError(`research bundle entry ${name} is not a regular file entry (type flag ${JSON.stringify(String.fromCharCode(type))})`);
21639
+ }
21640
+ const size = Number.parseInt(field(header, 124, 12).trim() || "0", 8);
21365
21641
  if (!name || name.startsWith("/") || name.split("/").includes("..") || !Number.isSafeInteger(size) || size < 0) {
21366
21642
  throw new ResearchError("research bundle contains an unsafe or malformed archive entry");
21367
21643
  }
21644
+ if (Buffer.byteLength(name, "utf8") > TAR_NAME_MAX || !header.equals(tarHeader(name, size))) {
21645
+ throw new ResearchError(`research bundle entry ${name} has a header this bundle format does not write`);
21646
+ }
21368
21647
  const start = offset + BLOCK;
21369
21648
  const end = start + size;
21370
21649
  if (end > bytes.length) throw new ResearchError("research bundle is truncated");
21650
+ const padded = start + Math.ceil(size / BLOCK) * BLOCK;
21651
+ if (padded > bytes.length || !bytes.subarray(end, padded).every((b) => b === 0)) {
21652
+ throw new ResearchError(`research bundle entry ${name} carries bytes in its padding`);
21653
+ }
21654
+ if (files.has(name)) throw new ResearchError(`research bundle contains a duplicate archive entry ${name}`);
21371
21655
  files.set(name, Buffer.from(bytes.subarray(start, end)));
21372
- offset = start + Math.ceil(size / BLOCK) * BLOCK;
21656
+ offset = padded;
21373
21657
  }
21374
- return files;
21375
21658
  }
21376
- function walkLedger(ledger) {
21659
+ function packagedRecords(ledger) {
21660
+ const files = readPairFiles(ledger);
21661
+ if (files.length === 0) throw new ResearchError(`ledger ${ledger} holds no pair records`);
21662
+ const records = [];
21377
21663
  const entries = [];
21378
- const pairs = join28(ledger, "pairs");
21379
- if (!existsSync20(pairs) || !statSync6(pairs).isDirectory()) throw new ResearchError(`ledger ${ledger} has no pairs directory`);
21380
- const names = readdirSync8(pairs).filter((n2) => n2.endsWith(".json")).sort();
21381
- if (names.length === 0) throw new ResearchError(`ledger ${ledger} holds no pair records`);
21382
- for (const name of names) {
21383
- const path = join28(pairs, name);
21384
- if (!statSync6(path).isFile()) continue;
21385
- entries.push({ name: `ledger/pairs/${name}`, bytes: readFileSync27(path) });
21664
+ for (const f of files) {
21665
+ let raw;
21666
+ try {
21667
+ raw = JSON.parse(f.bytes.toString("utf8"));
21668
+ } catch (e) {
21669
+ throw new ResearchError(`ledger record ${f.path} is not valid JSON: ${e instanceof Error ? e.message : String(e)}`);
21670
+ }
21671
+ const record = portablePairRecordFrom(raw, f.path);
21672
+ const archiveName = `ledger/pairs/${f.name}`;
21673
+ const nameBytes = Buffer.byteLength(archiveName, "utf8");
21674
+ if (nameBytes > TAR_NAME_MAX) {
21675
+ throw new ResearchError(`pair record name ${f.name} is too long for the archive (${nameBytes} bytes as ${archiveName}; the limit is ${TAR_NAME_MAX})`);
21676
+ }
21677
+ records.push(record);
21678
+ entries.push({ name: archiveName, bytes: jsonBytes(record) });
21386
21679
  }
21387
- return entries;
21680
+ return { records, entries };
21388
21681
  }
21389
21682
  function jsonBytes(value) {
21390
21683
  return Buffer.from(JSON.stringify(value, null, 2) + "\n", "utf8");
@@ -21392,10 +21685,15 @@ function jsonBytes(value) {
21392
21685
  function validateResearchBundle(path) {
21393
21686
  let files;
21394
21687
  try {
21395
- files = parseTar(gunzipSync(readFileSync27(resolve19(path))));
21688
+ files = parseTar(gunzipSync(readFileSync26(resolve19(path))));
21396
21689
  } catch (e) {
21397
21690
  throw new ResearchError(`cannot read research bundle ${path}: ${e instanceof Error ? e.message : String(e)}`);
21398
21691
  }
21692
+ for (const name of files.keys()) {
21693
+ if (!TOP_LEVEL_ENTRIES.has(name) && !PAIR_ENTRY_RE.test(name)) {
21694
+ throw new ResearchError(`research bundle carries an entry that is not evidence: ${name}`);
21695
+ }
21696
+ }
21399
21697
  const provenance = files.get("provenance.json");
21400
21698
  const summaryBytes = files.get("summary.json");
21401
21699
  const report2 = files.get("report.txt");
@@ -21422,14 +21720,16 @@ function validateResearchBundle(path) {
21422
21720
  if (parsedSummary.document !== "summary" || parsedSummary.command !== "research") throw new ResearchError("research bundle summary is not a research summary");
21423
21721
  const records = [];
21424
21722
  for (const [name, bytes] of files) {
21425
- if (!name.startsWith("ledger/pairs/") || !name.endsWith(".json")) continue;
21723
+ if (!PAIR_ENTRY_RE.test(name)) continue;
21426
21724
  let raw;
21427
21725
  try {
21428
21726
  raw = JSON.parse(bytes.toString("utf8"));
21429
21727
  } catch {
21430
21728
  throw new ResearchError(`invalid JSON in ${name}`);
21431
21729
  }
21432
- records.push(pairRecordFrom(raw, name));
21730
+ const record = portablePairRecordFrom(raw, name);
21731
+ if (!bytes.equals(jsonBytes(record))) throw new ResearchError(`${name} is not the canonical serialization of its pair record`);
21732
+ records.push(record);
21433
21733
  }
21434
21734
  if (records.length === 0) throw new ResearchError("research bundle contains no pair records");
21435
21735
  const derived = summarizeRecords(records);
@@ -21443,10 +21743,10 @@ function createResearchBundle(opts) {
21443
21743
  if (!opts.ledger) throw new ResearchError("research bundle requires --ledger");
21444
21744
  if (!opts.out) throw new ResearchError("research bundle requires --out");
21445
21745
  const ledger = resolve19(opts.ledger);
21446
- const summary = summarizeLedger(ledger);
21447
- const entries = walkLedger(ledger);
21746
+ const { records, entries } = packagedRecords(ledger);
21747
+ const summary = summarizeRecords(records);
21448
21748
  if (opts.manifest) {
21449
- const manifest = readFileSync27(resolve19(opts.manifest));
21749
+ const manifest = readFileSync26(resolve19(opts.manifest));
21450
21750
  const sha3 = createHash16("sha256").update(manifest).digest("hex");
21451
21751
  if (sha3 !== summary.manifest_sha256) throw new ResearchError(`manifest sha256 ${sha3} does not match ledger ${summary.manifest_sha256}`);
21452
21752
  entries.push({ name: "manifest.json", bytes: manifest });
@@ -21494,17 +21794,25 @@ function runResearchBundle(opts) {
21494
21794
  throw e;
21495
21795
  }
21496
21796
  }
21497
- var BLOCK, BUNDLE_VERSION;
21797
+ var BLOCK, BUNDLE_VERSION, TOP_LEVEL_ENTRIES, PAIR_ENTRY_RE, TAR_NAME_MAX, NAME_FIELD, TYPE_OFFSET, PREFIX_OFFSET, PREFIX_FIELD;
21498
21798
  var init_bundle = __esm({
21499
21799
  "src/research/bundle.ts"() {
21500
21800
  "use strict";
21501
21801
  init_machine_output();
21502
21802
  init_adapter();
21803
+ init_ledger_files();
21503
21804
  init_record();
21504
21805
  init_report2();
21505
21806
  init_summarize();
21506
21807
  BLOCK = 512;
21507
21808
  BUNDLE_VERSION = 1;
21809
+ TOP_LEVEL_ENTRIES = /* @__PURE__ */ new Set(["summary.json", "report.txt", "provenance.json", "manifest.json"]);
21810
+ PAIR_ENTRY_RE = /^ledger\/pairs\/[^/]+\.json$/;
21811
+ TAR_NAME_MAX = 99;
21812
+ NAME_FIELD = 100;
21813
+ TYPE_OFFSET = 156;
21814
+ PREFIX_OFFSET = 345;
21815
+ PREFIX_FIELD = 155;
21508
21816
  }
21509
21817
  });
21510
21818
 
@@ -22474,7 +22782,7 @@ var init_adapter4 = __esm({
22474
22782
 
22475
22783
  // src/adapters/copilot-sdk/changes.ts
22476
22784
  import { execFileSync as execFileSync15 } from "node:child_process";
22477
- import { mkdtempSync as mkdtempSync7, writeFileSync as writeFileSync15, readFileSync as readFileSync28, rmSync as rmSync13, existsSync as existsSync21, realpathSync as realpathSync11 } from "node:fs";
22785
+ import { mkdtempSync as mkdtempSync7, writeFileSync as writeFileSync15, readFileSync as readFileSync27, rmSync as rmSync13, existsSync as existsSync20, realpathSync as realpathSync12 } from "node:fs";
22478
22786
  import { tmpdir as tmpdir8 } from "node:os";
22479
22787
  import { basename as basename6, dirname as dirname14, isAbsolute as isAbsolute17, relative as relative12, resolve as resolve22, join as join29 } from "node:path";
22480
22788
  function asStr4(v) {
@@ -22487,13 +22795,13 @@ function relForDisplay5(path, cwd) {
22487
22795
  function canonicalContainedTarget(abs, root) {
22488
22796
  let realRoot;
22489
22797
  try {
22490
- realRoot = realpathSync11(root);
22798
+ realRoot = realpathSync12(root);
22491
22799
  } catch (e) {
22492
22800
  throw new Error(`trusted repository root ${root} cannot be resolved: ${e instanceof Error ? e.message : String(e)}`);
22493
22801
  }
22494
22802
  let dir = abs;
22495
22803
  const tail = [];
22496
- while (!existsSync21(dir)) {
22804
+ while (!existsSync20(dir)) {
22497
22805
  const parent = dirname14(dir);
22498
22806
  if (parent === dir) break;
22499
22807
  tail.unshift(basename6(dir));
@@ -22501,7 +22809,7 @@ function canonicalContainedTarget(abs, root) {
22501
22809
  }
22502
22810
  let realDir;
22503
22811
  try {
22504
- realDir = realpathSync11(dir);
22812
+ realDir = realpathSync12(dir);
22505
22813
  } catch (e) {
22506
22814
  throw new Error(`write target ${abs} cannot be resolved: ${e instanceof Error ? e.message : String(e)}`);
22507
22815
  }
@@ -22568,7 +22876,7 @@ function reconstructAfterViaGit(rawDiff, display2, before, isCreate) {
22568
22876
  if (statPath !== TARGET) throw new Error(`write diff reconstructs a different file (${statPath}) than the bound target`);
22569
22877
  git8(["--check"]);
22570
22878
  git8([]);
22571
- return op === "delete" ? null : readFileSync28(targetPath, "utf8");
22879
+ return op === "delete" ? null : readFileSync27(targetPath, "utf8");
22572
22880
  } catch (e) {
22573
22881
  throw new Error(`could not reconstruct the write to ${display2} from its diff via git apply: ${e instanceof Error ? e.message : String(e)}`);
22574
22882
  } finally {
@@ -23312,7 +23620,7 @@ var init_runtime_qualification = __esm({
23312
23620
 
23313
23621
  // src/cli/runtime.ts
23314
23622
  import { execFileSync as execFileSync16 } from "node:child_process";
23315
- import { existsSync as existsSync22, mkdirSync as mkdirSync13, readFileSync as readFileSync29, writeFileSync as writeFileSync16 } from "node:fs";
23623
+ import { existsSync as existsSync21, mkdirSync as mkdirSync13, readFileSync as readFileSync28, writeFileSync as writeFileSync16 } from "node:fs";
23316
23624
  import { join as join30, dirname as dirname15 } from "node:path";
23317
23625
  function storePath(cwd) {
23318
23626
  const dir = gitDir(cwd);
@@ -23320,9 +23628,9 @@ function storePath(cwd) {
23320
23628
  }
23321
23629
  function readStore(cwd) {
23322
23630
  const path = storePath(cwd);
23323
- if (!path || !existsSync22(path)) return null;
23631
+ if (!path || !existsSync21(path)) return null;
23324
23632
  try {
23325
- const parsed = JSON.parse(readFileSync29(path, "utf8"));
23633
+ const parsed = JSON.parse(readFileSync28(path, "utf8"));
23326
23634
  if (parsed && typeof parsed === "object" && parsed.records && typeof parsed.records === "object") return parsed;
23327
23635
  } catch {
23328
23636
  return null;
@@ -23482,9 +23790,9 @@ function resolveHookConfigHash(canonicalId, cwd) {
23482
23790
  "github-copilot-cli": join30(cwd, ".github", "hooks", "tamperward.json")
23483
23791
  };
23484
23792
  const file = files[canonicalId];
23485
- if (!file || !existsSync22(file)) return null;
23793
+ if (!file || !existsSync21(file)) return null;
23486
23794
  try {
23487
- const raw = readFileSync29(file, "utf8");
23795
+ const raw = readFileSync28(file, "utf8");
23488
23796
  let hooks = raw;
23489
23797
  try {
23490
23798
  const parsed = JSON.parse(raw);
@@ -24490,7 +24798,7 @@ var init_main = __esm({
24490
24798
  // src/cli/index.ts
24491
24799
  init_hook_client();
24492
24800
  init_exit();
24493
- import { readFileSync as readFileSync30 } from "node:fs";
24801
+ import { readFileSync as readFileSync29 } from "node:fs";
24494
24802
  function loadMain() {
24495
24803
  return Promise.resolve().then(() => (init_main(), main_exports));
24496
24804
  }
@@ -24503,7 +24811,7 @@ async function launch(argv) {
24503
24811
  if (kind && hookServiceEnabled()) {
24504
24812
  let raw = null;
24505
24813
  try {
24506
- raw = readFileSync30(0, "utf8");
24814
+ raw = readFileSync29(0, "utf8");
24507
24815
  } catch {
24508
24816
  raw = null;
24509
24817
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tamperward",
3
- "version": "2.37.0",
3
+ "version": "2.37.2",
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",