tamperward 2.29.14 → 2.29.16
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.
- package/dist/cli/index.js +187 -52
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -1381,6 +1381,38 @@ import { execFileSync as execFileSync4 } from "node:child_process";
|
|
|
1381
1381
|
import { writeFileSync, mkdtempSync, rmSync } from "node:fs";
|
|
1382
1382
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
1383
1383
|
import { isAbsolute as isAbsolute4, join as join5, relative, resolve as resolve2 } from "node:path";
|
|
1384
|
+
function sideSize(s) {
|
|
1385
|
+
if (s == null) return { bytes: 0, lines: 0 };
|
|
1386
|
+
let lines = 1;
|
|
1387
|
+
for (let i = 0; i < s.length; i++) if (s.charCodeAt(i) === 10) lines++;
|
|
1388
|
+
return { bytes: Buffer.byteLength(s), lines };
|
|
1389
|
+
}
|
|
1390
|
+
function overReconstructCeiling(before, after) {
|
|
1391
|
+
const maxBytes = RECONSTRUCT_MAX_BYTES();
|
|
1392
|
+
const maxLines = RECONSTRUCT_MAX_LINES();
|
|
1393
|
+
for (const [label, side] of [["before", before], ["after", after]]) {
|
|
1394
|
+
const { bytes, lines } = sideSize(side);
|
|
1395
|
+
if (bytes > maxBytes) return `${label} is ${bytes} bytes (over the ${maxBytes}-byte reconstruction budget)`;
|
|
1396
|
+
if (lines > maxLines) return `${label} is ${lines} lines (over the ${maxLines}-line reconstruction budget)`;
|
|
1397
|
+
}
|
|
1398
|
+
return null;
|
|
1399
|
+
}
|
|
1400
|
+
function linearFallbackHunks(before, after) {
|
|
1401
|
+
const b = before == null ? [] : before.split("\n");
|
|
1402
|
+
const a = after == null ? [] : after.split("\n");
|
|
1403
|
+
let p = 0;
|
|
1404
|
+
while (p < b.length && p < a.length && b[p] === a[p]) p++;
|
|
1405
|
+
let s = 0;
|
|
1406
|
+
while (s < b.length - p && s < a.length - p && b[b.length - 1 - s] === a[a.length - 1 - s]) s++;
|
|
1407
|
+
const delContent = b.slice(p, b.length - s);
|
|
1408
|
+
const addContent = a.slice(p, a.length - s);
|
|
1409
|
+
if (delContent.length === 0 && addContent.length === 0) return [];
|
|
1410
|
+
const lines = [
|
|
1411
|
+
...delContent.map((content, i) => ({ type: "del", content, oldLine: p + 1 + i, newLine: null })),
|
|
1412
|
+
...addContent.map((content, i) => ({ type: "add", content, oldLine: null, newLine: p + 1 + i }))
|
|
1413
|
+
];
|
|
1414
|
+
return [{ oldStart: p + 1, oldLines: delContent.length, newStart: p + 1, newLines: addContent.length, lines }];
|
|
1415
|
+
}
|
|
1384
1416
|
function asStr(v) {
|
|
1385
1417
|
return typeof v === "string" ? v : "";
|
|
1386
1418
|
}
|
|
@@ -1402,31 +1434,39 @@ function synthFileChange(displayPath, before, after) {
|
|
|
1402
1434
|
if (before === null) op = "add";
|
|
1403
1435
|
else if (after === null) op = "delete";
|
|
1404
1436
|
else op = "modify";
|
|
1437
|
+
const over = overReconstructCeiling(before, after);
|
|
1438
|
+
if (over) {
|
|
1439
|
+
throw new Error(`cannot safely reconstruct the incoming edit to ${displayPath} within the hook budget: ${over}`);
|
|
1440
|
+
}
|
|
1441
|
+
const mk = (hunks) => [
|
|
1442
|
+
{ kind: "file", path: displayPath, oldPath: null, op, before, after, binary: false, hunks }
|
|
1443
|
+
];
|
|
1405
1444
|
const dir = mkdtempSync(join5(tmpdir2(), "hf-"));
|
|
1406
|
-
let raw = "";
|
|
1407
1445
|
try {
|
|
1408
1446
|
const a = join5(dir, "a");
|
|
1409
1447
|
const b = join5(dir, "b");
|
|
1410
1448
|
writeFileSync(a, before ?? "");
|
|
1411
1449
|
writeFileSync(b, after ?? "");
|
|
1412
1450
|
try {
|
|
1413
|
-
raw = execFileSync4("git", ["diff", "--no-index", "--no-color", "--text", a, b], {
|
|
1451
|
+
const raw = execFileSync4("git", ["diff", "--no-index", "--no-color", "--text", a, b], {
|
|
1414
1452
|
encoding: "utf8",
|
|
1415
|
-
maxBuffer:
|
|
1453
|
+
maxBuffer: RECONSTRUCT_MAXBUFFER(),
|
|
1454
|
+
timeout: RECONSTRUCT_TIMEOUT_MS(),
|
|
1455
|
+
killSignal: "SIGKILL"
|
|
1416
1456
|
});
|
|
1457
|
+
const first = parseDiff(raw)[0];
|
|
1458
|
+
return mk(first && first.kind === "file" ? first.hunks : []);
|
|
1417
1459
|
} catch (e) {
|
|
1418
1460
|
const failure = execFailure(e);
|
|
1419
|
-
if (failure.status
|
|
1420
|
-
|
|
1461
|
+
if (failure.status === 1) {
|
|
1462
|
+
const first = parseDiff(failure.stdout)[0];
|
|
1463
|
+
return mk(first && first.kind === "file" ? first.hunks : []);
|
|
1421
1464
|
}
|
|
1422
|
-
|
|
1465
|
+
return mk(linearFallbackHunks(before, after));
|
|
1423
1466
|
}
|
|
1424
1467
|
} finally {
|
|
1425
1468
|
rmSync(dir, { recursive: true, force: true });
|
|
1426
1469
|
}
|
|
1427
|
-
const first = parseDiff(raw)[0];
|
|
1428
|
-
const hunks = first && first.kind === "file" ? first.hunks : [];
|
|
1429
|
-
return [{ kind: "file", path: displayPath, oldPath: null, op, before, after, binary: false, hunks }];
|
|
1430
1470
|
}
|
|
1431
1471
|
function changesFromClaudeHook(input, cwd, base = cwd) {
|
|
1432
1472
|
const ti = input.tool_input ?? {};
|
|
@@ -1471,12 +1511,22 @@ function changesFromClaudeHook(input, cwd, base = cwd) {
|
|
|
1471
1511
|
return [];
|
|
1472
1512
|
}
|
|
1473
1513
|
}
|
|
1514
|
+
var num, RECONSTRUCT_TIMEOUT_MS, RECONSTRUCT_MAXBUFFER, RECONSTRUCT_MAX_BYTES, RECONSTRUCT_MAX_LINES;
|
|
1474
1515
|
var init_changes = __esm({
|
|
1475
1516
|
"src/adapters/claude/changes.ts"() {
|
|
1476
1517
|
"use strict";
|
|
1477
1518
|
init_parse();
|
|
1478
1519
|
init_disk();
|
|
1479
1520
|
init_narrow();
|
|
1521
|
+
num = (name, fallback) => {
|
|
1522
|
+
const raw = process.env[name];
|
|
1523
|
+
const n = raw === void 0 ? NaN : Number(raw);
|
|
1524
|
+
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
1525
|
+
};
|
|
1526
|
+
RECONSTRUCT_TIMEOUT_MS = () => num("TAMPERWARD_RECONSTRUCT_TIMEOUT_MS", 5e3);
|
|
1527
|
+
RECONSTRUCT_MAXBUFFER = () => num("TAMPERWARD_RECONSTRUCT_MAXBUFFER", 32 * 1024 * 1024);
|
|
1528
|
+
RECONSTRUCT_MAX_BYTES = () => num("TAMPERWARD_RECONSTRUCT_MAX_BYTES", 384 * 1024);
|
|
1529
|
+
RECONSTRUCT_MAX_LINES = () => num("TAMPERWARD_RECONSTRUCT_MAX_LINES", 4e3);
|
|
1480
1530
|
}
|
|
1481
1531
|
});
|
|
1482
1532
|
|
|
@@ -3337,6 +3387,7 @@ var init_no_verify = __esm({
|
|
|
3337
3387
|
|
|
3338
3388
|
// src/ts-lazy.ts
|
|
3339
3389
|
import { createRequire as createRequire2 } from "node:module";
|
|
3390
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
3340
3391
|
function loadTs() {
|
|
3341
3392
|
if (loaded === null) {
|
|
3342
3393
|
const required = createRequire2(import.meta.url)("typescript");
|
|
@@ -3357,17 +3408,37 @@ function nestsWithinCeiling(src) {
|
|
|
3357
3408
|
return true;
|
|
3358
3409
|
}
|
|
3359
3410
|
function parseSource(fileName, src, kind) {
|
|
3360
|
-
|
|
3361
|
-
if (
|
|
3362
|
-
const
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3411
|
+
const bytes = Buffer.byteLength(src);
|
|
3412
|
+
if (bytes > MAX_PARSE_BYTES) return null;
|
|
3413
|
+
const key2 = `${kind ?? ""}\0${fileName}\0${createHash2("sha256").update(src).digest("hex")}`;
|
|
3414
|
+
const at = parseCache.findIndex((e) => e.key === key2);
|
|
3415
|
+
if (at !== -1) {
|
|
3416
|
+
const [entry] = parseCache.splice(at, 1);
|
|
3417
|
+
parseCache.push(entry);
|
|
3418
|
+
return entry.sf;
|
|
3419
|
+
}
|
|
3420
|
+
let sf;
|
|
3421
|
+
if (!nestsWithinCeiling(src)) {
|
|
3422
|
+
sf = null;
|
|
3423
|
+
} else {
|
|
3424
|
+
const t = loadTs();
|
|
3425
|
+
try {
|
|
3426
|
+
sf = t.createSourceFile(fileName, src, t.ScriptTarget.Latest, true, kind ?? t.ScriptKind.TS);
|
|
3427
|
+
} catch (e) {
|
|
3428
|
+
if (e instanceof RangeError) sf = null;
|
|
3429
|
+
else throw e;
|
|
3430
|
+
}
|
|
3368
3431
|
}
|
|
3432
|
+
parseCache.push({ key: key2, sf, bytes });
|
|
3433
|
+
parseCacheBytes += bytes;
|
|
3434
|
+
while (parseCache.length > PARSE_CACHE_MAX_ENTRIES || parseCacheBytes > PARSE_CACHE_MAX_BYTES) {
|
|
3435
|
+
const evicted = parseCache.shift();
|
|
3436
|
+
if (!evicted) break;
|
|
3437
|
+
parseCacheBytes -= evicted.bytes;
|
|
3438
|
+
}
|
|
3439
|
+
return sf;
|
|
3369
3440
|
}
|
|
3370
|
-
var loaded, handler, ts, MAX_PARSE_BYTES, MAX_PARSE_DEPTH;
|
|
3441
|
+
var loaded, handler, ts, MAX_PARSE_BYTES, MAX_PARSE_DEPTH, PARSE_CACHE_MAX_ENTRIES, PARSE_CACHE_MAX_BYTES, parseCache, parseCacheBytes;
|
|
3371
3442
|
var init_ts_lazy = __esm({
|
|
3372
3443
|
"src/ts-lazy.ts"() {
|
|
3373
3444
|
"use strict";
|
|
@@ -3390,6 +3461,10 @@ var init_ts_lazy = __esm({
|
|
|
3390
3461
|
ts = new Proxy(/* @__PURE__ */ Object.create(null), handler);
|
|
3391
3462
|
MAX_PARSE_BYTES = 4 * 1024 * 1024;
|
|
3392
3463
|
MAX_PARSE_DEPTH = 256;
|
|
3464
|
+
PARSE_CACHE_MAX_ENTRIES = 32;
|
|
3465
|
+
PARSE_CACHE_MAX_BYTES = 24 * 1024 * 1024;
|
|
3466
|
+
parseCache = [];
|
|
3467
|
+
parseCacheBytes = 0;
|
|
3393
3468
|
}
|
|
3394
3469
|
});
|
|
3395
3470
|
|
|
@@ -6903,10 +6978,10 @@ function lex(src) {
|
|
|
6903
6978
|
i = j + 1;
|
|
6904
6979
|
continue;
|
|
6905
6980
|
}
|
|
6906
|
-
const
|
|
6907
|
-
if (
|
|
6908
|
-
out3.push({ t: "num", v:
|
|
6909
|
-
i +=
|
|
6981
|
+
const num2 = src.slice(i).match(/^-?\d+(?:\.\d+)?/);
|
|
6982
|
+
if (num2) {
|
|
6983
|
+
out3.push({ t: "num", v: num2[0] });
|
|
6984
|
+
i += num2[0].length;
|
|
6910
6985
|
continue;
|
|
6911
6986
|
}
|
|
6912
6987
|
const op = src.slice(i).match(/^(?:==|!=|&&|\|\||<=|>=|[!()<>,[\]])/);
|
|
@@ -6931,13 +7006,13 @@ function foldConst(src) {
|
|
|
6931
7006
|
let p = 0;
|
|
6932
7007
|
const peek = () => toks[p];
|
|
6933
7008
|
const eat = (v2) => toks[p]?.t === "op" && toks[p].v === v2 ? (p++, true) : false;
|
|
6934
|
-
const
|
|
7009
|
+
const num2 = (v2) => v2 === TRUTHY ? NaN : typeof v2 === "number" ? v2 : typeof v2 === "boolean" ? v2 ? 1 : 0 : v2 === null ? 0 : v2.trim() === "" ? 0 : Number(v2);
|
|
6935
7010
|
const eq = (a, b) => {
|
|
6936
7011
|
if (a === TRUTHY || b === TRUTHY) return void 0;
|
|
6937
7012
|
if (typeof a === "string" && typeof b === "string") return a.toLowerCase() === b.toLowerCase();
|
|
6938
7013
|
if (a === null && b === null) return true;
|
|
6939
|
-
const x =
|
|
6940
|
-
const y2 =
|
|
7014
|
+
const x = num2(a);
|
|
7015
|
+
const y2 = num2(b);
|
|
6941
7016
|
return !Number.isNaN(x) && x === y2;
|
|
6942
7017
|
};
|
|
6943
7018
|
const or = () => {
|
|
@@ -6975,8 +7050,8 @@ function foldConst(src) {
|
|
|
6975
7050
|
const e = eq(l, r);
|
|
6976
7051
|
l = e === void 0 ? void 0 : t.v === "==" ? e : !e;
|
|
6977
7052
|
} else {
|
|
6978
|
-
const x =
|
|
6979
|
-
const y2 =
|
|
7053
|
+
const x = num2(l);
|
|
7054
|
+
const y2 = num2(r);
|
|
6980
7055
|
if (Number.isNaN(x) || Number.isNaN(y2)) l = void 0;
|
|
6981
7056
|
else l = t.v === "<" ? x < y2 : t.v === ">" ? x > y2 : t.v === "<=" ? x <= y2 : x >= y2;
|
|
6982
7057
|
}
|
|
@@ -9914,6 +9989,14 @@ var init_test_content_removal = __esm({
|
|
|
9914
9989
|
KeptPool = class {
|
|
9915
9990
|
text = "";
|
|
9916
9991
|
inLiteral = [];
|
|
9992
|
+
// O(1) fast path (#517): the ws-text of each kept significant line whose retained
|
|
9993
|
+
// text BEGINS at a code position (inLiteral 0). A removed line whose ws-text equals
|
|
9994
|
+
// one of these is kept — necessarily the SAME answer `indexOf` gives, since that exact
|
|
9995
|
+
// text then occurs in `text` at a code position — but in O(1) instead of scanning the
|
|
9996
|
+
// whole concatenated pool per removed line (a 20k-line high-churn spec was O(n²) here,
|
|
9997
|
+
// ~13s, #517). The substring scan below is unchanged and still covers rewrapped/joined
|
|
9998
|
+
// lines and moved fixtures, so `keeps` returns exactly what it did before.
|
|
9999
|
+
wholeCodeLines = /* @__PURE__ */ new Set();
|
|
9917
10000
|
addLive(src, path) {
|
|
9918
10001
|
const p = partition(src, path);
|
|
9919
10002
|
const lang = langOf(path);
|
|
@@ -9922,11 +10005,16 @@ var init_test_content_removal = __esm({
|
|
|
9922
10005
|
if (i < p.live.length && p.live[i] !== "\n") continue;
|
|
9923
10006
|
const line = p.live.slice(lineStart, i);
|
|
9924
10007
|
if (isSignificantLine(line.trim(), lang)) {
|
|
10008
|
+
let lineWs = "";
|
|
10009
|
+
let startsAtCode = false;
|
|
9925
10010
|
for (let j = lineStart; j < i; j++) {
|
|
9926
10011
|
if (/\s/.test(p.live[j])) continue;
|
|
10012
|
+
if (lineWs.length === 0) startsAtCode = p.inLiteral[j] === 0;
|
|
9927
10013
|
this.text += p.live[j];
|
|
9928
10014
|
this.inLiteral.push(p.inLiteral[j]);
|
|
10015
|
+
lineWs += p.live[j];
|
|
9929
10016
|
}
|
|
10017
|
+
if (lineWs.length > 0 && startsAtCode) this.wholeCodeLines.add(lineWs);
|
|
9930
10018
|
}
|
|
9931
10019
|
lineStart = i + 1;
|
|
9932
10020
|
}
|
|
@@ -9938,6 +10026,7 @@ var init_test_content_removal = __esm({
|
|
|
9938
10026
|
}
|
|
9939
10027
|
keeps(wsLine) {
|
|
9940
10028
|
if (wsLine.length === 0) return true;
|
|
10029
|
+
if (this.wholeCodeLines.has(wsLine)) return true;
|
|
9941
10030
|
let at = this.text.indexOf(wsLine);
|
|
9942
10031
|
while (at !== -1) {
|
|
9943
10032
|
if (this.inLiteral[at] === 0) return true;
|
|
@@ -10763,7 +10852,7 @@ var init_policy_load = __esm({
|
|
|
10763
10852
|
});
|
|
10764
10853
|
|
|
10765
10854
|
// src/signoff.ts
|
|
10766
|
-
import { createHash as
|
|
10855
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
10767
10856
|
import { appendFileSync, mkdirSync, readFileSync as readFileSync6, existsSync as existsSync6 } from "node:fs";
|
|
10768
10857
|
import { dirname as dirname2, join as join9 } from "node:path";
|
|
10769
10858
|
function ledgerEntryFrom(value) {
|
|
@@ -10775,7 +10864,7 @@ function ledgerEntryFrom(value) {
|
|
|
10775
10864
|
return { rule, ...file !== void 0 ? { file } : {}, fingerprint: fingerprint2, reason, recordedAt, expiresAt };
|
|
10776
10865
|
}
|
|
10777
10866
|
function fingerprint(rule, file, evidence) {
|
|
10778
|
-
return
|
|
10867
|
+
return createHash3("sha256").update(`${rule}\0${file ?? ""}\0${evidence}`).digest("hex").slice(0, 16);
|
|
10779
10868
|
}
|
|
10780
10869
|
function ledgerPath(cwd, policy) {
|
|
10781
10870
|
const rel = policy.signoff?.ledger ?? ".tamperward/ledger.jsonl";
|
|
@@ -11335,7 +11424,7 @@ var init_session = __esm({
|
|
|
11335
11424
|
});
|
|
11336
11425
|
|
|
11337
11426
|
// src/effect.ts
|
|
11338
|
-
import { createHash as
|
|
11427
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
11339
11428
|
import { existsSync as existsSync8, lstatSync as lstatSync3, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync8, renameSync as renameSync2, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
11340
11429
|
import { join as join11 } from "node:path";
|
|
11341
11430
|
function ptreeFrom(value) {
|
|
@@ -11478,19 +11567,19 @@ var init_effect = __esm({
|
|
|
11478
11567
|
init_narrow();
|
|
11479
11568
|
UNSAFE2 = /[^A-Za-z0-9._-]/g;
|
|
11480
11569
|
SKIP_DIRS2 = /* @__PURE__ */ new Set([".git", "node_modules", ".hg", ".svn"]);
|
|
11481
|
-
sha = (buf) =>
|
|
11570
|
+
sha = (buf) => createHash4("sha256").update(buf).digest("hex").slice(0, 16);
|
|
11482
11571
|
}
|
|
11483
11572
|
});
|
|
11484
11573
|
|
|
11485
11574
|
// src/cli/watch.ts
|
|
11486
11575
|
import { appendFileSync as appendFileSync3, lstatSync as lstatSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync9, readdirSync as readdirSync2, watch, writeFileSync as writeFileSync4 } from "node:fs";
|
|
11487
11576
|
import { join as join12 } from "node:path";
|
|
11488
|
-
import { createHash as
|
|
11577
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
11489
11578
|
function snap(abs) {
|
|
11490
11579
|
const e = inspectPath(abs);
|
|
11491
11580
|
if (e.kind === "absent" || e.kind === "unreadable") return { mode: null, size: null, hash: null };
|
|
11492
11581
|
if (e.kind !== "file" || e.content == null) return { mode: e.mode, size: null, hash: null };
|
|
11493
|
-
const hash =
|
|
11582
|
+
const hash = createHash5("sha256").update(e.content).digest("hex").slice(0, 16);
|
|
11494
11583
|
return { mode: e.mode, size: e.size, hash };
|
|
11495
11584
|
}
|
|
11496
11585
|
function defaultEventLog(cwd) {
|
|
@@ -12003,15 +12092,15 @@ var init_fs_events = __esm({
|
|
|
12003
12092
|
});
|
|
12004
12093
|
|
|
12005
12094
|
// src/cli/audit.ts
|
|
12006
|
-
import { createHash as
|
|
12095
|
+
import { createHash as createHash6, randomBytes } from "node:crypto";
|
|
12007
12096
|
import { appendFileSync as appendFileSync4, existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync10 } from "node:fs";
|
|
12008
12097
|
import { dirname as dirname4, isAbsolute as isAbsolute8, join as join13, resolve as resolve4 } from "node:path";
|
|
12009
12098
|
function sessionHash(sessionId) {
|
|
12010
12099
|
if (!sessionId) return void 0;
|
|
12011
|
-
return "sha256:" +
|
|
12100
|
+
return "sha256:" + createHash6("sha256").update(sessionId).digest("hex").slice(0, 24);
|
|
12012
12101
|
}
|
|
12013
12102
|
function eventId() {
|
|
12014
|
-
return "sha256:" +
|
|
12103
|
+
return "sha256:" + createHash6("sha256").update(randomBytes(32)).digest("hex").slice(0, 32);
|
|
12015
12104
|
}
|
|
12016
12105
|
function configuredAuditPath(cwd, env = process.env) {
|
|
12017
12106
|
const value = env[AUDIT_LOG_ENV];
|
|
@@ -12240,7 +12329,7 @@ var init_audit = __esm({
|
|
|
12240
12329
|
|
|
12241
12330
|
// src/cli/hook.ts
|
|
12242
12331
|
import { readFileSync as readFileSync11, appendFileSync as appendFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync10 } from "node:fs";
|
|
12243
|
-
import { createHash as
|
|
12332
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
12244
12333
|
function setSnapshotCache(cache3) {
|
|
12245
12334
|
snapshotCache = cache3;
|
|
12246
12335
|
}
|
|
@@ -12478,7 +12567,7 @@ function sanctionPredictedWrites(cwd, sessionId, policy, changes) {
|
|
|
12478
12567
|
let dirty = false;
|
|
12479
12568
|
for (const c of changes) {
|
|
12480
12569
|
if (c.kind !== "file" || c.after == null || !isProtected(c.path, policy)) continue;
|
|
12481
|
-
const hash =
|
|
12570
|
+
const hash = createHash7("sha256").update(c.after).digest("hex").slice(0, 16);
|
|
12482
12571
|
const prev = tree[c.path];
|
|
12483
12572
|
tree[c.path] = {
|
|
12484
12573
|
hash,
|
|
@@ -13325,7 +13414,7 @@ var init_safe_write = __esm({
|
|
|
13325
13414
|
|
|
13326
13415
|
// src/cli/init.ts
|
|
13327
13416
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
13328
|
-
import { createHash as
|
|
13417
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
13329
13418
|
import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync13, realpathSync as realpathSync5 } from "node:fs";
|
|
13330
13419
|
import { basename as basename3, dirname as dirname5, isAbsolute as isAbsolute9, join as join15, relative as relative3, resolve as resolve5 } from "node:path";
|
|
13331
13420
|
function gitConfig(cwd, key2) {
|
|
@@ -13375,7 +13464,7 @@ function stalePin(command) {
|
|
|
13375
13464
|
return pin !== null && (pin !== TW_VERSION || !OURS.test(command));
|
|
13376
13465
|
}
|
|
13377
13466
|
function bodyHash(body) {
|
|
13378
|
-
return
|
|
13467
|
+
return createHash8("sha256").update(body).digest("hex").slice(0, 16);
|
|
13379
13468
|
}
|
|
13380
13469
|
function workflowFile(body) {
|
|
13381
13470
|
return `${WORKFLOW_MARK} v${TW_VERSION} sha256:${bodyHash(body)}
|
|
@@ -14120,7 +14209,7 @@ jobs:
|
|
|
14120
14209
|
|
|
14121
14210
|
// src/fingerprint.ts
|
|
14122
14211
|
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
14123
|
-
import { createHash as
|
|
14212
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
14124
14213
|
function treeFingerprint(cwd, keepIgnored) {
|
|
14125
14214
|
const listed = execFileSync7("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
14126
14215
|
cwd,
|
|
@@ -14129,7 +14218,7 @@ function treeFingerprint(cwd, keepIgnored) {
|
|
|
14129
14218
|
env: trustedGitEnv()
|
|
14130
14219
|
}).split("\0").filter(Boolean);
|
|
14131
14220
|
const files = [.../* @__PURE__ */ new Set([...listed, ...keepIgnored ? ignoredPaths({ cwd }, keepIgnored) : []])].sort();
|
|
14132
|
-
const h =
|
|
14221
|
+
const h = createHash9("sha256");
|
|
14133
14222
|
for (const rel of files) {
|
|
14134
14223
|
h.update(rel);
|
|
14135
14224
|
h.update("\0");
|
|
@@ -14153,7 +14242,7 @@ var init_fingerprint = __esm({
|
|
|
14153
14242
|
});
|
|
14154
14243
|
|
|
14155
14244
|
// src/dependency-env.ts
|
|
14156
|
-
import { createHash as
|
|
14245
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
14157
14246
|
import {
|
|
14158
14247
|
existsSync as existsSync13,
|
|
14159
14248
|
lstatSync as lstatSync8,
|
|
@@ -14306,7 +14395,7 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
14306
14395
|
walk2(root.realPath, "");
|
|
14307
14396
|
}
|
|
14308
14397
|
function snapshot(roots, probes, cwd) {
|
|
14309
|
-
const h =
|
|
14398
|
+
const h = createHash10("sha256");
|
|
14310
14399
|
for (const probe of probes) {
|
|
14311
14400
|
h.update(
|
|
14312
14401
|
"probe\0" + probe.kind + "\0" + probe.path + "\0" + (existsSync13(probe.path) ? "present" : "absent") + "\0"
|
|
@@ -14640,6 +14729,7 @@ function parseCapturedSupervisorResult(stdoutText) {
|
|
|
14640
14729
|
signal: typeof raw.signal === "string" ? raw.signal : null,
|
|
14641
14730
|
timedOut: Boolean(raw.timedOut),
|
|
14642
14731
|
...typeof raw.error === "string" && raw.error ? { error: raw.error } : {},
|
|
14732
|
+
...raw.pipeHeldOpen === true ? { pipeHeldOpen: true } : {},
|
|
14643
14733
|
diagnostics
|
|
14644
14734
|
};
|
|
14645
14735
|
}
|
|
@@ -14657,6 +14747,7 @@ function runCapturedProcessSync(executable, args, opts) {
|
|
|
14657
14747
|
timeoutMs: opts.timeoutMs,
|
|
14658
14748
|
detached: Boolean(opts.detached),
|
|
14659
14749
|
killGroupOnFinish: Boolean(opts.killGroupOnFinish),
|
|
14750
|
+
drainMs: opts.drainMs ?? 1e3,
|
|
14660
14751
|
captureBytes: DIAGNOSTIC_TAIL_BYTES
|
|
14661
14752
|
}),
|
|
14662
14753
|
{ mode: 384 }
|
|
@@ -14789,6 +14880,7 @@ let done = false;
|
|
|
14789
14880
|
let timedOut = false;
|
|
14790
14881
|
let exitInfo = { exit: null, signal: null };
|
|
14791
14882
|
let descendantTracker = null;
|
|
14883
|
+
let drainTimer = null;
|
|
14792
14884
|
|
|
14793
14885
|
function killOwned() {
|
|
14794
14886
|
if (!child || !child.pid) return;
|
|
@@ -14810,6 +14902,7 @@ function finish(extra) {
|
|
|
14810
14902
|
if (done) return;
|
|
14811
14903
|
done = true;
|
|
14812
14904
|
if (descendantTracker) clearInterval(descendantTracker);
|
|
14905
|
+
if (drainTimer) clearTimeout(drainTimer);
|
|
14813
14906
|
try {
|
|
14814
14907
|
// stdout is reserved for the trusted supervisor result. Candidate suite
|
|
14815
14908
|
// stdout/stderr are separate pipes and are never inherited here.
|
|
@@ -14864,12 +14957,19 @@ if (child) {
|
|
|
14864
14957
|
signal: signal == null ? null : String(signal),
|
|
14865
14958
|
};
|
|
14866
14959
|
if (cfg.killGroupOnFinish) killOwned();
|
|
14960
|
+
// A leaked descendant can hold a pipe open so 'close' never fires; finish off
|
|
14961
|
+
// exit after a bounded drain instead of hanging. 'close' still wins if first.
|
|
14962
|
+
if (!drainTimer) {
|
|
14963
|
+
drainTimer = setTimeout(() => finish({ pipeHeldOpen: true }), Number(cfg.drainMs));
|
|
14964
|
+
drainTimer.unref();
|
|
14965
|
+
}
|
|
14867
14966
|
});
|
|
14868
14967
|
|
|
14869
14968
|
// close fires after the stdio pipes close, so the retained tails include all
|
|
14870
14969
|
// output that was drained before owned descendants were terminated.
|
|
14871
14970
|
child.on('close', (code, signal) => {
|
|
14872
14971
|
clearTimeout(timer);
|
|
14972
|
+
if (drainTimer) clearTimeout(drainTimer);
|
|
14873
14973
|
if (exitInfo.exit === null && code != null) exitInfo.exit = Number(code);
|
|
14874
14974
|
if (exitInfo.signal === null && signal != null) exitInfo.signal = String(signal);
|
|
14875
14975
|
finish({});
|
|
@@ -14880,7 +14980,7 @@ if (child) {
|
|
|
14880
14980
|
});
|
|
14881
14981
|
|
|
14882
14982
|
// src/verifier-backend.ts
|
|
14883
|
-
import { createHash as
|
|
14983
|
+
import { createHash as createHash11, randomUUID } from "node:crypto";
|
|
14884
14984
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
14885
14985
|
import { accessSync as accessSync2, constants as constants3, readFileSync as readFileSync16, realpathSync as realpathSync7, statSync as statSync4 } from "node:fs";
|
|
14886
14986
|
import { delimiter as delimiter2, dirname as dirname7, join as join18, resolve as resolve7 } from "node:path";
|
|
@@ -14927,7 +15027,7 @@ function findExecutable(name) {
|
|
|
14927
15027
|
return null;
|
|
14928
15028
|
}
|
|
14929
15029
|
function fileSha256(path) {
|
|
14930
|
-
return
|
|
15030
|
+
return createHash11("sha256").update(readFileSync16(path)).digest("hex");
|
|
14931
15031
|
}
|
|
14932
15032
|
function engineClientEnv() {
|
|
14933
15033
|
return {
|
|
@@ -15360,7 +15460,7 @@ var init_verifier_backend = __esm({
|
|
|
15360
15460
|
|
|
15361
15461
|
// src/cli/verify.ts
|
|
15362
15462
|
import { execFileSync as execFileSync8, spawnSync as spawnSync3 } from "node:child_process";
|
|
15363
|
-
import { createHash as
|
|
15463
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
15364
15464
|
import { chmodSync as chmodSync2, cpSync, lstatSync as lstatSync9, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync3, readFileSync as readFileSync17, readlinkSync as readlinkSync3, rmSync as rmSync7, symlinkSync, writeFileSync as writeFileSync9, existsSync as existsSync14 } from "node:fs";
|
|
15365
15465
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
15366
15466
|
import { dirname as dirname8, isAbsolute as isAbsolute11, join as join19, relative as relative5, resolve as resolve8, sep as sep3 } from "node:path";
|
|
@@ -15574,7 +15674,7 @@ function baseEntries(base, cwd) {
|
|
|
15574
15674
|
});
|
|
15575
15675
|
}
|
|
15576
15676
|
function overlayDigest(dest, paths) {
|
|
15577
|
-
const h =
|
|
15677
|
+
const h = createHash12("sha256");
|
|
15578
15678
|
for (const rel of [...paths].sort()) {
|
|
15579
15679
|
h.update(rel);
|
|
15580
15680
|
h.update("\0");
|
|
@@ -15714,7 +15814,12 @@ function runLocalSuite(dir, cmd, budgetSecs) {
|
|
|
15714
15814
|
diagnostics: r.diagnostics
|
|
15715
15815
|
};
|
|
15716
15816
|
}
|
|
15717
|
-
return {
|
|
15817
|
+
return {
|
|
15818
|
+
exit: r.exit,
|
|
15819
|
+
secs,
|
|
15820
|
+
...r.pipeHeldOpen ? { pipeHeldOpen: true } : {},
|
|
15821
|
+
diagnostics: r.diagnostics
|
|
15822
|
+
};
|
|
15718
15823
|
} finally {
|
|
15719
15824
|
rmSync7(scratch, { recursive: true, force: true });
|
|
15720
15825
|
}
|
|
@@ -15873,7 +15978,29 @@ function runVerify(opts) {
|
|
|
15873
15978
|
}
|
|
15874
15979
|
const frozenNodeModules = dependencyEnvironment ? dependencyEnvironment.roots.find((root) => root.kind === "node_modules")?.realPath ?? null : null;
|
|
15875
15980
|
const checkDeps = () => dependencyEnvironment ? checkDependencyEnvironment(cwd, dependencyEnvironment) : { ok: true };
|
|
15876
|
-
const runStage = (dir) => isolated ? runContainerStage(verifierBackend, dir, cmd, budget) : runLocalSuite(dir, cmd, budget);
|
|
15981
|
+
const runStage = opts.runStage ?? ((dir) => isolated ? runContainerStage(verifierBackend, dir, cmd, budget) : runLocalSuite(dir, cmd, budget));
|
|
15982
|
+
const notQuiescent = (stage, r) => {
|
|
15983
|
+
const detail = `the ${stage} suite exited (code ${r.exit ?? "unknown"}) but left a process holding stdout open after termination was attempted; the stage is not quiescent, so the run cannot be certified`;
|
|
15984
|
+
opts.onVerdict?.({ verdict: "CANNOT_VERIFY", reason: "VERIFIER_BACKEND_RUNTIME_FAILURE", detail });
|
|
15985
|
+
if (opts.json) {
|
|
15986
|
+
out3(JSON.stringify({
|
|
15987
|
+
schema_version: MACHINE_SCHEMA_VERSION,
|
|
15988
|
+
verdict: "CANNOT_VERIFY",
|
|
15989
|
+
reason: "VERIFIER_BACKEND_RUNTIME_FAILURE",
|
|
15990
|
+
stage,
|
|
15991
|
+
...caseReport(),
|
|
15992
|
+
detail,
|
|
15993
|
+
diagnostics: diagnosticsJson(r.diagnostics, true),
|
|
15994
|
+
verifier_backend: backendReport(),
|
|
15995
|
+
oracle_assurance: oracleAssuranceReport()
|
|
15996
|
+
}));
|
|
15997
|
+
} else {
|
|
15998
|
+
out3(`verify: the ${stage} suite exited but left a process holding stdout open \u2014 the stage is not quiescent, failing closed`);
|
|
15999
|
+
out3("verify: " + detail);
|
|
16000
|
+
renderStageDiagnostics(out3, stage, r);
|
|
16001
|
+
}
|
|
16002
|
+
return 2;
|
|
16003
|
+
};
|
|
15877
16004
|
let base;
|
|
15878
16005
|
try {
|
|
15879
16006
|
base = resolveBase(opts.base ?? "HEAD", cwd);
|
|
@@ -15933,6 +16060,10 @@ function runVerify(opts) {
|
|
|
15933
16060
|
);
|
|
15934
16061
|
}
|
|
15935
16062
|
const visible = runStage(visDir);
|
|
16063
|
+
if (visible.pipeHeldOpen) {
|
|
16064
|
+
cleanup([visRoot]);
|
|
16065
|
+
return notQuiescent("visible", visible);
|
|
16066
|
+
}
|
|
15936
16067
|
if (visible.failure === "backend" || visible.failure === "resource") {
|
|
15937
16068
|
cleanup([visRoot]);
|
|
15938
16069
|
const exhausted = visible.failure === "resource";
|
|
@@ -16002,6 +16133,10 @@ function runVerify(opts) {
|
|
|
16002
16133
|
}
|
|
16003
16134
|
const overlayBefore = overlayDigest(priDir, restored);
|
|
16004
16135
|
const pristine = runStage(priDir);
|
|
16136
|
+
if (pristine.pipeHeldOpen) {
|
|
16137
|
+
cleanup([visRoot, priRoot]);
|
|
16138
|
+
return notQuiescent("pristine", pristine);
|
|
16139
|
+
}
|
|
16005
16140
|
if (pristine.failure === "backend" || pristine.failure === "resource") {
|
|
16006
16141
|
cleanup([visRoot, priRoot]);
|
|
16007
16142
|
const exhausted = pristine.failure === "resource";
|
|
@@ -19101,7 +19236,7 @@ var init_capture = __esm({
|
|
|
19101
19236
|
});
|
|
19102
19237
|
|
|
19103
19238
|
// src/research/manifest.ts
|
|
19104
|
-
import { createHash as
|
|
19239
|
+
import { createHash as createHash14 } from "node:crypto";
|
|
19105
19240
|
import { readFileSync as readFileSync23 } from "node:fs";
|
|
19106
19241
|
import { dirname as dirname10, resolve as resolve14 } from "node:path";
|
|
19107
19242
|
function taskFrom(raw, index, manifestDir) {
|
|
@@ -19159,7 +19294,7 @@ function readManifest(path) {
|
|
|
19159
19294
|
if (seen.has(t.id)) throw new ResearchError(`task manifest ${abs}: duplicate task id "${t.id}"`);
|
|
19160
19295
|
seen.add(t.id);
|
|
19161
19296
|
}
|
|
19162
|
-
return { path: abs, sha256:
|
|
19297
|
+
return { path: abs, sha256: createHash14("sha256").update(bytes).digest("hex"), tasks };
|
|
19163
19298
|
}
|
|
19164
19299
|
var MANIFEST_VERSION;
|
|
19165
19300
|
var init_manifest = __esm({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "2.29.
|
|
3
|
+
"version": "2.29.16",
|
|
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",
|