tamperward 2.26.0 → 2.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -2
- package/dist/cli/index.js +618 -306
- package/package.json +1 -1
- package/schemas/audit-v1.schema.json +100 -0
- package/schemas/stats-v1.schema.json +119 -0
package/dist/cli/index.js
CHANGED
|
@@ -211,7 +211,7 @@ function forwardedEnv(env) {
|
|
|
211
211
|
return out3;
|
|
212
212
|
}
|
|
213
213
|
function exchange(paths, req2, timeoutMs) {
|
|
214
|
-
return new Promise((
|
|
214
|
+
return new Promise((resolve17) => {
|
|
215
215
|
let done = false;
|
|
216
216
|
let handedOff = false;
|
|
217
217
|
let accepted = false;
|
|
@@ -222,7 +222,7 @@ function exchange(paths, req2, timeoutMs) {
|
|
|
222
222
|
done = true;
|
|
223
223
|
clearTimeout(timer);
|
|
224
224
|
sock.destroy();
|
|
225
|
-
|
|
225
|
+
resolve17(value);
|
|
226
226
|
};
|
|
227
227
|
const sock = createConnection(paths.socket);
|
|
228
228
|
const timer = setTimeout(() => finish(handedOff ? acceptedFailure("timeout") : null), timeoutMs);
|
|
@@ -298,6 +298,7 @@ var init_hook_client = __esm({
|
|
|
298
298
|
MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
299
299
|
FORWARDED_ENV = [
|
|
300
300
|
"TAMPERWARD_DENYLOG",
|
|
301
|
+
"TAMPERWARD_AUDIT_LOG",
|
|
301
302
|
"TAMPERWARD_FSEVENTS",
|
|
302
303
|
"TAMPERWARD_TRANSIENT",
|
|
303
304
|
"CLAUDE_CONFIG_DIR",
|
|
@@ -1090,7 +1091,7 @@ var init_trusted = __esm({
|
|
|
1090
1091
|
|
|
1091
1092
|
// src/repo-context.ts
|
|
1092
1093
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
1093
|
-
import { accessSync, constants as constants2, existsSync as existsSync2, statSync } from "node:fs";
|
|
1094
|
+
import { accessSync, constants as constants2, existsSync as existsSync2, realpathSync as realpathSync3, statSync } from "node:fs";
|
|
1094
1095
|
import { isAbsolute as isAbsolute3, resolve } from "node:path";
|
|
1095
1096
|
function rev(cwd, flag) {
|
|
1096
1097
|
try {
|
|
@@ -1123,6 +1124,27 @@ function repoContext(cwd) {
|
|
|
1123
1124
|
function repoRoot(cwd) {
|
|
1124
1125
|
return repoContext(cwd)?.root ?? cwd;
|
|
1125
1126
|
}
|
|
1127
|
+
function validateClaimAgainstRoot(claimedCwd, trustedRoot, base) {
|
|
1128
|
+
if (claimedCwd === void 0) return { ok: true, trustedRoot };
|
|
1129
|
+
if (typeof claimedCwd !== "string" || claimedCwd.trim() === "") return { ok: false, rejected: "malformed cwd claim" };
|
|
1130
|
+
const abs = isAbsolute3(claimedCwd) ? claimedCwd : resolve(base, claimedCwd);
|
|
1131
|
+
let realClaim;
|
|
1132
|
+
try {
|
|
1133
|
+
realClaim = realpathSync3(abs);
|
|
1134
|
+
} catch (e) {
|
|
1135
|
+
return { ok: false, rejected: `cwd claim path cannot be resolved (${abs}: ${e instanceof Error ? e.message : String(e)})` };
|
|
1136
|
+
}
|
|
1137
|
+
let realTrusted;
|
|
1138
|
+
try {
|
|
1139
|
+
realTrusted = realpathSync3(trustedRoot);
|
|
1140
|
+
} catch {
|
|
1141
|
+
realTrusted = trustedRoot;
|
|
1142
|
+
}
|
|
1143
|
+
const ctx = repoContext(realClaim);
|
|
1144
|
+
if (!ctx) return { ok: false, rejected: `cwd claim resolves to no repository (${realClaim})` };
|
|
1145
|
+
if (ctx.root !== realTrusted) return { ok: false, rejected: `cwd claim resolves to a different repository (${ctx.root} != trusted ${realTrusted})` };
|
|
1146
|
+
return { ok: true, trustedRoot: realTrusted };
|
|
1147
|
+
}
|
|
1126
1148
|
function outsideRepository(cwd) {
|
|
1127
1149
|
const key2 = resolve(cwd);
|
|
1128
1150
|
let isDir;
|
|
@@ -3517,15 +3539,15 @@ function buildAliasMap(sf) {
|
|
|
3517
3539
|
ts.forEachChild(node, collect2);
|
|
3518
3540
|
};
|
|
3519
3541
|
collect2(sf);
|
|
3520
|
-
const
|
|
3542
|
+
const resolve17 = (name, seen) => {
|
|
3521
3543
|
if (seen.has(name)) return null;
|
|
3522
3544
|
seen.add(name);
|
|
3523
3545
|
const t = decls.get(name);
|
|
3524
|
-
return t ? kindOfType(t, (n) =>
|
|
3546
|
+
return t ? kindOfType(t, (n) => resolve17(n, seen)) : null;
|
|
3525
3547
|
};
|
|
3526
3548
|
const resolved = /* @__PURE__ */ new Map();
|
|
3527
3549
|
for (const name of decls.keys()) {
|
|
3528
|
-
const k =
|
|
3550
|
+
const k = resolve17(name, /* @__PURE__ */ new Set());
|
|
3529
3551
|
if (k) resolved.set(name, k);
|
|
3530
3552
|
}
|
|
3531
3553
|
return resolved;
|
|
@@ -4912,8 +4934,8 @@ function numericOf(e) {
|
|
|
4912
4934
|
}
|
|
4913
4935
|
return void 0;
|
|
4914
4936
|
}
|
|
4915
|
-
function metricsOf(expr,
|
|
4916
|
-
const obj =
|
|
4937
|
+
function metricsOf(expr, resolve17) {
|
|
4938
|
+
const obj = resolve17(expr);
|
|
4917
4939
|
if (!ts.isObjectLiteralExpression(obj)) return void 0;
|
|
4918
4940
|
const out3 = { values: {}, opaque: false };
|
|
4919
4941
|
let any = false;
|
|
@@ -4927,7 +4949,7 @@ function metricsOf(expr, resolve16) {
|
|
|
4927
4949
|
const k = keyName(p.name);
|
|
4928
4950
|
if (k === null || !isMetric(k)) continue;
|
|
4929
4951
|
any = true;
|
|
4930
|
-
const n = numericOf(
|
|
4952
|
+
const n = numericOf(resolve17(p.initializer));
|
|
4931
4953
|
if (n === void 0) out3.opaque = true;
|
|
4932
4954
|
else out3.values[k] = n;
|
|
4933
4955
|
}
|
|
@@ -4974,17 +4996,17 @@ function parseThresholds(src) {
|
|
|
4974
4996
|
try {
|
|
4975
4997
|
const sf = sourceOf(src);
|
|
4976
4998
|
if (!sf) return res;
|
|
4977
|
-
const
|
|
4999
|
+
const resolve17 = resolverFor(sf);
|
|
4978
5000
|
const visit = (node) => {
|
|
4979
5001
|
if (ts.isPropertyAssignment(node)) {
|
|
4980
5002
|
const key2 = keyName(node.name);
|
|
4981
5003
|
if (key2 === "coverageThreshold" || key2 === "thresholds" && underKey(node, "coverage")) {
|
|
4982
5004
|
res.present = true;
|
|
4983
|
-
const init =
|
|
5005
|
+
const init = resolve17(node.initializer);
|
|
4984
5006
|
if (!ts.isObjectLiteralExpression(init)) {
|
|
4985
5007
|
res.opaque = true;
|
|
4986
5008
|
} else {
|
|
4987
|
-
const flat = metricsOf(init,
|
|
5009
|
+
const flat = metricsOf(init, resolve17);
|
|
4988
5010
|
if (flat) res.global = merge(res.global, flat);
|
|
4989
5011
|
for (const p of init.properties) {
|
|
4990
5012
|
if (ts.isSpreadAssignment(p)) {
|
|
@@ -4998,7 +5020,7 @@ function parseThresholds(src) {
|
|
|
4998
5020
|
res.global = merge(res.global, { values: { branches: 100, functions: 100, lines: 100, statements: 100 }, opaque: false });
|
|
4999
5021
|
continue;
|
|
5000
5022
|
}
|
|
5001
|
-
const m = metricsOf(p.initializer,
|
|
5023
|
+
const m = metricsOf(p.initializer, resolve17);
|
|
5002
5024
|
if (!m) continue;
|
|
5003
5025
|
if (k === "global") res.global = merge(res.global, m);
|
|
5004
5026
|
else res.paths.set(norm(k), m);
|
|
@@ -5032,18 +5054,18 @@ function parseLists(src) {
|
|
|
5032
5054
|
try {
|
|
5033
5055
|
const sf = sourceOf(src);
|
|
5034
5056
|
if (!sf) return res;
|
|
5035
|
-
const
|
|
5057
|
+
const resolve17 = resolverFor(sf);
|
|
5036
5058
|
const visit = (node) => {
|
|
5037
5059
|
if (ts.isPropertyAssignment(node)) {
|
|
5038
5060
|
const k = keyName(node.name);
|
|
5039
5061
|
const vitestCoverage = ownerKey(node) === "coverage";
|
|
5040
5062
|
if (k === "collectCoverageFrom" || k === "include" && vitestCoverage) {
|
|
5041
|
-
const { items, opaque } = literals(
|
|
5063
|
+
const { items, opaque } = literals(resolve17(node.initializer));
|
|
5042
5064
|
res.collectFrom = items;
|
|
5043
5065
|
res.collectKey = k === "include" ? "coverage.include" : k;
|
|
5044
5066
|
if (opaque) res.opaqueCollect = true;
|
|
5045
5067
|
} else if (k === "coveragePathIgnorePatterns" || k === "exclude" && vitestCoverage) {
|
|
5046
|
-
const { items, opaque } = literals(
|
|
5068
|
+
const { items, opaque } = literals(resolve17(node.initializer));
|
|
5047
5069
|
(k === "exclude" ? res.ignore : res.ignoreRe).push(...items);
|
|
5048
5070
|
if (opaque) res.opaqueIgnore = true;
|
|
5049
5071
|
}
|
|
@@ -11808,15 +11830,251 @@ var init_fs_events = __esm({
|
|
|
11808
11830
|
}
|
|
11809
11831
|
});
|
|
11810
11832
|
|
|
11833
|
+
// src/cli/audit.ts
|
|
11834
|
+
import { createHash as createHash5, randomBytes } from "node:crypto";
|
|
11835
|
+
import { appendFileSync as appendFileSync4, existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync10 } from "node:fs";
|
|
11836
|
+
import { dirname as dirname4, isAbsolute as isAbsolute8, join as join13, resolve as resolve4 } from "node:path";
|
|
11837
|
+
function sessionHash(sessionId) {
|
|
11838
|
+
if (!sessionId) return void 0;
|
|
11839
|
+
return "sha256:" + createHash5("sha256").update(sessionId).digest("hex").slice(0, 24);
|
|
11840
|
+
}
|
|
11841
|
+
function eventId() {
|
|
11842
|
+
return "sha256:" + createHash5("sha256").update(randomBytes(32)).digest("hex").slice(0, 32);
|
|
11843
|
+
}
|
|
11844
|
+
function configuredAuditPath(cwd, env = process.env) {
|
|
11845
|
+
const value = env[AUDIT_LOG_ENV];
|
|
11846
|
+
if (!value) return null;
|
|
11847
|
+
if (value === "auto") {
|
|
11848
|
+
const ctx = repoContext(cwd);
|
|
11849
|
+
return ctx ? join13(ctx.gitDir, "tamperward", "audit.jsonl") : null;
|
|
11850
|
+
}
|
|
11851
|
+
return isAbsolute8(value) ? value : resolve4(cwd, value);
|
|
11852
|
+
}
|
|
11853
|
+
function defaultAuditPath(cwd) {
|
|
11854
|
+
const ctx = repoContext(cwd);
|
|
11855
|
+
return ctx ? join13(ctx.gitDir, "tamperward", "audit.jsonl") : null;
|
|
11856
|
+
}
|
|
11857
|
+
function recordAuditFindings(findings, context) {
|
|
11858
|
+
if (findings.length === 0) return;
|
|
11859
|
+
const path = configuredAuditPath(context.cwd);
|
|
11860
|
+
if (!path) return;
|
|
11861
|
+
try {
|
|
11862
|
+
mkdirSync5(dirname4(path), { recursive: true });
|
|
11863
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
11864
|
+
const session = sessionHash(context.sessionId);
|
|
11865
|
+
const lines = findings.map((finding) => {
|
|
11866
|
+
const severity = finding.severity === "block" ? "block" : "warn";
|
|
11867
|
+
const event = {
|
|
11868
|
+
schema_version: AUDIT_SCHEMA_VERSION,
|
|
11869
|
+
id: eventId(),
|
|
11870
|
+
timestamp,
|
|
11871
|
+
surface: context.surface,
|
|
11872
|
+
agent: "claude-code",
|
|
11873
|
+
rule: finding.rule,
|
|
11874
|
+
severity,
|
|
11875
|
+
decision: severity === "block" ? "deny" : "warn",
|
|
11876
|
+
...session ? { session } : {}
|
|
11877
|
+
};
|
|
11878
|
+
return JSON.stringify(event);
|
|
11879
|
+
});
|
|
11880
|
+
appendFileSync4(path, lines.join("\n") + "\n");
|
|
11881
|
+
} catch {
|
|
11882
|
+
}
|
|
11883
|
+
}
|
|
11884
|
+
function recordAuditSignal(rule, severity, context) {
|
|
11885
|
+
const finding = {
|
|
11886
|
+
rule,
|
|
11887
|
+
severity,
|
|
11888
|
+
message: "",
|
|
11889
|
+
evidence: "",
|
|
11890
|
+
remediation: "",
|
|
11891
|
+
signoff: { required: severity === "block", command: "" }
|
|
11892
|
+
};
|
|
11893
|
+
recordAuditFindings([finding], context);
|
|
11894
|
+
}
|
|
11895
|
+
function isRecord3(v) {
|
|
11896
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
11897
|
+
}
|
|
11898
|
+
function parseAuditEvent(value, line = 0) {
|
|
11899
|
+
const where = line > 0 ? `audit line ${line}` : "audit event";
|
|
11900
|
+
if (!isRecord3(value)) throw new Error(`${where} must be a JSON object`);
|
|
11901
|
+
const unknown = Object.keys(value).filter((key2) => !ALLOWED_KEYS.has(key2));
|
|
11902
|
+
if (unknown.length) throw new Error(`${where} contains unsupported field "${unknown[0]}"`);
|
|
11903
|
+
if (value.schema_version !== AUDIT_SCHEMA_VERSION) {
|
|
11904
|
+
throw new Error(`${where} has unsupported schema_version ${String(value.schema_version)}`);
|
|
11905
|
+
}
|
|
11906
|
+
if (typeof value.id !== "string" || !/^sha256:[0-9a-f]{32}$/.test(value.id)) {
|
|
11907
|
+
throw new Error(`${where} has an invalid id`);
|
|
11908
|
+
}
|
|
11909
|
+
if (typeof value.timestamp !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/.test(value.timestamp) || !Number.isFinite(Date.parse(value.timestamp))) {
|
|
11910
|
+
throw new Error(`${where} has an invalid timestamp`);
|
|
11911
|
+
}
|
|
11912
|
+
if (value.surface !== "pretooluse" && value.surface !== "stop") {
|
|
11913
|
+
throw new Error(`${where} has an invalid surface`);
|
|
11914
|
+
}
|
|
11915
|
+
if (typeof value.agent !== "string" || !/^[a-z0-9][a-z0-9-]{0,63}$/.test(value.agent)) {
|
|
11916
|
+
throw new Error(`${where} has an invalid agent`);
|
|
11917
|
+
}
|
|
11918
|
+
if (typeof value.rule !== "string" || !/^[a-z0-9][a-z0-9-]{0,79}$/.test(value.rule)) {
|
|
11919
|
+
throw new Error(`${where} has an invalid rule`);
|
|
11920
|
+
}
|
|
11921
|
+
if (value.severity !== "block" && value.severity !== "warn") {
|
|
11922
|
+
throw new Error(`${where} has an invalid severity`);
|
|
11923
|
+
}
|
|
11924
|
+
if (value.decision !== "deny" && value.decision !== "warn") {
|
|
11925
|
+
throw new Error(`${where} has an invalid decision`);
|
|
11926
|
+
}
|
|
11927
|
+
if (value.severity === "block" && value.decision !== "deny" || value.severity === "warn" && value.decision !== "warn") {
|
|
11928
|
+
throw new Error(`${where} has inconsistent severity and decision`);
|
|
11929
|
+
}
|
|
11930
|
+
if (value.session !== void 0 && (typeof value.session !== "string" || !/^sha256:[0-9a-f]{24}$/.test(value.session))) {
|
|
11931
|
+
throw new Error(`${where} has an invalid session`);
|
|
11932
|
+
}
|
|
11933
|
+
return {
|
|
11934
|
+
schema_version: AUDIT_SCHEMA_VERSION,
|
|
11935
|
+
id: value.id,
|
|
11936
|
+
timestamp: value.timestamp,
|
|
11937
|
+
surface: value.surface,
|
|
11938
|
+
agent: value.agent,
|
|
11939
|
+
rule: value.rule,
|
|
11940
|
+
severity: value.severity,
|
|
11941
|
+
decision: value.decision,
|
|
11942
|
+
...typeof value.session === "string" ? { session: value.session } : {}
|
|
11943
|
+
};
|
|
11944
|
+
}
|
|
11945
|
+
function parseAuditJsonl(raw) {
|
|
11946
|
+
const events = [];
|
|
11947
|
+
raw.split(/\r?\n/).forEach((line, index) => {
|
|
11948
|
+
if (!line.trim()) return;
|
|
11949
|
+
let value;
|
|
11950
|
+
try {
|
|
11951
|
+
value = JSON.parse(line);
|
|
11952
|
+
} catch {
|
|
11953
|
+
throw new Error(`audit line ${index + 1} is not valid JSON`);
|
|
11954
|
+
}
|
|
11955
|
+
events.push(parseAuditEvent(value, index + 1));
|
|
11956
|
+
});
|
|
11957
|
+
return events;
|
|
11958
|
+
}
|
|
11959
|
+
function parseSince(value, nowMs = Date.now()) {
|
|
11960
|
+
const relative8 = /^(\d+)(m|h|d)$/.exec(value);
|
|
11961
|
+
if (relative8) {
|
|
11962
|
+
const count = Number(relative8[1]);
|
|
11963
|
+
const unit = relative8[2] === "m" ? 6e4 : relative8[2] === "h" ? 36e5 : 864e5;
|
|
11964
|
+
if (!Number.isSafeInteger(count) || count <= 0) throw new Error(`invalid --since value "${value}"`);
|
|
11965
|
+
return nowMs - count * unit;
|
|
11966
|
+
}
|
|
11967
|
+
const parsed = Date.parse(value);
|
|
11968
|
+
if (!Number.isFinite(parsed)) {
|
|
11969
|
+
throw new Error(`invalid --since value "${value}" (use e.g. 30d, 12h, 90m, or an ISO timestamp)`);
|
|
11970
|
+
}
|
|
11971
|
+
return parsed;
|
|
11972
|
+
}
|
|
11973
|
+
function summarizeAudit(events) {
|
|
11974
|
+
const ordered = [...events].sort((a, b) => a.timestamp.localeCompare(b.timestamp) || a.id.localeCompare(b.id));
|
|
11975
|
+
const rules = /* @__PURE__ */ new Map();
|
|
11976
|
+
const surfaces = /* @__PURE__ */ new Map();
|
|
11977
|
+
const sessions = /* @__PURE__ */ new Set();
|
|
11978
|
+
for (const event of ordered) {
|
|
11979
|
+
const bucket = rules.get(event.rule) ?? { events: 0, blocked: 0, warnings: 0 };
|
|
11980
|
+
bucket.events++;
|
|
11981
|
+
if (event.severity === "block") bucket.blocked++;
|
|
11982
|
+
else bucket.warnings++;
|
|
11983
|
+
rules.set(event.rule, bucket);
|
|
11984
|
+
surfaces.set(event.surface, (surfaces.get(event.surface) ?? 0) + 1);
|
|
11985
|
+
if (event.session) sessions.add(event.session);
|
|
11986
|
+
}
|
|
11987
|
+
const by_rule = [...rules.entries()].map(([rule, counts]) => ({ rule, ...counts })).sort((a, b) => b.events - a.events || a.rule.localeCompare(b.rule));
|
|
11988
|
+
const by_surface = [...surfaces.entries()].map(([surface, count]) => ({ surface, events: count })).sort((a, b) => b.events - a.events || a.surface.localeCompare(b.surface));
|
|
11989
|
+
return {
|
|
11990
|
+
schema_version: AUDIT_SCHEMA_VERSION,
|
|
11991
|
+
events: ordered.length,
|
|
11992
|
+
blocked: ordered.filter((event) => event.severity === "block").length,
|
|
11993
|
+
warnings: ordered.filter((event) => event.severity === "warn").length,
|
|
11994
|
+
sessions: sessions.size,
|
|
11995
|
+
first_event: ordered[0]?.timestamp ?? null,
|
|
11996
|
+
last_event: ordered.at(-1)?.timestamp ?? null,
|
|
11997
|
+
by_rule,
|
|
11998
|
+
by_surface,
|
|
11999
|
+
interpretation: "finding-is-not-proof-of-intent"
|
|
12000
|
+
};
|
|
12001
|
+
}
|
|
12002
|
+
function renderAuditStats(summary) {
|
|
12003
|
+
const out3 = [
|
|
12004
|
+
"TamperWard audit stats",
|
|
12005
|
+
"",
|
|
12006
|
+
`Events ${summary.events}`,
|
|
12007
|
+
`Blocked ${summary.blocked}`,
|
|
12008
|
+
`Warnings ${summary.warnings}`,
|
|
12009
|
+
`Sessions ${summary.sessions}`
|
|
12010
|
+
];
|
|
12011
|
+
if (summary.first_event && summary.last_event) {
|
|
12012
|
+
out3.push(`First ${summary.first_event}`, `Last ${summary.last_event}`);
|
|
12013
|
+
}
|
|
12014
|
+
if (summary.by_rule.length) {
|
|
12015
|
+
out3.push("", "By rule");
|
|
12016
|
+
const width = Math.max(...summary.by_rule.map((row) => row.rule.length));
|
|
12017
|
+
for (const row of summary.by_rule) out3.push(` ${row.rule.padEnd(width)} ${row.events}`);
|
|
12018
|
+
}
|
|
12019
|
+
if (summary.by_surface.length) {
|
|
12020
|
+
out3.push("", "By surface");
|
|
12021
|
+
const width = Math.max(...summary.by_surface.map((row) => row.surface.length));
|
|
12022
|
+
for (const row of summary.by_surface) out3.push(` ${row.surface.padEnd(width)} ${row.events}`);
|
|
12023
|
+
}
|
|
12024
|
+
out3.push("", "Note: an integrity finding is a signal, not proof of agent intent; legitimate refactors can trigger findings.");
|
|
12025
|
+
return out3.join("\n") + "\n";
|
|
12026
|
+
}
|
|
12027
|
+
function runStats(opts = {}) {
|
|
12028
|
+
const cwd = resolve4(opts.cwd ?? process.cwd());
|
|
12029
|
+
const explicitFile = opts.file !== void 0;
|
|
12030
|
+
const configured = process.env[AUDIT_LOG_ENV];
|
|
12031
|
+
const file = opts.file ? isAbsolute8(opts.file) ? opts.file : resolve4(cwd, opts.file) : configured && configured !== "auto" ? isAbsolute8(configured) ? configured : resolve4(cwd, configured) : defaultAuditPath(cwd);
|
|
12032
|
+
const cutoff = opts.since ? parseSince(opts.since) : null;
|
|
12033
|
+
if (!file) throw new Error("stats needs --file outside a Git repository");
|
|
12034
|
+
if (!existsSync9(file)) {
|
|
12035
|
+
if (explicitFile) throw new Error(`audit file not found: ${file}`);
|
|
12036
|
+
const empty2 = summarizeAudit([]);
|
|
12037
|
+
process.stdout.write(opts.json ? JSON.stringify(empty2) + "\n" : renderAuditStats(empty2));
|
|
12038
|
+
return 0;
|
|
12039
|
+
}
|
|
12040
|
+
let events = parseAuditJsonl(readFileSync10(file, "utf8"));
|
|
12041
|
+
if (cutoff !== null) {
|
|
12042
|
+
events = events.filter((event) => Date.parse(event.timestamp) >= cutoff);
|
|
12043
|
+
}
|
|
12044
|
+
const summary = summarizeAudit(events);
|
|
12045
|
+
process.stdout.write(opts.json ? JSON.stringify(summary) + "\n" : renderAuditStats(summary));
|
|
12046
|
+
return 0;
|
|
12047
|
+
}
|
|
12048
|
+
var AUDIT_SCHEMA_VERSION, AUDIT_LOG_ENV, ALLOWED_KEYS;
|
|
12049
|
+
var init_audit = __esm({
|
|
12050
|
+
"src/cli/audit.ts"() {
|
|
12051
|
+
"use strict";
|
|
12052
|
+
init_repo_context();
|
|
12053
|
+
AUDIT_SCHEMA_VERSION = 1;
|
|
12054
|
+
AUDIT_LOG_ENV = "TAMPERWARD_AUDIT_LOG";
|
|
12055
|
+
ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
12056
|
+
"schema_version",
|
|
12057
|
+
"id",
|
|
12058
|
+
"timestamp",
|
|
12059
|
+
"surface",
|
|
12060
|
+
"agent",
|
|
12061
|
+
"rule",
|
|
12062
|
+
"severity",
|
|
12063
|
+
"decision",
|
|
12064
|
+
"session"
|
|
12065
|
+
]);
|
|
12066
|
+
}
|
|
12067
|
+
});
|
|
12068
|
+
|
|
11811
12069
|
// src/cli/hook.ts
|
|
11812
|
-
import { readFileSync as
|
|
11813
|
-
import { createHash as
|
|
12070
|
+
import { readFileSync as readFileSync11, appendFileSync as appendFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync10 } from "node:fs";
|
|
12071
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
11814
12072
|
function setSnapshotCache(cache3) {
|
|
11815
12073
|
snapshotCache = cache3;
|
|
11816
12074
|
}
|
|
11817
12075
|
function readStdin() {
|
|
11818
12076
|
try {
|
|
11819
|
-
return
|
|
12077
|
+
return readFileSync11(0, "utf8");
|
|
11820
12078
|
} catch (e) {
|
|
11821
12079
|
throw new HookInputError(`cannot read the hook payload on stdin: ${e instanceof Error ? e.message : String(e)}`);
|
|
11822
12080
|
}
|
|
@@ -11844,7 +12102,7 @@ function recordDenylog(blocks2) {
|
|
|
11844
12102
|
const log = process.env.TAMPERWARD_DENYLOG;
|
|
11845
12103
|
if (!log) return;
|
|
11846
12104
|
try {
|
|
11847
|
-
|
|
12105
|
+
appendFileSync5(log, blocks2.map((b) => b.rule).join(",") + "\n");
|
|
11848
12106
|
} catch {
|
|
11849
12107
|
}
|
|
11850
12108
|
}
|
|
@@ -11852,7 +12110,7 @@ function errText2(e) {
|
|
|
11852
12110
|
const m = e instanceof Error ? e.message : String(e);
|
|
11853
12111
|
return m.length > 200 ? m.slice(0, 200) + "\u2026" : m;
|
|
11854
12112
|
}
|
|
11855
|
-
function failClosed(kind, detail) {
|
|
12113
|
+
function failClosed(kind, detail, audit) {
|
|
11856
12114
|
const f = {
|
|
11857
12115
|
rule: "tamperward-unavailable",
|
|
11858
12116
|
severity: "block",
|
|
@@ -11861,12 +12119,9 @@ function failClosed(kind, detail) {
|
|
|
11861
12119
|
remediation: "Repair the Tamperward setup \u2014 most often an unparseable .tamperward.yml (check for merge-conflict markers) \u2014 then retry. Do not work around the gate while it is down.",
|
|
11862
12120
|
signoff: { required: true, command: 'tamperward allow --reason "..."' }
|
|
11863
12121
|
};
|
|
11864
|
-
return verdict([f], kind);
|
|
12122
|
+
return verdict([f], kind, audit);
|
|
11865
12123
|
}
|
|
11866
|
-
function
|
|
11867
|
-
if (blocks2.length === 0) return { exitCode: 0, stdout: "" };
|
|
11868
|
-
recordDenylog(blocks2);
|
|
11869
|
-
const reason = formatDenial(blocks2);
|
|
12124
|
+
function denyWire(reason, kind) {
|
|
11870
12125
|
const payload = kind === "PreToolUse" ? {
|
|
11871
12126
|
hookSpecificOutput: {
|
|
11872
12127
|
hookEventName: "PreToolUse",
|
|
@@ -11874,7 +12129,20 @@ function verdict(blocks2, kind) {
|
|
|
11874
12129
|
permissionDecisionReason: reason
|
|
11875
12130
|
}
|
|
11876
12131
|
} : { decision: "block", reason };
|
|
11877
|
-
return
|
|
12132
|
+
return JSON.stringify(payload) + "\n";
|
|
12133
|
+
}
|
|
12134
|
+
function verdict(blocks2, kind, audit) {
|
|
12135
|
+
if (blocks2.length === 0) return { exitCode: 0, stdout: "" };
|
|
12136
|
+
recordDenylog(blocks2);
|
|
12137
|
+
if (audit) {
|
|
12138
|
+
recordAuditFindings(blocks2, {
|
|
12139
|
+
cwd: audit.cwd,
|
|
12140
|
+
surface: kind === "PreToolUse" ? "pretooluse" : "stop",
|
|
12141
|
+
sessionId: audit.sessionId
|
|
12142
|
+
});
|
|
12143
|
+
}
|
|
12144
|
+
const reason = formatDenial(blocks2);
|
|
12145
|
+
return { exitCode: 0, stdout: denyWire(reason, kind) };
|
|
11878
12146
|
}
|
|
11879
12147
|
function fileChanges(changes) {
|
|
11880
12148
|
return changes.filter((c) => c.kind === "file");
|
|
@@ -12038,7 +12306,7 @@ function sanctionPredictedWrites(cwd, sessionId, policy, changes) {
|
|
|
12038
12306
|
let dirty = false;
|
|
12039
12307
|
for (const c of changes) {
|
|
12040
12308
|
if (c.kind !== "file" || c.after == null || !isProtected(c.path, policy)) continue;
|
|
12041
|
-
const hash =
|
|
12309
|
+
const hash = createHash6("sha256").update(c.after).digest("hex").slice(0, 16);
|
|
12042
12310
|
const prev = tree[c.path];
|
|
12043
12311
|
tree[c.path] = {
|
|
12044
12312
|
hash,
|
|
@@ -12052,20 +12320,24 @@ function sanctionPredictedWrites(cwd, sessionId, policy, changes) {
|
|
|
12052
12320
|
}
|
|
12053
12321
|
if (dirty) savePtree(cwd, sessionId, tree);
|
|
12054
12322
|
}
|
|
12055
|
-
function preToolUseVerdict(input, defaultCwd) {
|
|
12323
|
+
function preToolUseVerdict(input, defaultCwd, trustedRoot) {
|
|
12324
|
+
const sessionCwd = input.cwd ?? defaultCwd ?? process.cwd();
|
|
12056
12325
|
try {
|
|
12057
|
-
|
|
12326
|
+
if (trustedRoot !== void 0) {
|
|
12327
|
+
const v = validateClaimAgainstRoot(input.cwd, trustedRoot, defaultCwd ?? process.cwd());
|
|
12328
|
+
if (!v.ok) return failClosed("PreToolUse", `repository identity claim rejected: ${v.rejected}`);
|
|
12329
|
+
}
|
|
12058
12330
|
const cwd = repoRoot(sessionCwd);
|
|
12059
12331
|
turnBaseline(cwd, input.session_id);
|
|
12060
12332
|
const policy = loadPolicy(cwd);
|
|
12061
12333
|
const driftBlocks = effectDriftBlocks(cwd, input.session_id, policy);
|
|
12062
|
-
if (driftBlocks) return verdict(driftBlocks, "PreToolUse");
|
|
12334
|
+
if (driftBlocks) return verdict(driftBlocks, "PreToolUse", { cwd, sessionId: input.session_id });
|
|
12063
12335
|
const changes = changesFromClaudeHook(input, cwd, sessionCwd);
|
|
12064
12336
|
const blocks2 = evaluate(changes, policy, void 0, "tool-call", { cwd }).filter((f) => f.severity === "block");
|
|
12065
12337
|
if (blocks2.length === 0) sanctionPredictedWrites(cwd, input.session_id, policy, changes);
|
|
12066
|
-
return verdict(blocks2, "PreToolUse");
|
|
12338
|
+
return verdict(blocks2, "PreToolUse", { cwd, sessionId: input.session_id });
|
|
12067
12339
|
} catch (e) {
|
|
12068
|
-
return failClosed("PreToolUse", errText2(e));
|
|
12340
|
+
return failClosed("PreToolUse", errText2(e), { cwd: sessionCwd, sessionId: input.session_id });
|
|
12069
12341
|
}
|
|
12070
12342
|
}
|
|
12071
12343
|
function cursorPath(cwd, sessionId) {
|
|
@@ -12089,19 +12361,21 @@ function turnTransientBlocks(cwd, sessionId, policy, changes) {
|
|
|
12089
12361
|
const log = defaultEventLog(cwd);
|
|
12090
12362
|
const telemetry = watcherTelemetry(log);
|
|
12091
12363
|
if (telemetry.state !== "healthy") {
|
|
12092
|
-
recordObserverHealth(telemetry.state, telemetry.reason);
|
|
12364
|
+
recordObserverHealth(cwd, sessionId, telemetry.state, telemetry.reason);
|
|
12093
12365
|
}
|
|
12094
12366
|
const cp = cursorPath(cwd, sessionId);
|
|
12095
|
-
if (!
|
|
12367
|
+
if (!existsSync10(log)) return none;
|
|
12096
12368
|
let offset = 0;
|
|
12097
|
-
if (cp &&
|
|
12098
|
-
const n = Number(
|
|
12369
|
+
if (cp && existsSync10(cp)) {
|
|
12370
|
+
const n = Number(readFileSync11(cp, "utf8"));
|
|
12099
12371
|
offset = Number.isFinite(n) ? n : 0;
|
|
12100
12372
|
}
|
|
12101
12373
|
const drained = drainEvents(log, offset);
|
|
12102
12374
|
if (!drained.complete) {
|
|
12103
12375
|
const issue = drained.issue ?? "read-stalled";
|
|
12104
12376
|
recordObserverHealth(
|
|
12377
|
+
cwd,
|
|
12378
|
+
sessionId,
|
|
12105
12379
|
"degraded",
|
|
12106
12380
|
`event telemetry was not fully classified (${issue}); Stop retained the prior cursor`
|
|
12107
12381
|
);
|
|
@@ -12129,7 +12403,7 @@ function turnTransientBlocks(cwd, sessionId, policy, changes) {
|
|
|
12129
12403
|
return e.kind === "file" && e.content != null ? contentHash(e.content) : null;
|
|
12130
12404
|
};
|
|
12131
12405
|
const findings = transientFindings(events, persistent, policy, finalHash);
|
|
12132
|
-
recordWarns(findings.filter((f) => f.severity !== "block"));
|
|
12406
|
+
recordWarns(cwd, sessionId, findings.filter((f) => f.severity !== "block"));
|
|
12133
12407
|
return {
|
|
12134
12408
|
blocks: findings.filter((f) => f.severity === "block"),
|
|
12135
12409
|
commit: () => {
|
|
@@ -12140,20 +12414,23 @@ function turnTransientBlocks(cwd, sessionId, policy, changes) {
|
|
|
12140
12414
|
}
|
|
12141
12415
|
};
|
|
12142
12416
|
}
|
|
12143
|
-
function recordWarns(warns) {
|
|
12417
|
+
function recordWarns(cwd, sessionId, warns) {
|
|
12418
|
+
if (warns.length === 0) return;
|
|
12419
|
+
recordAuditFindings(warns, { cwd, surface: "stop", sessionId });
|
|
12144
12420
|
const log = process.env.TAMPERWARD_DENYLOG;
|
|
12145
|
-
if (!log
|
|
12421
|
+
if (!log) return;
|
|
12146
12422
|
try {
|
|
12147
|
-
|
|
12423
|
+
appendFileSync5(log, warns.map((w) => `warn:${w.rule}:${w.file ?? ""}`).join("\n") + "\n");
|
|
12148
12424
|
} catch {
|
|
12149
12425
|
}
|
|
12150
12426
|
}
|
|
12151
|
-
function recordObserverHealth(state, reason) {
|
|
12427
|
+
function recordObserverHealth(cwd, sessionId, state, reason) {
|
|
12428
|
+
recordAuditSignal("transient-observer", "warn", { cwd, surface: "stop", sessionId });
|
|
12152
12429
|
const log = process.env.TAMPERWARD_DENYLOG;
|
|
12153
12430
|
if (!log) return;
|
|
12154
12431
|
const detail = (reason ?? "").replace(/[\r\n]+/g, " ").trim();
|
|
12155
12432
|
try {
|
|
12156
|
-
|
|
12433
|
+
appendFileSync5(
|
|
12157
12434
|
log,
|
|
12158
12435
|
`warn:transient-observer:${state}${detail ? ":" + detail : ""}
|
|
12159
12436
|
`
|
|
@@ -12161,9 +12438,13 @@ function recordObserverHealth(state, reason) {
|
|
|
12161
12438
|
} catch {
|
|
12162
12439
|
}
|
|
12163
12440
|
}
|
|
12164
|
-
function stopVerdict(input, defaultCwd) {
|
|
12441
|
+
function stopVerdict(input, defaultCwd, trustedRoot) {
|
|
12165
12442
|
if (input.stop_hook_active) return { exitCode: 0, stdout: "" };
|
|
12166
12443
|
const sessionCwd = input.cwd ?? defaultCwd ?? process.cwd();
|
|
12444
|
+
if (trustedRoot !== void 0) {
|
|
12445
|
+
const v = validateClaimAgainstRoot(input.cwd, trustedRoot, defaultCwd ?? process.cwd());
|
|
12446
|
+
if (!v.ok) return failClosed("Stop", `repository identity claim rejected: ${v.rejected}`);
|
|
12447
|
+
}
|
|
12167
12448
|
const ctx = repoContext(sessionCwd);
|
|
12168
12449
|
if (!ctx) {
|
|
12169
12450
|
const why = outsideRepository(sessionCwd);
|
|
@@ -12194,28 +12475,28 @@ function stopVerdict(input, defaultCwd) {
|
|
|
12194
12475
|
saveTurnTree(cwd, input.session_id, current);
|
|
12195
12476
|
}
|
|
12196
12477
|
} catch (e) {
|
|
12197
|
-
return failClosed("Stop", errText2(e));
|
|
12478
|
+
return failClosed("Stop", errText2(e), { cwd: sessionCwd, sessionId: input.session_id });
|
|
12198
12479
|
}
|
|
12199
12480
|
if (blocks2.length === 0) {
|
|
12200
12481
|
advanceTurnBaseline(cwd, input.session_id);
|
|
12201
12482
|
commitCursor();
|
|
12202
12483
|
}
|
|
12203
|
-
return verdict(blocks2, "Stop");
|
|
12484
|
+
return verdict(blocks2, "Stop", { cwd, sessionId: input.session_id });
|
|
12204
12485
|
}
|
|
12205
12486
|
function emit(r) {
|
|
12206
12487
|
if (r.stdout) process.stdout.write(r.stdout);
|
|
12207
12488
|
return r.exitCode;
|
|
12208
12489
|
}
|
|
12209
|
-
function preToolUseFromRaw(raw, defaultCwd) {
|
|
12490
|
+
function preToolUseFromRaw(raw, defaultCwd, trustedRoot) {
|
|
12210
12491
|
try {
|
|
12211
|
-
return preToolUseVerdict(parseInput(raw), defaultCwd);
|
|
12492
|
+
return preToolUseVerdict(parseInput(raw), defaultCwd, trustedRoot);
|
|
12212
12493
|
} catch (e) {
|
|
12213
12494
|
return failClosed("PreToolUse", errText2(e));
|
|
12214
12495
|
}
|
|
12215
12496
|
}
|
|
12216
|
-
function stopFromRaw(raw, defaultCwd) {
|
|
12497
|
+
function stopFromRaw(raw, defaultCwd, trustedRoot) {
|
|
12217
12498
|
try {
|
|
12218
|
-
return stopVerdict(parseInput(raw), defaultCwd);
|
|
12499
|
+
return stopVerdict(parseInput(raw), defaultCwd, trustedRoot);
|
|
12219
12500
|
} catch (e) {
|
|
12220
12501
|
return failClosed("Stop", errText2(e));
|
|
12221
12502
|
}
|
|
@@ -12258,6 +12539,7 @@ var init_hook = __esm({
|
|
|
12258
12539
|
init_disk();
|
|
12259
12540
|
init_narrow();
|
|
12260
12541
|
init_repo_context();
|
|
12542
|
+
init_audit();
|
|
12261
12543
|
snapshotCache = null;
|
|
12262
12544
|
HookInputError = class extends Error {
|
|
12263
12545
|
};
|
|
@@ -12266,7 +12548,7 @@ var init_hook = __esm({
|
|
|
12266
12548
|
|
|
12267
12549
|
// src/ptree-cache.ts
|
|
12268
12550
|
import { lstatSync as lstatSync5 } from "node:fs";
|
|
12269
|
-
import { join as
|
|
12551
|
+
import { join as join14 } from "node:path";
|
|
12270
12552
|
function keyOf(st) {
|
|
12271
12553
|
return `${st.dev}:${st.ino}:${st.size}:${st.mode}:${st.mtimeNs}:${st.ctimeNs}`;
|
|
12272
12554
|
}
|
|
@@ -12306,7 +12588,7 @@ var init_ptree_cache = __esm({
|
|
|
12306
12588
|
/** A path the caller has reason to distrust (an fs event, a sanctioned edit):
|
|
12307
12589
|
* its entry is forgotten so the next walk hashes it whatever its stat says. */
|
|
12308
12590
|
markDirty(cwd, rel) {
|
|
12309
|
-
this.map.delete(
|
|
12591
|
+
this.map.delete(join14(cwd, rel));
|
|
12310
12592
|
}
|
|
12311
12593
|
/**
|
|
12312
12594
|
* The snapshot entry for `rel`, from the cache when the six-field stat key
|
|
@@ -12316,7 +12598,7 @@ var init_ptree_cache = __esm({
|
|
|
12316
12598
|
* time, the way the in-process path treats them.
|
|
12317
12599
|
*/
|
|
12318
12600
|
entry(cwd, rel, compute) {
|
|
12319
|
-
const abs =
|
|
12601
|
+
const abs = join14(cwd, rel);
|
|
12320
12602
|
let st;
|
|
12321
12603
|
try {
|
|
12322
12604
|
st = lstatSync5(abs, { bigint: true });
|
|
@@ -12359,13 +12641,13 @@ var init_ptree_cache = __esm({
|
|
|
12359
12641
|
});
|
|
12360
12642
|
|
|
12361
12643
|
// src/cli/hook-service.ts
|
|
12362
|
-
import { chmodSync, existsSync as
|
|
12644
|
+
import { chmodSync, existsSync as existsSync11, lstatSync as lstatSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync12, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "node:fs";
|
|
12363
12645
|
import { createConnection as createConnection2, createServer } from "node:net";
|
|
12364
|
-
import { realpathSync as
|
|
12646
|
+
import { realpathSync as realpathSync4 } from "node:fs";
|
|
12365
12647
|
import { sep } from "node:path";
|
|
12366
12648
|
function readServiceState(paths) {
|
|
12367
12649
|
try {
|
|
12368
|
-
const v = JSON.parse(
|
|
12650
|
+
const v = JSON.parse(readFileSync12(paths.state, "utf8"));
|
|
12369
12651
|
if (!isRecord(v)) return null;
|
|
12370
12652
|
const { pid, version, root, started_at } = v;
|
|
12371
12653
|
if (typeof pid !== "number" || typeof version !== "string" || typeof root !== "string" || typeof started_at !== "string") return null;
|
|
@@ -12384,12 +12666,12 @@ function pidAlive2(pid) {
|
|
|
12384
12666
|
}
|
|
12385
12667
|
}
|
|
12386
12668
|
function listening(socket) {
|
|
12387
|
-
return new Promise((
|
|
12669
|
+
return new Promise((resolve17) => {
|
|
12388
12670
|
const sock = createConnection2(socket);
|
|
12389
12671
|
const done = (v) => {
|
|
12390
12672
|
clearTimeout(timer);
|
|
12391
12673
|
sock.destroy();
|
|
12392
|
-
|
|
12674
|
+
resolve17(v);
|
|
12393
12675
|
};
|
|
12394
12676
|
const timer = setTimeout(() => done(false), 1e3);
|
|
12395
12677
|
sock.once("connect", () => done(true));
|
|
@@ -12399,7 +12681,7 @@ function listening(socket) {
|
|
|
12399
12681
|
function within(root, cwd) {
|
|
12400
12682
|
let real;
|
|
12401
12683
|
try {
|
|
12402
|
-
real =
|
|
12684
|
+
real = realpathSync4(cwd);
|
|
12403
12685
|
} catch {
|
|
12404
12686
|
return false;
|
|
12405
12687
|
}
|
|
@@ -12447,7 +12729,7 @@ function removeQuietly(p) {
|
|
|
12447
12729
|
}
|
|
12448
12730
|
}
|
|
12449
12731
|
function ensurePrivateDir(dir, uid) {
|
|
12450
|
-
if (!
|
|
12732
|
+
if (!existsSync11(dir)) mkdirSync6(dir, { recursive: true, mode: 448 });
|
|
12451
12733
|
const st = lstatSync6(dir);
|
|
12452
12734
|
if (!st.isDirectory()) throw new Error(`${dir} is not a directory`);
|
|
12453
12735
|
if (st.uid !== uid) throw new Error(`${dir} is owned by uid ${st.uid}, not ${uid}; refusing to serve from it`);
|
|
@@ -12464,7 +12746,7 @@ async function startHookService(opts) {
|
|
|
12464
12746
|
if (uid === void 0) throw new Error("no uid on this platform; refusing to start");
|
|
12465
12747
|
let root;
|
|
12466
12748
|
try {
|
|
12467
|
-
root =
|
|
12749
|
+
root = realpathSync4(repoRoot(realpathSync4(opts.root)));
|
|
12468
12750
|
} catch {
|
|
12469
12751
|
throw new Error(`cannot resolve ${opts.root}`);
|
|
12470
12752
|
}
|
|
@@ -12542,11 +12824,11 @@ async function startHookService(opts) {
|
|
|
12542
12824
|
});
|
|
12543
12825
|
const umask = process.umask(63);
|
|
12544
12826
|
try {
|
|
12545
|
-
await new Promise((
|
|
12827
|
+
await new Promise((resolve17, reject) => {
|
|
12546
12828
|
server.once("error", reject);
|
|
12547
12829
|
server.listen(paths.socket, () => {
|
|
12548
12830
|
server.off("error", reject);
|
|
12549
|
-
|
|
12831
|
+
resolve17();
|
|
12550
12832
|
});
|
|
12551
12833
|
});
|
|
12552
12834
|
} finally {
|
|
@@ -12563,14 +12845,14 @@ async function startHookService(opts) {
|
|
|
12563
12845
|
get served() {
|
|
12564
12846
|
return served;
|
|
12565
12847
|
},
|
|
12566
|
-
close: () => new Promise((
|
|
12567
|
-
if (closed) return
|
|
12848
|
+
close: () => new Promise((resolve17) => {
|
|
12849
|
+
if (closed) return resolve17();
|
|
12568
12850
|
closed = true;
|
|
12569
12851
|
setSnapshotCache(null);
|
|
12570
12852
|
server.close(() => {
|
|
12571
12853
|
removeQuietly(paths.socket);
|
|
12572
12854
|
removeQuietly(paths.state);
|
|
12573
|
-
|
|
12855
|
+
resolve17();
|
|
12574
12856
|
});
|
|
12575
12857
|
})
|
|
12576
12858
|
};
|
|
@@ -12599,7 +12881,7 @@ async function stopHookService(paths) {
|
|
|
12599
12881
|
process.kill(state.pid, "SIGTERM");
|
|
12600
12882
|
} catch {
|
|
12601
12883
|
}
|
|
12602
|
-
const gone = () => !pidAlive2(state.pid) || !
|
|
12884
|
+
const gone = () => !pidAlive2(state.pid) || !existsSync11(paths.socket);
|
|
12603
12885
|
const until = Date.now() + STOP_WAIT_MS;
|
|
12604
12886
|
while (!gone() && Date.now() < until) sleepSync(50);
|
|
12605
12887
|
if (!gone()) throw new Error(`hook service pid ${state.pid} did not exit within ${STOP_WAIT_MS / 1e3}s`);
|
|
@@ -12784,7 +13066,7 @@ var init_verifier_limits = __esm({
|
|
|
12784
13066
|
});
|
|
12785
13067
|
|
|
12786
13068
|
// src/safe-write.ts
|
|
12787
|
-
import { randomBytes } from "node:crypto";
|
|
13069
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
12788
13070
|
import { lstatSync as lstatSync7, renameSync as renameSync3, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "node:fs";
|
|
12789
13071
|
function writeTargetKind(path) {
|
|
12790
13072
|
let st;
|
|
@@ -12813,7 +13095,7 @@ function existingMode(path, fallback) {
|
|
|
12813
13095
|
return lstatSync7(path).mode & 511;
|
|
12814
13096
|
}
|
|
12815
13097
|
function atomicReplaceFile(path, content, mode) {
|
|
12816
|
-
const tmp = `${path}.${process.pid}.${
|
|
13098
|
+
const tmp = `${path}.${process.pid}.${randomBytes2(8).toString("hex")}.tmp`;
|
|
12817
13099
|
try {
|
|
12818
13100
|
writeFileSync7(tmp, content, { encoding: "utf8", flag: "wx", mode });
|
|
12819
13101
|
renameSync3(tmp, path);
|
|
@@ -12830,9 +13112,9 @@ var init_safe_write = __esm({
|
|
|
12830
13112
|
|
|
12831
13113
|
// src/cli/init.ts
|
|
12832
13114
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
12833
|
-
import { createHash as
|
|
12834
|
-
import { existsSync as
|
|
12835
|
-
import { basename as basename3, dirname as
|
|
13115
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
13116
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync13, realpathSync as realpathSync5 } from "node:fs";
|
|
13117
|
+
import { basename as basename3, dirname as dirname5, isAbsolute as isAbsolute9, join as join15, relative as relative3, resolve as resolve5 } from "node:path";
|
|
12836
13118
|
function gitConfig(cwd, key2) {
|
|
12837
13119
|
try {
|
|
12838
13120
|
return execFileSync6("git", ["config", "--get", key2], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || null;
|
|
@@ -12880,7 +13162,7 @@ function stalePin(command) {
|
|
|
12880
13162
|
return pin !== null && (pin !== TW_VERSION || !OURS.test(command));
|
|
12881
13163
|
}
|
|
12882
13164
|
function bodyHash(body) {
|
|
12883
|
-
return
|
|
13165
|
+
return createHash7("sha256").update(body).digest("hex").slice(0, 16);
|
|
12884
13166
|
}
|
|
12885
13167
|
function workflowFile(body) {
|
|
12886
13168
|
return `${WORKFLOW_MARK} v${TW_VERSION} sha256:${bodyHash(body)}
|
|
@@ -12925,34 +13207,34 @@ function verifierCandidates(cwd) {
|
|
|
12925
13207
|
if (!candidates.includes(command)) candidates.push(command);
|
|
12926
13208
|
};
|
|
12927
13209
|
try {
|
|
12928
|
-
const pkg = JSON.parse(
|
|
13210
|
+
const pkg = JSON.parse(readFileSync13(join15(cwd, "package.json"), "utf8"));
|
|
12929
13211
|
const test = isRecord(pkg) && isRecord(pkg.scripts) ? pkg.scripts.test : void 0;
|
|
12930
13212
|
if (typeof test === "string" && test.trim() !== "" && !/error:\s*no test specified/i.test(test)) add("npm test");
|
|
12931
13213
|
} catch {
|
|
12932
13214
|
}
|
|
12933
13215
|
try {
|
|
12934
|
-
const pyproject =
|
|
13216
|
+
const pyproject = readFileSync13(join15(cwd, "pyproject.toml"), "utf8");
|
|
12935
13217
|
if (/^\s*\[tool\.pytest(?:\.ini_options)?\]\s*$/m.test(pyproject)) add("pytest");
|
|
12936
13218
|
} catch {
|
|
12937
13219
|
}
|
|
12938
|
-
if (
|
|
13220
|
+
if (existsSync12(join15(cwd, "pytest.ini"))) add("pytest");
|
|
12939
13221
|
try {
|
|
12940
|
-
const setupCfg =
|
|
13222
|
+
const setupCfg = readFileSync13(join15(cwd, "setup.cfg"), "utf8");
|
|
12941
13223
|
if (/^\s*\[tool:pytest\]\s*$/m.test(setupCfg)) add("pytest");
|
|
12942
13224
|
} catch {
|
|
12943
13225
|
}
|
|
12944
13226
|
try {
|
|
12945
|
-
const tox =
|
|
13227
|
+
const tox = readFileSync13(join15(cwd, "tox.ini"), "utf8");
|
|
12946
13228
|
if (/^\s*\[tox\]\s*$/m.test(tox)) add("tox");
|
|
12947
13229
|
} catch {
|
|
12948
13230
|
}
|
|
12949
13231
|
try {
|
|
12950
|
-
const cargo =
|
|
13232
|
+
const cargo = readFileSync13(join15(cwd, "Cargo.toml"), "utf8");
|
|
12951
13233
|
if (/^\s*\[(?:package|workspace)\]\s*(?:#.*)?$/m.test(cargo)) add("cargo test");
|
|
12952
13234
|
} catch {
|
|
12953
13235
|
}
|
|
12954
13236
|
try {
|
|
12955
|
-
const goMod =
|
|
13237
|
+
const goMod = readFileSync13(join15(cwd, "go.mod"), "utf8");
|
|
12956
13238
|
if (/^\s*module\s+\S+\s*(?:\/\/.*)?$/m.test(goMod)) add("go test ./...");
|
|
12957
13239
|
} catch {
|
|
12958
13240
|
}
|
|
@@ -12992,10 +13274,10 @@ function verifierSetupMessage(cwd) {
|
|
|
12992
13274
|
return lines.join("\n");
|
|
12993
13275
|
}
|
|
12994
13276
|
function planPolicy(cwd) {
|
|
12995
|
-
const path =
|
|
13277
|
+
const path = join15(cwd, POLICY_FILE);
|
|
12996
13278
|
const refused = refuseNonRegular(path);
|
|
12997
13279
|
if (refused) return { item: "policy", path: POLICY_FILE, status: "error", detail: refused };
|
|
12998
|
-
if (
|
|
13280
|
+
if (existsSync12(path)) {
|
|
12999
13281
|
try {
|
|
13000
13282
|
loadPolicy(cwd);
|
|
13001
13283
|
} catch (e) {
|
|
@@ -13013,8 +13295,8 @@ function planPolicy(cwd) {
|
|
|
13013
13295
|
}
|
|
13014
13296
|
function planGitignore(cwd) {
|
|
13015
13297
|
const rel = ".gitignore";
|
|
13016
|
-
const path =
|
|
13017
|
-
if (!
|
|
13298
|
+
const path = join15(cwd, rel);
|
|
13299
|
+
if (!existsSync12(join15(cwd, "node_modules"))) return null;
|
|
13018
13300
|
let existing = "";
|
|
13019
13301
|
let kind;
|
|
13020
13302
|
try {
|
|
@@ -13024,7 +13306,7 @@ function planGitignore(cwd) {
|
|
|
13024
13306
|
}
|
|
13025
13307
|
const refused = refuseNonRegular(path);
|
|
13026
13308
|
if (refused) return { item: "gitignore", path: rel, status: "error", detail: refused };
|
|
13027
|
-
if (kind === "file") existing =
|
|
13309
|
+
if (kind === "file") existing = readFileSync13(path, "utf8");
|
|
13028
13310
|
try {
|
|
13029
13311
|
const tracked = execFileSync6("git", ["ls-files", "--cached", "--", "node_modules"], {
|
|
13030
13312
|
cwd,
|
|
@@ -13075,14 +13357,14 @@ function gateVerdicts(event, groups, needle) {
|
|
|
13075
13357
|
}
|
|
13076
13358
|
function planClaudeHooks(cwd) {
|
|
13077
13359
|
const rel = ".claude/settings.json";
|
|
13078
|
-
const path =
|
|
13360
|
+
const path = join15(cwd, rel);
|
|
13079
13361
|
const refused = refuseNonRegular(path);
|
|
13080
13362
|
if (refused) return { item: "agent", path: rel, status: "error", detail: refused };
|
|
13081
13363
|
let settings = {};
|
|
13082
|
-
if (
|
|
13364
|
+
if (existsSync12(path)) {
|
|
13083
13365
|
let parsed;
|
|
13084
13366
|
try {
|
|
13085
|
-
parsed = JSON.parse(
|
|
13367
|
+
parsed = JSON.parse(readFileSync13(path, "utf8"));
|
|
13086
13368
|
} catch {
|
|
13087
13369
|
return {
|
|
13088
13370
|
item: "agent",
|
|
@@ -13131,7 +13413,7 @@ function planClaudeHooks(cwd) {
|
|
|
13131
13413
|
return {
|
|
13132
13414
|
item: "agent",
|
|
13133
13415
|
path: rel,
|
|
13134
|
-
status:
|
|
13416
|
+
status: existsSync12(path) ? "update" : "create",
|
|
13135
13417
|
detail: [
|
|
13136
13418
|
needPre && "wire PreToolUse deny",
|
|
13137
13419
|
needStop && "wire Stop sweep",
|
|
@@ -13164,7 +13446,7 @@ function planClaudeHooks(cwd) {
|
|
|
13164
13446
|
(hooks.Stop ??= []).push({ hooks: [{ type: "command", command: SWEEP_CMD }] });
|
|
13165
13447
|
}
|
|
13166
13448
|
if (needDisableFalse) settings.disableAllHooks = false;
|
|
13167
|
-
|
|
13449
|
+
mkdirSync7(dirname5(path), { recursive: true });
|
|
13168
13450
|
atomicReplaceFile(path, JSON.stringify(settings, null, 2) + "\n", existingMode(path, 420));
|
|
13169
13451
|
}
|
|
13170
13452
|
};
|
|
@@ -13172,14 +13454,14 @@ function planClaudeHooks(cwd) {
|
|
|
13172
13454
|
function hooksDir(cwd) {
|
|
13173
13455
|
try {
|
|
13174
13456
|
const out3 = execFileSync6("git", ["rev-parse", "--git-path", "hooks"], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
13175
|
-
if (out3) return
|
|
13457
|
+
if (out3) return resolve5(cwd, out3);
|
|
13176
13458
|
} catch {
|
|
13177
13459
|
}
|
|
13178
|
-
return
|
|
13460
|
+
return existsSync12(join15(cwd, ".git")) ? join15(cwd, ".git", "hooks") : null;
|
|
13179
13461
|
}
|
|
13180
13462
|
function display(cwd, path) {
|
|
13181
13463
|
const rel = relative3(cwd, path);
|
|
13182
|
-
return rel && !rel.startsWith("..") && !
|
|
13464
|
+
return rel && !rel.startsWith("..") && !isAbsolute9(rel) ? rel : path;
|
|
13183
13465
|
}
|
|
13184
13466
|
function unconditionalExitAt(lines) {
|
|
13185
13467
|
let depth = 0;
|
|
@@ -13202,29 +13484,29 @@ function planPreCommit(cwd) {
|
|
|
13202
13484
|
const skip = (detail) => ({ item: "pre-commit", path: "(none)", status: "skip", detail });
|
|
13203
13485
|
let path;
|
|
13204
13486
|
let note;
|
|
13205
|
-
if (
|
|
13206
|
-
path =
|
|
13487
|
+
if (existsSync12(join15(cwd, ".husky"))) {
|
|
13488
|
+
path = join15(cwd, ".husky", "pre-commit");
|
|
13207
13489
|
note = "husky";
|
|
13208
13490
|
} else {
|
|
13209
|
-
const managed = MANAGED_HOOKS.find((m) => m.configs.some((c) =>
|
|
13491
|
+
const managed = MANAGED_HOOKS.find((m) => m.configs.some((c) => existsSync12(join15(cwd, c))));
|
|
13210
13492
|
if (managed) {
|
|
13211
13493
|
return skip(`${managed.name} manages this repository's hooks and regenerates them on install, so a line in the hooks directory would not last \u2014 ${managed.hint}`);
|
|
13212
13494
|
}
|
|
13213
13495
|
const dir = hooksDir(cwd);
|
|
13214
13496
|
if (!dir) return skip("not a git repo and no .husky/ \u2014 nothing to wire");
|
|
13215
|
-
if (basename3(dir) === "_" && basename3(
|
|
13216
|
-
path =
|
|
13497
|
+
if (basename3(dir) === "_" && basename3(dirname5(dir)) === ".husky") {
|
|
13498
|
+
path = join15(dirname5(dir), "pre-commit");
|
|
13217
13499
|
note = "husky";
|
|
13218
13500
|
} else {
|
|
13219
|
-
path =
|
|
13501
|
+
path = join15(dir, "pre-commit");
|
|
13220
13502
|
const rel2 = display(cwd, dir);
|
|
13221
|
-
note = rel2 ===
|
|
13503
|
+
note = rel2 === join15(".git", "hooks") ? "plain git hook (local-only: .git/hooks is not committed \u2014 consider husky to share it)" : `git hooks directory ${rel2} (core.hooksPath)`;
|
|
13222
13504
|
}
|
|
13223
13505
|
}
|
|
13224
13506
|
const rel = display(cwd, path);
|
|
13225
13507
|
const refused = refuseNonRegular(path);
|
|
13226
13508
|
if (refused) return { item: "pre-commit", path: rel, status: "error", detail: refused };
|
|
13227
|
-
const existing =
|
|
13509
|
+
const existing = existsSync12(path) ? readFileSync13(path, "utf8") : null;
|
|
13228
13510
|
const hookMode = () => existingMode(path, 493) | 73;
|
|
13229
13511
|
if (existing === null) {
|
|
13230
13512
|
return {
|
|
@@ -13233,7 +13515,7 @@ function planPreCommit(cwd) {
|
|
|
13233
13515
|
status: "create",
|
|
13234
13516
|
detail: `create via ${note}`,
|
|
13235
13517
|
apply: () => {
|
|
13236
|
-
|
|
13518
|
+
mkdirSync7(dirname5(path), { recursive: true });
|
|
13237
13519
|
atomicReplaceFile(path, `#!/bin/sh
|
|
13238
13520
|
${block.join("\n")}
|
|
13239
13521
|
`, hookMode());
|
|
@@ -13310,12 +13592,12 @@ ${block.join("\n")}
|
|
|
13310
13592
|
function planCodeowners(cwd) {
|
|
13311
13593
|
const rel = ".github/CODEOWNERS";
|
|
13312
13594
|
const existingRel = [".github/CODEOWNERS", "CODEOWNERS", "docs/CODEOWNERS"].find(
|
|
13313
|
-
(r) => writeTargetKind(
|
|
13595
|
+
(r) => writeTargetKind(join15(cwd, r)) !== "absent"
|
|
13314
13596
|
);
|
|
13315
|
-
const path =
|
|
13597
|
+
const path = join15(cwd, existingRel ?? rel);
|
|
13316
13598
|
const refused = refuseNonRegular(path);
|
|
13317
13599
|
if (refused) return { item: "codeowners", path: existingRel ?? rel, status: "error", detail: refused };
|
|
13318
|
-
const existing = existingRel ?
|
|
13600
|
+
const existing = existingRel ? readFileSync13(path, "utf8") : null;
|
|
13319
13601
|
const missing = CODEOWNERS_PATHS.filter((p) => existing === null || !coveredBy(existing, p));
|
|
13320
13602
|
if (existingRel !== void 0 && missing.length === 0) {
|
|
13321
13603
|
return { item: "codeowners", path: existingRel, status: "ok", detail: "gate paths already have code owners" };
|
|
@@ -13341,21 +13623,21 @@ ${CODEOWNERS_MARK}
|
|
|
13341
13623
|
warning: `${inferred.owner} was inferred from the origin remote and is not confirmed to be a user. If "${inferred.owner.slice(1)}" is an organisation, replace it with ${inferred.owner}/<team> in CODEOWNERS: GitHub rejects a bare organisation as an owner, and a rule with an invalid owner protects nothing.`
|
|
13342
13624
|
} : {},
|
|
13343
13625
|
apply: () => {
|
|
13344
|
-
|
|
13626
|
+
mkdirSync7(dirname5(path), { recursive: true });
|
|
13345
13627
|
atomicReplaceFile(path, existing === null ? block.replace(/^\n/, "") : existing.replace(/\n?$/, "\n") + block, existingMode(path, 420));
|
|
13346
13628
|
}
|
|
13347
13629
|
};
|
|
13348
13630
|
}
|
|
13349
13631
|
function planWorkflow(cwd, force) {
|
|
13350
13632
|
const rel = ".github/workflows/tamperward.yml";
|
|
13351
|
-
const path =
|
|
13633
|
+
const path = join15(cwd, rel);
|
|
13352
13634
|
const write = () => {
|
|
13353
|
-
|
|
13635
|
+
mkdirSync7(dirname5(path), { recursive: true });
|
|
13354
13636
|
atomicReplaceFile(path, workflowFile(WORKFLOW_CONTENT), existingMode(path, 420));
|
|
13355
13637
|
};
|
|
13356
13638
|
const refused = refuseNonRegular(path);
|
|
13357
13639
|
if (refused) return { item: "ci", path: rel, status: "error", detail: refused };
|
|
13358
|
-
if (!
|
|
13640
|
+
if (!existsSync12(path)) {
|
|
13359
13641
|
return {
|
|
13360
13642
|
item: "ci",
|
|
13361
13643
|
path: rel,
|
|
@@ -13366,7 +13648,7 @@ function planWorkflow(cwd, force) {
|
|
|
13366
13648
|
}
|
|
13367
13649
|
let src;
|
|
13368
13650
|
try {
|
|
13369
|
-
src =
|
|
13651
|
+
src = readFileSync13(path, "utf8");
|
|
13370
13652
|
} catch {
|
|
13371
13653
|
return { item: "ci", path: rel, status: "error", detail: "exists but cannot be read \u2014 refusing to overwrite" };
|
|
13372
13654
|
}
|
|
@@ -13409,7 +13691,7 @@ function planned(item, path, plan) {
|
|
|
13409
13691
|
}
|
|
13410
13692
|
function sameDir(a, b) {
|
|
13411
13693
|
try {
|
|
13412
|
-
return
|
|
13694
|
+
return realpathSync5(a) === realpathSync5(b);
|
|
13413
13695
|
} catch {
|
|
13414
13696
|
return false;
|
|
13415
13697
|
}
|
|
@@ -13427,7 +13709,7 @@ function planInit(cwd, opts = {}) {
|
|
|
13427
13709
|
];
|
|
13428
13710
|
}
|
|
13429
13711
|
function runInit(opts) {
|
|
13430
|
-
const requested =
|
|
13712
|
+
const requested = resolve5(opts.cwd ?? process.cwd());
|
|
13431
13713
|
const cwd = repoRoot(requested);
|
|
13432
13714
|
const w = opts.quiet ? { write: (_text) => true } : process.stdout;
|
|
13433
13715
|
if (cwd !== requested && !sameDir(cwd, requested)) {
|
|
@@ -13625,7 +13907,7 @@ jobs:
|
|
|
13625
13907
|
|
|
13626
13908
|
// src/fingerprint.ts
|
|
13627
13909
|
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
13628
|
-
import { createHash as
|
|
13910
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
13629
13911
|
function treeFingerprint(cwd, keepIgnored) {
|
|
13630
13912
|
const listed = execFileSync7("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
13631
13913
|
cwd,
|
|
@@ -13634,7 +13916,7 @@ function treeFingerprint(cwd, keepIgnored) {
|
|
|
13634
13916
|
env: trustedGitEnv()
|
|
13635
13917
|
}).split("\0").filter(Boolean);
|
|
13636
13918
|
const files = [.../* @__PURE__ */ new Set([...listed, ...keepIgnored ? ignoredPaths({ cwd }, keepIgnored) : []])].sort();
|
|
13637
|
-
const h =
|
|
13919
|
+
const h = createHash8("sha256");
|
|
13638
13920
|
for (const rel of files) {
|
|
13639
13921
|
h.update(rel);
|
|
13640
13922
|
h.update("\0");
|
|
@@ -13658,32 +13940,32 @@ var init_fingerprint = __esm({
|
|
|
13658
13940
|
});
|
|
13659
13941
|
|
|
13660
13942
|
// src/dependency-env.ts
|
|
13661
|
-
import { createHash as
|
|
13943
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
13662
13944
|
import {
|
|
13663
|
-
existsSync as
|
|
13945
|
+
existsSync as existsSync13,
|
|
13664
13946
|
lstatSync as lstatSync8,
|
|
13665
13947
|
readdirSync as readdirSync3,
|
|
13666
|
-
readFileSync as
|
|
13948
|
+
readFileSync as readFileSync14,
|
|
13667
13949
|
readlinkSync as readlinkSync2,
|
|
13668
|
-
realpathSync as
|
|
13950
|
+
realpathSync as realpathSync6,
|
|
13669
13951
|
statSync as statSync3
|
|
13670
13952
|
} from "node:fs";
|
|
13671
13953
|
import {
|
|
13672
13954
|
basename as basename4,
|
|
13673
13955
|
delimiter,
|
|
13674
|
-
dirname as
|
|
13675
|
-
isAbsolute as
|
|
13676
|
-
join as
|
|
13956
|
+
dirname as dirname6,
|
|
13957
|
+
isAbsolute as isAbsolute10,
|
|
13958
|
+
join as join16,
|
|
13677
13959
|
relative as relative4,
|
|
13678
|
-
resolve as
|
|
13960
|
+
resolve as resolve6,
|
|
13679
13961
|
sep as sep2
|
|
13680
13962
|
} from "node:path";
|
|
13681
13963
|
function metricsFor(descriptor) {
|
|
13682
13964
|
return descriptor.metrics ??= { fullSnapshots: 0, reusedSnapshots: 0, totalMs: 0 };
|
|
13683
13965
|
}
|
|
13684
13966
|
function inside(root, candidate) {
|
|
13685
|
-
const rel = relative4(
|
|
13686
|
-
return rel === "" || rel !== ".." && !rel.startsWith(".." + sep2) && !
|
|
13967
|
+
const rel = relative4(resolve6(root), resolve6(candidate));
|
|
13968
|
+
return rel === "" || rel !== ".." && !rel.startsWith(".." + sep2) && !isAbsolute10(rel);
|
|
13687
13969
|
}
|
|
13688
13970
|
function modeKind(path) {
|
|
13689
13971
|
const st = lstatSync8(path);
|
|
@@ -13697,7 +13979,7 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
13697
13979
|
let currentReal;
|
|
13698
13980
|
try {
|
|
13699
13981
|
rootStat = lstatSync8(root.path);
|
|
13700
|
-
currentReal =
|
|
13982
|
+
currentReal = realpathSync6(root.path);
|
|
13701
13983
|
} catch (e) {
|
|
13702
13984
|
throw new Unattestable(
|
|
13703
13985
|
root.kind + " root " + JSON.stringify(root.path) + " can no longer be resolved (" + (e instanceof Error ? e.message : String(e)) + ")"
|
|
@@ -13713,7 +13995,7 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
13713
13995
|
}
|
|
13714
13996
|
h.update("root\0" + root.kind + "\0" + root.path + "\0" + root.realPath + "\0" + String(rootStat.mode) + "\0");
|
|
13715
13997
|
if (rootStat.isSymbolicLink()) h.update(readlinkSync2(root.path) + "\0");
|
|
13716
|
-
const cwdReal =
|
|
13998
|
+
const cwdReal = realpathSync6(cwd);
|
|
13717
13999
|
const visited = /* @__PURE__ */ new Set();
|
|
13718
14000
|
const hashFile = (path, label) => {
|
|
13719
14001
|
let st;
|
|
@@ -13721,7 +14003,7 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
13721
14003
|
st = statSync3(path);
|
|
13722
14004
|
if (!st.isFile()) throw new Error("not a regular file");
|
|
13723
14005
|
h.update(label + "\0external-file\0" + path + "\0" + String(st.mode) + "\0");
|
|
13724
|
-
h.update(
|
|
14006
|
+
h.update(readFileSync14(path));
|
|
13725
14007
|
h.update("\0");
|
|
13726
14008
|
} catch (e) {
|
|
13727
14009
|
throw new Unattestable(
|
|
@@ -13730,7 +14012,7 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
13730
14012
|
}
|
|
13731
14013
|
};
|
|
13732
14014
|
const walk2 = (dir, namespace) => {
|
|
13733
|
-
const realDir =
|
|
14015
|
+
const realDir = realpathSync6(dir);
|
|
13734
14016
|
if (visited.has(realDir)) return;
|
|
13735
14017
|
visited.add(realDir);
|
|
13736
14018
|
let entries;
|
|
@@ -13742,7 +14024,7 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
13742
14024
|
);
|
|
13743
14025
|
}
|
|
13744
14026
|
for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
13745
|
-
const path =
|
|
14027
|
+
const path = join16(dir, entry.name);
|
|
13746
14028
|
const rel = namespace ? namespace + "/" + entry.name : entry.name;
|
|
13747
14029
|
let ident;
|
|
13748
14030
|
try {
|
|
@@ -13755,7 +14037,7 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
13755
14037
|
h.update(rel + "\0" + ident.kind + "\0" + String(ident.mode) + "\0");
|
|
13756
14038
|
if (ident.kind === "file") {
|
|
13757
14039
|
try {
|
|
13758
|
-
h.update(
|
|
14040
|
+
h.update(readFileSync14(path));
|
|
13759
14041
|
h.update("\0");
|
|
13760
14042
|
} catch (e) {
|
|
13761
14043
|
throw new Unattestable(
|
|
@@ -13780,7 +14062,7 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
13780
14062
|
h.update(target + "\0");
|
|
13781
14063
|
let resolved;
|
|
13782
14064
|
try {
|
|
13783
|
-
resolved =
|
|
14065
|
+
resolved = realpathSync6(path);
|
|
13784
14066
|
} catch {
|
|
13785
14067
|
continue;
|
|
13786
14068
|
}
|
|
@@ -13811,10 +14093,10 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
13811
14093
|
walk2(root.realPath, "");
|
|
13812
14094
|
}
|
|
13813
14095
|
function snapshot(roots, probes, cwd) {
|
|
13814
|
-
const h =
|
|
14096
|
+
const h = createHash9("sha256");
|
|
13815
14097
|
for (const probe of probes) {
|
|
13816
14098
|
h.update(
|
|
13817
|
-
"probe\0" + probe.kind + "\0" + probe.path + "\0" + (
|
|
14099
|
+
"probe\0" + probe.kind + "\0" + probe.path + "\0" + (existsSync13(probe.path) ? "present" : "absent") + "\0"
|
|
13818
14100
|
);
|
|
13819
14101
|
}
|
|
13820
14102
|
for (const root of roots) fingerprintRoot(root, cwd, h);
|
|
@@ -13835,7 +14117,7 @@ function freezeRoot(kind, path) {
|
|
|
13835
14117
|
if (!statSync3(path).isDirectory()) {
|
|
13836
14118
|
throw new Error("not a directory");
|
|
13837
14119
|
}
|
|
13838
|
-
return { kind, path:
|
|
14120
|
+
return { kind, path: resolve6(path), realPath: realpathSync6(path) };
|
|
13839
14121
|
} catch (e) {
|
|
13840
14122
|
throw new Unattestable(
|
|
13841
14123
|
kind + " root " + JSON.stringify(path) + " is not resolvable (" + (e instanceof Error ? e.message : String(e)) + ")"
|
|
@@ -13871,17 +14153,17 @@ function firstExecutable(command) {
|
|
|
13871
14153
|
function resolveExecutable(token, cwd, env) {
|
|
13872
14154
|
if (!token) return null;
|
|
13873
14155
|
if (token.includes("/") || token.includes("\\")) {
|
|
13874
|
-
const path =
|
|
14156
|
+
const path = isAbsolute10(token) ? token : resolve6(cwd, token);
|
|
13875
14157
|
try {
|
|
13876
|
-
return
|
|
14158
|
+
return existsSync13(path) ? realpathSync6(path) : null;
|
|
13877
14159
|
} catch {
|
|
13878
14160
|
return null;
|
|
13879
14161
|
}
|
|
13880
14162
|
}
|
|
13881
14163
|
for (const entry of (env.PATH ?? "").split(delimiter).filter(Boolean)) {
|
|
13882
|
-
const candidate =
|
|
14164
|
+
const candidate = resolve6(entry, token);
|
|
13883
14165
|
try {
|
|
13884
|
-
if (
|
|
14166
|
+
if (existsSync13(candidate) && statSync3(candidate).isFile()) return realpathSync6(candidate);
|
|
13885
14167
|
} catch {
|
|
13886
14168
|
}
|
|
13887
14169
|
}
|
|
@@ -13898,13 +14180,13 @@ function looksPython(command, executable, token) {
|
|
|
13898
14180
|
}
|
|
13899
14181
|
function inferredVenvFromToken(token, cwd) {
|
|
13900
14182
|
if (!token || !token.includes("/") && !token.includes("\\")) return null;
|
|
13901
|
-
const path =
|
|
14183
|
+
const path = isAbsolute10(token) ? token : resolve6(cwd, token);
|
|
13902
14184
|
if (!pythonName(basename4(path))) return null;
|
|
13903
|
-
const parent =
|
|
14185
|
+
const parent = dirname6(path);
|
|
13904
14186
|
const parentName = basename4(parent).toLowerCase();
|
|
13905
14187
|
if (parentName !== "bin" && parentName !== "scripts") return null;
|
|
13906
|
-
const root =
|
|
13907
|
-
return
|
|
14188
|
+
const root = dirname6(parent);
|
|
14189
|
+
return existsSync13(join16(root, "pyvenv.cfg")) ? root : null;
|
|
13908
14190
|
}
|
|
13909
14191
|
function unsupportedEcosystem(command, token) {
|
|
13910
14192
|
const name = token ? basename4(token).toLowerCase() : "";
|
|
@@ -13916,17 +14198,17 @@ function unsupportedEcosystem(command, token) {
|
|
|
13916
14198
|
}
|
|
13917
14199
|
function inferredVenv(executable) {
|
|
13918
14200
|
if (!executable) return null;
|
|
13919
|
-
const parent =
|
|
14201
|
+
const parent = dirname6(executable);
|
|
13920
14202
|
const parentName = basename4(parent).toLowerCase();
|
|
13921
14203
|
if (parentName !== "bin" && parentName !== "scripts") return null;
|
|
13922
|
-
const root =
|
|
13923
|
-
return
|
|
14204
|
+
const root = dirname6(parent);
|
|
14205
|
+
return existsSync13(join16(root, "pyvenv.cfg")) ? root : null;
|
|
13924
14206
|
}
|
|
13925
14207
|
function venvProblem(root) {
|
|
13926
|
-
const cfg =
|
|
13927
|
-
if (!
|
|
14208
|
+
const cfg = join16(root, "pyvenv.cfg");
|
|
14209
|
+
if (!existsSync13(cfg)) return "VIRTUAL_ENV " + JSON.stringify(root) + " has no pyvenv.cfg";
|
|
13928
14210
|
try {
|
|
13929
|
-
const body =
|
|
14211
|
+
const body = readFileSync14(cfg, "utf8");
|
|
13930
14212
|
if (/^\s*include-system-site-packages\s*=\s*true\s*$/im.test(body)) {
|
|
13931
14213
|
return "Python environment " + JSON.stringify(root) + " enables system site-packages; those external package roots are not bounded";
|
|
13932
14214
|
}
|
|
@@ -13936,7 +14218,7 @@ function venvProblem(root) {
|
|
|
13936
14218
|
return null;
|
|
13937
14219
|
}
|
|
13938
14220
|
function discoverDependencyEnvironment(cwdInput, command, env = process.env) {
|
|
13939
|
-
const cwd =
|
|
14221
|
+
const cwd = resolve6(cwdInput);
|
|
13940
14222
|
const roots = [];
|
|
13941
14223
|
const probes = [];
|
|
13942
14224
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -13950,21 +14232,21 @@ function discoverDependencyEnvironment(cwdInput, command, env = process.env) {
|
|
|
13950
14232
|
}
|
|
13951
14233
|
};
|
|
13952
14234
|
try {
|
|
13953
|
-
const nodeModules =
|
|
13954
|
-
if (
|
|
14235
|
+
const nodeModules = join16(cwd, "node_modules");
|
|
14236
|
+
if (existsSync13(nodeModules)) addRoot("node_modules", nodeModules);
|
|
13955
14237
|
else probes.push({ kind: "node_modules", path: nodeModules });
|
|
13956
14238
|
const token = firstExecutable(command);
|
|
13957
14239
|
const executable = resolveExecutable(token, cwd, env);
|
|
13958
14240
|
let venv = null;
|
|
13959
14241
|
if (env.VIRTUAL_ENV?.trim()) {
|
|
13960
|
-
if (!
|
|
14242
|
+
if (!isAbsolute10(env.VIRTUAL_ENV)) {
|
|
13961
14243
|
throw new Unattestable("VIRTUAL_ENV is relative (" + JSON.stringify(env.VIRTUAL_ENV) + ")");
|
|
13962
14244
|
}
|
|
13963
|
-
venv =
|
|
14245
|
+
venv = resolve6(env.VIRTUAL_ENV);
|
|
13964
14246
|
} else {
|
|
13965
14247
|
venv = inferredVenv(executable) ?? inferredVenvFromToken(token, cwd);
|
|
13966
|
-
if (!venv && /(?:^|\s)uv\s+run(?:\s|$)/.test(command) &&
|
|
13967
|
-
venv =
|
|
14248
|
+
if (!venv && /(?:^|\s)uv\s+run(?:\s|$)/.test(command) && existsSync13(join16(cwd, ".venv", "pyvenv.cfg"))) {
|
|
14249
|
+
venv = join16(cwd, ".venv");
|
|
13968
14250
|
}
|
|
13969
14251
|
}
|
|
13970
14252
|
if (venv) {
|
|
@@ -14000,7 +14282,7 @@ function checkDependencyEnvironment(cwdInput, descriptor) {
|
|
|
14000
14282
|
const fingerprint2 = measuredSnapshot(
|
|
14001
14283
|
descriptor.roots,
|
|
14002
14284
|
descriptor.probes,
|
|
14003
|
-
|
|
14285
|
+
resolve6(cwdInput),
|
|
14004
14286
|
metricsFor(descriptor)
|
|
14005
14287
|
);
|
|
14006
14288
|
if (fingerprint2 !== descriptor.fingerprint) {
|
|
@@ -14013,7 +14295,7 @@ function checkDependencyEnvironment(cwdInput, descriptor) {
|
|
|
14013
14295
|
}
|
|
14014
14296
|
function attestDependencyEnvironment(cwdInput, descriptor) {
|
|
14015
14297
|
const attestation = {
|
|
14016
|
-
cwd:
|
|
14298
|
+
cwd: resolve6(cwdInput),
|
|
14017
14299
|
descriptor,
|
|
14018
14300
|
check: checkDependencyEnvironment(cwdInput, descriptor)
|
|
14019
14301
|
};
|
|
@@ -14021,7 +14303,7 @@ function attestDependencyEnvironment(cwdInput, descriptor) {
|
|
|
14021
14303
|
return attestation;
|
|
14022
14304
|
}
|
|
14023
14305
|
function reuseDependencyEnvironmentAttestation(cwdInput, descriptor, attestation) {
|
|
14024
|
-
if (!attestation || !issuedAttestations.has(attestation) || attestation.descriptor !== descriptor || attestation.cwd !==
|
|
14306
|
+
if (!attestation || !issuedAttestations.has(attestation) || attestation.descriptor !== descriptor || attestation.cwd !== resolve6(cwdInput)) return null;
|
|
14025
14307
|
metricsFor(descriptor).reusedSnapshots += 1;
|
|
14026
14308
|
return attestation.check;
|
|
14027
14309
|
}
|
|
@@ -14070,7 +14352,7 @@ var init_dependency_env = __esm({
|
|
|
14070
14352
|
import { spawnSync } from "node:child_process";
|
|
14071
14353
|
import { mkdtempSync as mkdtempSync2, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "node:fs";
|
|
14072
14354
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
14073
|
-
import { join as
|
|
14355
|
+
import { join as join17 } from "node:path";
|
|
14074
14356
|
function emptySuiteDiagnostics() {
|
|
14075
14357
|
return {
|
|
14076
14358
|
stdout: { ...EMPTY_STREAM },
|
|
@@ -14149,8 +14431,8 @@ function parseCapturedSupervisorResult(stdoutText) {
|
|
|
14149
14431
|
};
|
|
14150
14432
|
}
|
|
14151
14433
|
function runCapturedProcessSync(executable, args, opts) {
|
|
14152
|
-
const stateDir = mkdtempSync2(
|
|
14153
|
-
const configFile =
|
|
14434
|
+
const stateDir = mkdtempSync2(join17(tmpdir3(), "tw-suite-capture-"));
|
|
14435
|
+
const configFile = join17(stateDir, "config.json");
|
|
14154
14436
|
const backstop = opts.backstopMs ?? 3e4;
|
|
14155
14437
|
try {
|
|
14156
14438
|
writeFileSync8(
|
|
@@ -14385,10 +14667,10 @@ if (child) {
|
|
|
14385
14667
|
});
|
|
14386
14668
|
|
|
14387
14669
|
// src/verifier-backend.ts
|
|
14388
|
-
import { createHash as
|
|
14670
|
+
import { createHash as createHash10, randomUUID } from "node:crypto";
|
|
14389
14671
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
14390
|
-
import { accessSync as accessSync2, constants as constants3, readFileSync as
|
|
14391
|
-
import { delimiter as delimiter2, dirname as
|
|
14672
|
+
import { accessSync as accessSync2, constants as constants3, readFileSync as readFileSync16, realpathSync as realpathSync7, statSync as statSync4 } from "node:fs";
|
|
14673
|
+
import { delimiter as delimiter2, dirname as dirname7, join as join18, resolve as resolve7 } from "node:path";
|
|
14392
14674
|
function identity() {
|
|
14393
14675
|
const uid = typeof process.getuid === "function" ? process.getuid() : null;
|
|
14394
14676
|
const groups = typeof process.getgroups === "function" ? process.getgroups() : [];
|
|
@@ -14408,12 +14690,12 @@ function writableByCaller(path) {
|
|
|
14408
14690
|
}
|
|
14409
14691
|
}
|
|
14410
14692
|
function replaceableByCaller(path) {
|
|
14411
|
-
let current =
|
|
14693
|
+
let current = resolve7(path);
|
|
14412
14694
|
if (writableByCaller(current)) return true;
|
|
14413
|
-
current =
|
|
14695
|
+
current = dirname7(current);
|
|
14414
14696
|
while (true) {
|
|
14415
14697
|
if (writableByCaller(current)) return true;
|
|
14416
|
-
const parent =
|
|
14698
|
+
const parent = dirname7(current);
|
|
14417
14699
|
if (parent === current) break;
|
|
14418
14700
|
current = parent;
|
|
14419
14701
|
}
|
|
@@ -14422,17 +14704,17 @@ function replaceableByCaller(path) {
|
|
|
14422
14704
|
function findExecutable(name) {
|
|
14423
14705
|
for (const raw of (process.env.PATH ?? "").split(delimiter2)) {
|
|
14424
14706
|
if (!raw) continue;
|
|
14425
|
-
const candidate =
|
|
14707
|
+
const candidate = join18(raw, name);
|
|
14426
14708
|
try {
|
|
14427
14709
|
accessSync2(candidate, constants3.X_OK);
|
|
14428
|
-
return
|
|
14710
|
+
return realpathSync7(candidate);
|
|
14429
14711
|
} catch {
|
|
14430
14712
|
}
|
|
14431
14713
|
}
|
|
14432
14714
|
return null;
|
|
14433
14715
|
}
|
|
14434
14716
|
function fileSha256(path) {
|
|
14435
|
-
return
|
|
14717
|
+
return createHash10("sha256").update(readFileSync16(path)).digest("hex");
|
|
14436
14718
|
}
|
|
14437
14719
|
function engineClientEnv() {
|
|
14438
14720
|
return {
|
|
@@ -14500,7 +14782,7 @@ function inspectImageMetadata(enginePath, host, image) {
|
|
|
14500
14782
|
function engineStable(backend) {
|
|
14501
14783
|
if (!backend.enginePath || !backend.engineSha256) return false;
|
|
14502
14784
|
try {
|
|
14503
|
-
if (
|
|
14785
|
+
if (realpathSync7(backend.enginePath) !== backend.enginePath) return false;
|
|
14504
14786
|
if (replaceableByCaller(backend.enginePath)) return false;
|
|
14505
14787
|
return fileSha256(backend.enginePath) === backend.engineSha256;
|
|
14506
14788
|
} catch {
|
|
@@ -14754,7 +15036,7 @@ function runContainerStage(backend, dir, command, budgetSecs) {
|
|
|
14754
15036
|
const args = containerRunArgs({
|
|
14755
15037
|
image: backend.image,
|
|
14756
15038
|
name,
|
|
14757
|
-
workspace:
|
|
15039
|
+
workspace: resolve7(dir),
|
|
14758
15040
|
command,
|
|
14759
15041
|
uid,
|
|
14760
15042
|
gid,
|
|
@@ -14765,7 +15047,7 @@ function runContainerStage(backend, dir, command, budgetSecs) {
|
|
|
14765
15047
|
backend.enginePath,
|
|
14766
15048
|
dockerArgs(backend.daemonHost, args),
|
|
14767
15049
|
{
|
|
14768
|
-
cwd:
|
|
15050
|
+
cwd: dirname7(backend.enginePath),
|
|
14769
15051
|
env: engineClientEnv(),
|
|
14770
15052
|
timeoutMs: budgetSecs * 1e3,
|
|
14771
15053
|
backstopMs: 3e4
|
|
@@ -14865,10 +15147,10 @@ var init_verifier_backend = __esm({
|
|
|
14865
15147
|
|
|
14866
15148
|
// src/cli/verify.ts
|
|
14867
15149
|
import { execFileSync as execFileSync8, spawnSync as spawnSync3 } from "node:child_process";
|
|
14868
|
-
import { createHash as
|
|
14869
|
-
import { chmodSync as chmodSync2, cpSync, lstatSync as lstatSync9, mkdirSync as
|
|
15150
|
+
import { createHash as createHash11 } from "node:crypto";
|
|
15151
|
+
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";
|
|
14870
15152
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
14871
|
-
import { dirname as
|
|
15153
|
+
import { dirname as dirname8, isAbsolute as isAbsolute11, join as join19, relative as relative5, resolve as resolve8, sep as sep3 } from "node:path";
|
|
14872
15154
|
function oracleAssuranceReport() {
|
|
14873
15155
|
return {
|
|
14874
15156
|
level: "suite-exit-only",
|
|
@@ -14894,8 +15176,8 @@ function baseIsAncestorOfHead(base, cwd) {
|
|
|
14894
15176
|
return r.status === 0;
|
|
14895
15177
|
}
|
|
14896
15178
|
function inside2(root, path) {
|
|
14897
|
-
const rel = relative5(
|
|
14898
|
-
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep3}`) && !
|
|
15179
|
+
const rel = relative5(resolve8(root), resolve8(path));
|
|
15180
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep3}`) && !isAbsolute11(rel);
|
|
14899
15181
|
}
|
|
14900
15182
|
function linkParts(path) {
|
|
14901
15183
|
const parts = sep3 === "\\" ? path.split(/[\\/]+/) : path.split("/");
|
|
@@ -14908,10 +15190,10 @@ function driveRelativeLinkTarget(target) {
|
|
|
14908
15190
|
return sep3 === "\\" && /^[A-Za-z]:(?![\\/])/.test(target);
|
|
14909
15191
|
}
|
|
14910
15192
|
function rootedLinkTarget(target) {
|
|
14911
|
-
return
|
|
15193
|
+
return isAbsolute11(target) || driveRelativeLinkTarget(target);
|
|
14912
15194
|
}
|
|
14913
15195
|
function safeSymlink(target, out3, root, label) {
|
|
14914
|
-
if (rootedLinkTarget(target) || !inside2(root,
|
|
15196
|
+
if (rootedLinkTarget(target) || !inside2(root, resolve8(dirname8(out3), target))) {
|
|
14915
15197
|
throw linkEscape(label, target);
|
|
14916
15198
|
}
|
|
14917
15199
|
rmSync7(out3, { force: true });
|
|
@@ -14919,7 +15201,7 @@ function safeSymlink(target, out3, root, label) {
|
|
|
14919
15201
|
}
|
|
14920
15202
|
function validateSymlinkGraph(target, out3, root, dependencyRoot, label) {
|
|
14921
15203
|
let domain = "tree";
|
|
14922
|
-
let stack = linkParts(relative5(root,
|
|
15204
|
+
let stack = linkParts(relative5(root, dirname8(out3)));
|
|
14923
15205
|
const pending = linkParts(target);
|
|
14924
15206
|
const seen = /* @__PURE__ */ new Set();
|
|
14925
15207
|
let hops = 0;
|
|
@@ -14936,7 +15218,7 @@ function validateSymlinkGraph(target, out3, root, dependencyRoot, label) {
|
|
|
14936
15218
|
continue;
|
|
14937
15219
|
}
|
|
14938
15220
|
const base = domain === "deps" && dependencyRoot ? dependencyRoot : root;
|
|
14939
|
-
const current =
|
|
15221
|
+
const current = join19(base, ...stack);
|
|
14940
15222
|
let st;
|
|
14941
15223
|
try {
|
|
14942
15224
|
st = lstatSync9(current);
|
|
@@ -14957,12 +15239,12 @@ function validateSymlinkGraph(target, out3, root, dependencyRoot, label) {
|
|
|
14957
15239
|
if (driveRelativeLinkTarget(next)) {
|
|
14958
15240
|
throw linkEscape(label, target);
|
|
14959
15241
|
}
|
|
14960
|
-
if (
|
|
15242
|
+
if (isAbsolute11(next)) {
|
|
14961
15243
|
if (domain === "tree" || !dependencyRoot || !inside2(dependencyRoot, next)) {
|
|
14962
15244
|
throw linkEscape(label, target);
|
|
14963
15245
|
}
|
|
14964
15246
|
stack = [];
|
|
14965
|
-
pending.unshift(...linkParts(relative5(dependencyRoot,
|
|
15247
|
+
pending.unshift(...linkParts(relative5(dependencyRoot, resolve8(next))));
|
|
14966
15248
|
} else {
|
|
14967
15249
|
pending.unshift(...linkParts(next));
|
|
14968
15250
|
}
|
|
@@ -14972,7 +15254,7 @@ function rejectLinkedParent(cwd, rel) {
|
|
|
14972
15254
|
const parts = rel.split("/").slice(0, -1);
|
|
14973
15255
|
let current = cwd;
|
|
14974
15256
|
for (const part of parts) {
|
|
14975
|
-
current =
|
|
15257
|
+
current = join19(current, part);
|
|
14976
15258
|
let st;
|
|
14977
15259
|
try {
|
|
14978
15260
|
st = lstatSync9(current);
|
|
@@ -14988,7 +15270,7 @@ function materialize(cwd, dest, dependencyRoot) {
|
|
|
14988
15270
|
const listed = workingTreePaths(cwd);
|
|
14989
15271
|
const links = [];
|
|
14990
15272
|
for (const rel of listed) {
|
|
14991
|
-
const src =
|
|
15273
|
+
const src = join19(cwd, rel);
|
|
14992
15274
|
rejectLinkedParent(cwd, rel);
|
|
14993
15275
|
let st;
|
|
14994
15276
|
try {
|
|
@@ -14996,8 +15278,8 @@ function materialize(cwd, dest, dependencyRoot) {
|
|
|
14996
15278
|
} catch {
|
|
14997
15279
|
continue;
|
|
14998
15280
|
}
|
|
14999
|
-
const out3 =
|
|
15000
|
-
|
|
15281
|
+
const out3 = join19(dest, rel);
|
|
15282
|
+
mkdirSync8(dirname8(out3), { recursive: true });
|
|
15001
15283
|
dropSymlink(out3);
|
|
15002
15284
|
if (st.isSymbolicLink()) {
|
|
15003
15285
|
const target = readlinkSync3(src);
|
|
@@ -15013,17 +15295,17 @@ function materialize(cwd, dest, dependencyRoot) {
|
|
|
15013
15295
|
for (const link of links) {
|
|
15014
15296
|
validateSymlinkGraph(link.target, link.out, dest, dependencyRoot, link.rel);
|
|
15015
15297
|
}
|
|
15016
|
-
if (dependencyRoot) symlinkSync(dependencyRoot,
|
|
15298
|
+
if (dependencyRoot) symlinkSync(dependencyRoot, join19(dest, "node_modules"), "dir");
|
|
15017
15299
|
}
|
|
15018
15300
|
function workingTreePaths(cwd) {
|
|
15019
15301
|
return git2(["ls-files", "-z", "--cached", "--others", "--exclude-standard"], cwd).split("\0").filter(Boolean);
|
|
15020
15302
|
}
|
|
15021
15303
|
function probeFilesystemCaseSensitivity(dir) {
|
|
15022
|
-
const upper =
|
|
15023
|
-
const lower =
|
|
15304
|
+
const upper = join19(dir, ".tw-case-probe-A");
|
|
15305
|
+
const lower = join19(dir, ".tw-case-probe-a");
|
|
15024
15306
|
try {
|
|
15025
15307
|
writeFileSync9(upper, "", { flag: "wx" });
|
|
15026
|
-
return !
|
|
15308
|
+
return !existsSync14(lower);
|
|
15027
15309
|
} finally {
|
|
15028
15310
|
rmSync7(upper, { force: true });
|
|
15029
15311
|
}
|
|
@@ -15079,15 +15361,15 @@ function baseEntries(base, cwd) {
|
|
|
15079
15361
|
});
|
|
15080
15362
|
}
|
|
15081
15363
|
function overlayDigest(dest, paths) {
|
|
15082
|
-
const h =
|
|
15364
|
+
const h = createHash11("sha256");
|
|
15083
15365
|
for (const rel of [...paths].sort()) {
|
|
15084
15366
|
h.update(rel);
|
|
15085
15367
|
h.update("\0");
|
|
15086
|
-
const p =
|
|
15368
|
+
const p = join19(dest, rel);
|
|
15087
15369
|
try {
|
|
15088
15370
|
const st = lstatSync9(p);
|
|
15089
15371
|
h.update(String(st.mode));
|
|
15090
|
-
h.update(st.isSymbolicLink() ? readlinkSync3(p) :
|
|
15372
|
+
h.update(st.isSymbolicLink() ? readlinkSync3(p) : readFileSync17(p));
|
|
15091
15373
|
} catch {
|
|
15092
15374
|
h.update("<gone>");
|
|
15093
15375
|
}
|
|
@@ -15117,8 +15399,8 @@ function overlayPristine(cwd, base, dest, policy, cmd, dependencyRoot, caseSensi
|
|
|
15117
15399
|
throw new Error(`the base's ${e.path} is a submodule; the pristine copy cannot reproduce it`);
|
|
15118
15400
|
}
|
|
15119
15401
|
const content = execFileSync8("git", ["show", `${base}:${e.path}`], { cwd, maxBuffer: 1 << 28, env: trustedGitEnv() });
|
|
15120
|
-
const out3 =
|
|
15121
|
-
|
|
15402
|
+
const out3 = join19(dest, e.path);
|
|
15403
|
+
mkdirSync8(dirname8(out3), { recursive: true });
|
|
15122
15404
|
rmSync7(out3, { force: true });
|
|
15123
15405
|
if (e.mode === "120000") {
|
|
15124
15406
|
const target = content.toString("utf8");
|
|
@@ -15134,7 +15416,7 @@ function overlayPristine(cwd, base, dest, policy, cmd, dependencyRoot, caseSensi
|
|
|
15134
15416
|
const inCopy = workingTreePaths(cwd);
|
|
15135
15417
|
for (const rel of inCopy) {
|
|
15136
15418
|
if (!isOverlay(rel) || baseProtected.has(fold(rel))) continue;
|
|
15137
|
-
rmSync7(
|
|
15419
|
+
rmSync7(join19(dest, rel), { force: true });
|
|
15138
15420
|
removed++;
|
|
15139
15421
|
}
|
|
15140
15422
|
for (const link of restoredLinks) {
|
|
@@ -15148,8 +15430,8 @@ function suiteEnv(scratch) {
|
|
|
15148
15430
|
if (DROPPED_ENV.has(k) || PINNED_NPM.test(k)) continue;
|
|
15149
15431
|
env[k] = v;
|
|
15150
15432
|
}
|
|
15151
|
-
const userRc =
|
|
15152
|
-
const globalRc =
|
|
15433
|
+
const userRc = join19(scratch, "npmrc-user");
|
|
15434
|
+
const globalRc = join19(scratch, "npmrc-global");
|
|
15153
15435
|
for (const f of [userRc, globalRc]) writeFileSync9(f, "", { mode: 292 });
|
|
15154
15436
|
env.npm_config_userconfig = userRc;
|
|
15155
15437
|
env.npm_config_globalconfig = globalRc;
|
|
@@ -15181,7 +15463,7 @@ function runLocalSuite(dir, cmd, budgetSecs) {
|
|
|
15181
15463
|
reason: `checkpointed-local verifier is unsupported on ${process.platform}; no trusted local shell contract is defined`
|
|
15182
15464
|
};
|
|
15183
15465
|
}
|
|
15184
|
-
const scratch = mkdtempSync3(
|
|
15466
|
+
const scratch = mkdtempSync3(join19(tmpdir4(), "tw-verify-run-"));
|
|
15185
15467
|
try {
|
|
15186
15468
|
const r = runCapturedProcessSync(shell.executable, shell.args, {
|
|
15187
15469
|
cwd: dir,
|
|
@@ -15370,8 +15652,8 @@ function runVerify(opts) {
|
|
|
15370
15652
|
} catch {
|
|
15371
15653
|
return cannotVerify("BASE_UNRESOLVABLE", `cannot resolve base rev "${opts.base ?? "HEAD"}"`);
|
|
15372
15654
|
}
|
|
15373
|
-
const visRoot = mkdtempSync3(
|
|
15374
|
-
const visDir =
|
|
15655
|
+
const visRoot = mkdtempSync3(join19(tmpdir4(), "tw-verify-vis-"));
|
|
15656
|
+
const visDir = join19(visRoot, "t");
|
|
15375
15657
|
const cleanup = (dirs) => {
|
|
15376
15658
|
if (opts.keep) return;
|
|
15377
15659
|
for (const d of dirs) rmSync7(d, { recursive: true, force: true });
|
|
@@ -15380,7 +15662,7 @@ function runVerify(opts) {
|
|
|
15380
15662
|
if (!filesystemCaseSensitive) {
|
|
15381
15663
|
const present = (rel) => {
|
|
15382
15664
|
try {
|
|
15383
|
-
lstatSync9(
|
|
15665
|
+
lstatSync9(join19(cwd, rel));
|
|
15384
15666
|
return true;
|
|
15385
15667
|
} catch {
|
|
15386
15668
|
return false;
|
|
@@ -15412,7 +15694,7 @@ function runVerify(opts) {
|
|
|
15412
15694
|
);
|
|
15413
15695
|
}
|
|
15414
15696
|
try {
|
|
15415
|
-
|
|
15697
|
+
mkdirSync8(visDir);
|
|
15416
15698
|
materialize(cwd, visDir, frozenNodeModules);
|
|
15417
15699
|
} catch (e) {
|
|
15418
15700
|
cleanup([visRoot]);
|
|
@@ -15466,12 +15748,12 @@ function runVerify(opts) {
|
|
|
15466
15748
|
{ stage: "visible", dependency_environment: dependencyReport(), verifier_backend: backendReport(), oracle_assurance: oracleAssuranceReport() }
|
|
15467
15749
|
);
|
|
15468
15750
|
}
|
|
15469
|
-
const priRoot = mkdtempSync3(
|
|
15470
|
-
const priDir =
|
|
15751
|
+
const priRoot = mkdtempSync3(join19(tmpdir4(), "tw-verify-pri-"));
|
|
15752
|
+
const priDir = join19(priRoot, "t");
|
|
15471
15753
|
let restored = [];
|
|
15472
15754
|
let removedAdded = 0;
|
|
15473
15755
|
try {
|
|
15474
|
-
|
|
15756
|
+
mkdirSync8(priDir);
|
|
15475
15757
|
materialize(cwd, priDir, frozenNodeModules);
|
|
15476
15758
|
({ restored, removed: removedAdded } = overlayPristine(
|
|
15477
15759
|
cwd,
|
|
@@ -15740,10 +16022,10 @@ var init_verify = __esm({
|
|
|
15740
16022
|
|
|
15741
16023
|
// src/cli/run.ts
|
|
15742
16024
|
import { execFileSync as execFileSync9, spawn, spawnSync as spawnSync4 } from "node:child_process";
|
|
15743
|
-
import { randomBytes as
|
|
15744
|
-
import { accessSync as accessSync3, constants as fsConstants, mkdtempSync as mkdtempSync4, readdirSync as readdirSync4, readFileSync as
|
|
16025
|
+
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
16026
|
+
import { accessSync as accessSync3, constants as fsConstants, mkdtempSync as mkdtempSync4, readdirSync as readdirSync4, readFileSync as readFileSync18, readlinkSync as readlinkSync4, realpathSync as realpathSync9, rmSync as rmSync8, statSync as statSync5, writeFileSync as writeFileSync10 } from "node:fs";
|
|
15745
16027
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
15746
|
-
import { dirname as
|
|
16028
|
+
import { dirname as dirname9, join as join20, resolve as resolve9 } from "node:path";
|
|
15747
16029
|
function authoritativeRunLifecyclePlatform(platform = process.platform) {
|
|
15748
16030
|
return platform === "linux";
|
|
15749
16031
|
}
|
|
@@ -15780,7 +16062,7 @@ function writableByCaller2(path) {
|
|
|
15780
16062
|
return true;
|
|
15781
16063
|
} catch {
|
|
15782
16064
|
}
|
|
15783
|
-
const parent =
|
|
16065
|
+
const parent = dirname9(cur);
|
|
15784
16066
|
if (parent === cur) break;
|
|
15785
16067
|
cur = parent;
|
|
15786
16068
|
}
|
|
@@ -15796,7 +16078,7 @@ function trustedLinuxPython(candidates = DEFAULT_TRUSTED_PYTHON_CANDIDATES) {
|
|
|
15796
16078
|
}
|
|
15797
16079
|
for (const candidate of candidates) {
|
|
15798
16080
|
try {
|
|
15799
|
-
const real =
|
|
16081
|
+
const real = realpathSync9(candidate);
|
|
15800
16082
|
const st = statSync5(real);
|
|
15801
16083
|
if (!st.isFile() || writableByCaller2(real)) continue;
|
|
15802
16084
|
const probe = spawnSync4(
|
|
@@ -15819,9 +16101,9 @@ function trustedLinuxPython(candidates = DEFAULT_TRUSTED_PYTHON_CANDIDATES) {
|
|
|
15819
16101
|
};
|
|
15820
16102
|
}
|
|
15821
16103
|
function runAgentSupervised(argv, cwd, budgetSecs, linuxPythonCandidates, lifecycleTestMode, machineMode = false) {
|
|
15822
|
-
const stateDir = mkdtempSync4(
|
|
15823
|
-
const resultFile =
|
|
15824
|
-
const agentEnvFile =
|
|
16104
|
+
const stateDir = mkdtempSync4(join20(tmpdir5(), "tw-agent-supervisor-"));
|
|
16105
|
+
const resultFile = join20(stateDir, "result.json");
|
|
16106
|
+
const agentEnvFile = join20(stateDir, "agent-env.json");
|
|
15825
16107
|
try {
|
|
15826
16108
|
const linux = process.platform === "linux";
|
|
15827
16109
|
let executable;
|
|
@@ -15895,7 +16177,7 @@ function runAgentSupervised(argv, cwd, budgetSecs, linuxPythonCandidates, lifecy
|
|
|
15895
16177
|
}
|
|
15896
16178
|
let state = {};
|
|
15897
16179
|
try {
|
|
15898
|
-
state = JSON.parse(
|
|
16180
|
+
state = JSON.parse(readFileSync18(resultFile, "utf8"));
|
|
15899
16181
|
} catch {
|
|
15900
16182
|
return {
|
|
15901
16183
|
exit: 1,
|
|
@@ -15939,7 +16221,7 @@ function observerExited(pid) {
|
|
|
15939
16221
|
if (!pidAlive3(pid)) return true;
|
|
15940
16222
|
if (process.platform !== "linux") return false;
|
|
15941
16223
|
try {
|
|
15942
|
-
const stat =
|
|
16224
|
+
const stat = readFileSync18(`/proc/${pid}/stat`, "utf8");
|
|
15943
16225
|
const state = stat.slice(stat.lastIndexOf(")") + 2).split(" ")[0];
|
|
15944
16226
|
return state === "Z" || state === "X";
|
|
15945
16227
|
} catch (e) {
|
|
@@ -15947,11 +16229,11 @@ function observerExited(pid) {
|
|
|
15947
16229
|
}
|
|
15948
16230
|
}
|
|
15949
16231
|
function supervisedObserverLog(cwd) {
|
|
15950
|
-
const gd = gitDir(cwd) ??
|
|
15951
|
-
return
|
|
16232
|
+
const gd = gitDir(cwd) ?? join20(cwd, ".git");
|
|
16233
|
+
return join20(
|
|
15952
16234
|
gd,
|
|
15953
16235
|
"tamperward",
|
|
15954
|
-
`run-observer-${process.pid}-${Date.now()}-${
|
|
16236
|
+
`run-observer-${process.pid}-${Date.now()}-${randomBytes3(4).toString("hex")}.jsonl`
|
|
15955
16237
|
);
|
|
15956
16238
|
}
|
|
15957
16239
|
function startSupervisedObserver(cwd, base, entry) {
|
|
@@ -15962,7 +16244,7 @@ function startSupervisedObserver(cwd, base, entry) {
|
|
|
15962
16244
|
try {
|
|
15963
16245
|
child = spawn(
|
|
15964
16246
|
process.execPath,
|
|
15965
|
-
[
|
|
16247
|
+
[resolve9(cliEntry), "watch", "--dir", cwd, "--log", log, "--base", base],
|
|
15966
16248
|
{
|
|
15967
16249
|
cwd,
|
|
15968
16250
|
stdio: ["ignore", "ignore", "inherit"],
|
|
@@ -16061,7 +16343,7 @@ function git3(args, cwd) {
|
|
|
16061
16343
|
}
|
|
16062
16344
|
function nowTicks() {
|
|
16063
16345
|
try {
|
|
16064
|
-
return Math.floor(parseFloat(
|
|
16346
|
+
return Math.floor(parseFloat(readFileSync18("/proc/uptime", "utf8").split(" ")[0]) * 100);
|
|
16065
16347
|
} catch {
|
|
16066
16348
|
return Number.POSITIVE_INFINITY;
|
|
16067
16349
|
}
|
|
@@ -16070,7 +16352,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
|
|
|
16070
16352
|
const out3 = [];
|
|
16071
16353
|
let real;
|
|
16072
16354
|
try {
|
|
16073
|
-
real =
|
|
16355
|
+
real = realpathSync9(cwd);
|
|
16074
16356
|
} catch {
|
|
16075
16357
|
return out3;
|
|
16076
16358
|
}
|
|
@@ -16084,7 +16366,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
|
|
|
16084
16366
|
const n = Number(pid);
|
|
16085
16367
|
if (n === process.pid) continue;
|
|
16086
16368
|
try {
|
|
16087
|
-
const stat =
|
|
16369
|
+
const stat = readFileSync18(`/proc/${pid}/stat`, "utf8");
|
|
16088
16370
|
const startTicks = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[19]);
|
|
16089
16371
|
if (!Number.isFinite(startTicks) || startTicks < spawnedAfterTicks) continue;
|
|
16090
16372
|
} catch {
|
|
@@ -16099,7 +16381,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
|
|
|
16099
16381
|
for (const link of procLinks) {
|
|
16100
16382
|
try {
|
|
16101
16383
|
const raw = readlinkSync4(link).replace(/ \(deleted\)$/, "");
|
|
16102
|
-
const held = raw.startsWith("/") ?
|
|
16384
|
+
const held = raw.startsWith("/") ? resolve9(raw) : raw;
|
|
16103
16385
|
if (held === real || held.startsWith(real + "/")) {
|
|
16104
16386
|
holds = true;
|
|
16105
16387
|
break;
|
|
@@ -16112,7 +16394,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
|
|
|
16112
16394
|
return out3;
|
|
16113
16395
|
}
|
|
16114
16396
|
function runEnvelope(opts) {
|
|
16115
|
-
const cwd =
|
|
16397
|
+
const cwd = resolve9(opts.cwd ?? process.cwd());
|
|
16116
16398
|
const say = opts.json ? (_s) => {
|
|
16117
16399
|
} : out;
|
|
16118
16400
|
if (!opts.argv.length) {
|
|
@@ -16656,8 +16938,8 @@ raise SystemExit(0)
|
|
|
16656
16938
|
|
|
16657
16939
|
// src/cli/doctor.ts
|
|
16658
16940
|
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
16659
|
-
import { existsSync as
|
|
16660
|
-
import { join as
|
|
16941
|
+
import { existsSync as existsSync15, readFileSync as readFileSync19, readdirSync as readdirSync5 } from "node:fs";
|
|
16942
|
+
import { join as join21, resolve as resolve10 } from "node:path";
|
|
16661
16943
|
function lifecyclePlatformCheck(platform = process.platform, linuxPython = platform === "linux" ? trustedLinuxPython() : null) {
|
|
16662
16944
|
if (platform === "linux") {
|
|
16663
16945
|
if (linuxPython?.path) {
|
|
@@ -16712,10 +16994,10 @@ function wiringState(status, authority) {
|
|
|
16712
16994
|
return authority ? "BROKEN" : "WARN";
|
|
16713
16995
|
}
|
|
16714
16996
|
function pinsInFile(path) {
|
|
16715
|
-
if (!
|
|
16997
|
+
if (!existsSync15(path)) return [];
|
|
16716
16998
|
let src = "";
|
|
16717
16999
|
try {
|
|
16718
|
-
src =
|
|
17000
|
+
src = readFileSync19(path, "utf8");
|
|
16719
17001
|
} catch {
|
|
16720
17002
|
return [];
|
|
16721
17003
|
}
|
|
@@ -16735,14 +17017,14 @@ function workflowPermissionCheck(cwd, workflowRels = [".github/workflows/tamperw
|
|
|
16735
17017
|
const broken = [];
|
|
16736
17018
|
const warnings = [];
|
|
16737
17019
|
for (const rel of workflowRels) {
|
|
16738
|
-
const path =
|
|
16739
|
-
if (!
|
|
17020
|
+
const path = resolve10(cwd, rel);
|
|
17021
|
+
if (!existsSync15(path)) {
|
|
16740
17022
|
broken.push(`${rel} is missing`);
|
|
16741
17023
|
continue;
|
|
16742
17024
|
}
|
|
16743
17025
|
let doc;
|
|
16744
17026
|
try {
|
|
16745
|
-
doc = yaml.parse(
|
|
17027
|
+
doc = yaml.parse(readFileSync19(path, "utf8"));
|
|
16746
17028
|
} catch (e) {
|
|
16747
17029
|
broken.push(`${rel} is not valid YAML (${e instanceof Error ? e.message : String(e)})`);
|
|
16748
17030
|
continue;
|
|
@@ -16855,9 +17137,9 @@ function collectLocalPosture(cwd, policy, authorityWorkflows, explicitWorkflow =
|
|
|
16855
17137
|
addWiring("codeowners", "codeowners", true);
|
|
16856
17138
|
checks.push(workflowPermissionCheck(cwd, authorityWorkflows));
|
|
16857
17139
|
const pins = [
|
|
16858
|
-
...pinsInFile(
|
|
16859
|
-
...pinsInFile(
|
|
16860
|
-
...pinsInFile(
|
|
17140
|
+
...pinsInFile(join21(cwd, ".claude", "settings.json")),
|
|
17141
|
+
...pinsInFile(join21(cwd, ".git", "hooks", "pre-commit")),
|
|
17142
|
+
...pinsInFile(join21(cwd, ".husky", "pre-commit"))
|
|
16861
17143
|
];
|
|
16862
17144
|
const below = pins.filter((pin) => (compareVersions(pin, TW_VERSION) ?? 0) < 0);
|
|
16863
17145
|
const above = pins.filter((pin) => (compareVersions(TW_VERSION, pin) ?? 0) < 0);
|
|
@@ -17082,7 +17364,7 @@ function verifyJobs(doc) {
|
|
|
17082
17364
|
return found;
|
|
17083
17365
|
}
|
|
17084
17366
|
function diagnose(opts = {}) {
|
|
17085
|
-
const cwd = repoRoot(
|
|
17367
|
+
const cwd = repoRoot(resolve10(opts.cwd ?? process.cwd()));
|
|
17086
17368
|
let policy;
|
|
17087
17369
|
try {
|
|
17088
17370
|
policy = policyFor(opts, cwd);
|
|
@@ -17106,8 +17388,8 @@ function diagnose(opts = {}) {
|
|
|
17106
17388
|
workflowRels.push(opts.workflow);
|
|
17107
17389
|
} else {
|
|
17108
17390
|
const workflowDirRel = ".github/workflows";
|
|
17109
|
-
const workflowDir =
|
|
17110
|
-
if (!
|
|
17391
|
+
const workflowDir = resolve10(cwd, workflowDirRel);
|
|
17392
|
+
if (!existsSync15(workflowDir)) {
|
|
17111
17393
|
return fail("ci-verifier", `${workflowDirRel}: workflow directory does not exist`);
|
|
17112
17394
|
}
|
|
17113
17395
|
let entries;
|
|
@@ -17119,20 +17401,20 @@ function diagnose(opts = {}) {
|
|
|
17119
17401
|
`${workflowDirRel}: could not enumerate workflows (${e instanceof Error ? e.message : String(e)})`
|
|
17120
17402
|
);
|
|
17121
17403
|
}
|
|
17122
|
-
for (const name of entries) workflowRels.push(
|
|
17404
|
+
for (const name of entries) workflowRels.push(join21(workflowDirRel, name));
|
|
17123
17405
|
}
|
|
17124
17406
|
const requiredSecs = requiredVerifierAuthoritySeconds(policy.verify.budget);
|
|
17125
17407
|
const requiredMinutes = Math.ceil(requiredSecs / 60);
|
|
17126
17408
|
let verifyJobCount = 0;
|
|
17127
17409
|
const authorityWorkflows = [];
|
|
17128
17410
|
for (const workflowRel of workflowRels) {
|
|
17129
|
-
const workflowPath =
|
|
17130
|
-
if (!
|
|
17411
|
+
const workflowPath = resolve10(cwd, workflowRel);
|
|
17412
|
+
if (!existsSync15(workflowPath)) {
|
|
17131
17413
|
return fail("ci-verifier", `${workflowRel}: workflow does not exist`);
|
|
17132
17414
|
}
|
|
17133
17415
|
let doc;
|
|
17134
17416
|
try {
|
|
17135
|
-
doc = yaml.parse(
|
|
17417
|
+
doc = yaml.parse(readFileSync19(workflowPath, "utf8"));
|
|
17136
17418
|
} catch (e) {
|
|
17137
17419
|
return fail(
|
|
17138
17420
|
"ci-verifier",
|
|
@@ -17288,16 +17570,16 @@ var init_doctor = __esm({
|
|
|
17288
17570
|
// src/cli/trace-verify.ts
|
|
17289
17571
|
import { execFileSync as execFileSync11, spawnSync as spawnSync5 } from "node:child_process";
|
|
17290
17572
|
import {
|
|
17291
|
-
existsSync as
|
|
17573
|
+
existsSync as existsSync16,
|
|
17292
17574
|
mkdtempSync as mkdtempSync5,
|
|
17293
|
-
readFileSync as
|
|
17575
|
+
readFileSync as readFileSync20,
|
|
17294
17576
|
readdirSync as readdirSync6,
|
|
17295
|
-
realpathSync as
|
|
17577
|
+
realpathSync as realpathSync10,
|
|
17296
17578
|
rmSync as rmSync9,
|
|
17297
17579
|
symlinkSync as symlinkSync2
|
|
17298
17580
|
} from "node:fs";
|
|
17299
17581
|
import { tmpdir as tmpdir6 } from "node:os";
|
|
17300
|
-
import { isAbsolute as
|
|
17582
|
+
import { isAbsolute as isAbsolute12, join as join22, relative as relative6, resolve as resolve11, sep as sep4 } from "node:path";
|
|
17301
17583
|
function unescapeStraceString(s) {
|
|
17302
17584
|
return s.replace(/\\([0-7]{1,3})/g, (_m, oct) => String.fromCharCode(parseInt(oct, 8))).replace(/\\x([0-9a-fA-F]{2})/g, (_m, hex) => String.fromCharCode(parseInt(hex, 16))).replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
17303
17585
|
}
|
|
@@ -17322,11 +17604,11 @@ function parseStraceFileAccess(raw) {
|
|
|
17322
17604
|
return out3;
|
|
17323
17605
|
}
|
|
17324
17606
|
function inside3(root, path) {
|
|
17325
|
-
const rel = relative6(
|
|
17326
|
-
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep4}`) && !
|
|
17607
|
+
const rel = relative6(resolve11(root), resolve11(path));
|
|
17608
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep4}`) && !isAbsolute12(rel);
|
|
17327
17609
|
}
|
|
17328
17610
|
function normalRepoPath(root, raw) {
|
|
17329
|
-
const abs =
|
|
17611
|
+
const abs = isAbsolute12(raw) ? resolve11(raw) : resolve11(root, raw);
|
|
17330
17612
|
if (!inside3(root, abs)) return null;
|
|
17331
17613
|
const rel = relative6(root, abs).split(sep4).join("/");
|
|
17332
17614
|
return rel && rel !== ".." ? rel : null;
|
|
@@ -17346,7 +17628,7 @@ function summarizeTraceRuns(opts) {
|
|
|
17346
17628
|
for (const item of run) {
|
|
17347
17629
|
const rel = normalRepoPath(opts.root, item.path);
|
|
17348
17630
|
const isTracked = rel !== null && opts.tracked.has(rel);
|
|
17349
|
-
const keyPath = rel !== null ? opts.tracked.has(rel) ? rel : null :
|
|
17631
|
+
const keyPath = rel !== null ? opts.tracked.has(rel) ? rel : null : resolve11(opts.root, item.path);
|
|
17350
17632
|
if (keyPath === null) continue;
|
|
17351
17633
|
const target = isTracked ? repo : external;
|
|
17352
17634
|
const key2 = `${isTracked ? "repo" : "external"}\0${keyPath}`;
|
|
@@ -17423,36 +17705,36 @@ function materializeBase(base, cwd, dest) {
|
|
|
17423
17705
|
`tar extraction failed${untar.stderr?.length ? `: ${String(untar.stderr).trim()}` : ""}`
|
|
17424
17706
|
);
|
|
17425
17707
|
}
|
|
17426
|
-
const hostModules =
|
|
17427
|
-
const tracedModules =
|
|
17428
|
-
if (
|
|
17429
|
-
symlinkSync2(
|
|
17708
|
+
const hostModules = join22(cwd, "node_modules");
|
|
17709
|
+
const tracedModules = join22(dest, "node_modules");
|
|
17710
|
+
if (existsSync16(hostModules) && !existsSync16(tracedModules)) {
|
|
17711
|
+
symlinkSync2(resolve11(hostModules), tracedModules, "dir");
|
|
17430
17712
|
}
|
|
17431
17713
|
}
|
|
17432
17714
|
function traceFiles(prefix) {
|
|
17433
|
-
const dir =
|
|
17715
|
+
const dir = resolve11(prefix, "..");
|
|
17434
17716
|
const base = prefix.split(sep4).at(-1) ?? prefix;
|
|
17435
|
-
return readdirSync6(dir).filter((name) => name === base || name.startsWith(base + ".")).map((name) =>
|
|
17717
|
+
return readdirSync6(dir).filter((name) => name === base || name.startsWith(base + ".")).map((name) => join22(dir, name)).sort();
|
|
17436
17718
|
}
|
|
17437
17719
|
function rewriteTraceRoot(accesses, actualRoot) {
|
|
17438
17720
|
return accesses.map((item) => {
|
|
17439
|
-
const abs =
|
|
17721
|
+
const abs = isAbsolute12(item.path) ? resolve11(item.path) : resolve11(actualRoot, item.path);
|
|
17440
17722
|
if (!inside3(actualRoot, abs)) return { ...item, path: abs };
|
|
17441
17723
|
const rel = relative6(actualRoot, abs).split(sep4).join("/");
|
|
17442
17724
|
try {
|
|
17443
|
-
const real =
|
|
17725
|
+
const real = resolve11(realpathSync10(abs));
|
|
17444
17726
|
if (!inside3(actualRoot, real)) return { ...item, path: real };
|
|
17445
17727
|
} catch {
|
|
17446
17728
|
}
|
|
17447
|
-
return { ...item, path:
|
|
17729
|
+
return { ...item, path: join22(TRACE_ROOT, ...rel.split("/")) };
|
|
17448
17730
|
});
|
|
17449
17731
|
}
|
|
17450
17732
|
function runOneTrace(cwd, base, command, budget) {
|
|
17451
|
-
const root = mkdtempSync5(
|
|
17452
|
-
const traceDir = mkdtempSync5(
|
|
17733
|
+
const root = mkdtempSync5(join22(tmpdir6(), "tw-trace-base-"));
|
|
17734
|
+
const traceDir = mkdtempSync5(join22(tmpdir6(), "tw-trace-log-"));
|
|
17453
17735
|
try {
|
|
17454
17736
|
materializeBase(base, cwd, root);
|
|
17455
|
-
const prefix =
|
|
17737
|
+
const prefix = join22(traceDir, "trace");
|
|
17456
17738
|
const traced = spawnSync5(
|
|
17457
17739
|
"strace",
|
|
17458
17740
|
[
|
|
@@ -17485,7 +17767,7 @@ function runOneTrace(cwd, base, command, budget) {
|
|
|
17485
17767
|
if (logs.length === 0) {
|
|
17486
17768
|
throw new Error("strace produced no trace files");
|
|
17487
17769
|
}
|
|
17488
|
-
const accesses = logs.flatMap((path) => parseStraceFileAccess(
|
|
17770
|
+
const accesses = logs.flatMap((path) => parseStraceFileAccess(readFileSync20(path, "utf8")));
|
|
17489
17771
|
return {
|
|
17490
17772
|
exit: traced.status ?? 1,
|
|
17491
17773
|
accesses: rewriteTraceRoot(accesses, root)
|
|
@@ -17529,7 +17811,7 @@ function renderText2(report2) {
|
|
|
17529
17811
|
for (const note of report2.notes) out3(`note: ${note}`);
|
|
17530
17812
|
}
|
|
17531
17813
|
function runTraceVerify(opts = {}) {
|
|
17532
|
-
const cwd =
|
|
17814
|
+
const cwd = resolve11(opts.cwd ?? process.cwd());
|
|
17533
17815
|
if (process.platform !== "linux") {
|
|
17534
17816
|
process.stderr.write("tamperward trace-verify: Linux only in this release; unsupported platform \u2014 no parity is implied.\n");
|
|
17535
17817
|
return 2;
|
|
@@ -17676,9 +17958,9 @@ var init_trace_verify = __esm({
|
|
|
17676
17958
|
|
|
17677
17959
|
// src/cli/onboard.ts
|
|
17678
17960
|
import { execFileSync as execFileSync12 } from "node:child_process";
|
|
17679
|
-
import { lstatSync as lstatSync10, mkdtempSync as mkdtempSync6, readFileSync as
|
|
17961
|
+
import { lstatSync as lstatSync10, mkdtempSync as mkdtempSync6, readFileSync as readFileSync21, realpathSync as realpathSync11, rmSync as rmSync10 } from "node:fs";
|
|
17680
17962
|
import { tmpdir as tmpdir7 } from "node:os";
|
|
17681
|
-
import { join as
|
|
17963
|
+
import { join as join23, relative as relative7, resolve as resolve12, sep as sep5 } from "node:path";
|
|
17682
17964
|
import { createInterface } from "node:readline";
|
|
17683
17965
|
import { parseDocument } from "yaml";
|
|
17684
17966
|
function readlineAsker(input, output) {
|
|
@@ -17687,9 +17969,9 @@ function readlineAsker(input, output) {
|
|
|
17687
17969
|
let pending = null;
|
|
17688
17970
|
rl.on("close", () => {
|
|
17689
17971
|
closed = true;
|
|
17690
|
-
const
|
|
17972
|
+
const resolve17 = pending;
|
|
17691
17973
|
pending = null;
|
|
17692
|
-
|
|
17974
|
+
resolve17?.(null);
|
|
17693
17975
|
});
|
|
17694
17976
|
rl.on("SIGINT", () => rl.close());
|
|
17695
17977
|
const ask = (question) => new Promise((resolvePromise) => {
|
|
@@ -17715,7 +17997,7 @@ function terminalText(text) {
|
|
|
17715
17997
|
return stripControl(text).replace(/\s+/g, " ").trim();
|
|
17716
17998
|
}
|
|
17717
17999
|
function committableSetupPath(cwd, path) {
|
|
17718
|
-
const rel = relative7(cwd,
|
|
18000
|
+
const rel = relative7(cwd, resolve12(cwd, path));
|
|
17719
18001
|
if (!rel || rel === ".." || rel.startsWith(".." + sep5)) return false;
|
|
17720
18002
|
return rel !== ".git" && !rel.startsWith(".git" + sep5);
|
|
17721
18003
|
}
|
|
@@ -17746,13 +18028,13 @@ function explainVerdict(v, code2) {
|
|
|
17746
18028
|
}
|
|
17747
18029
|
}
|
|
17748
18030
|
function writeVerifyCommand(cwd, command) {
|
|
17749
|
-
const path =
|
|
18031
|
+
const path = join23(cwd, POLICY_FILE);
|
|
17750
18032
|
const kind = writeTargetKind(path);
|
|
17751
18033
|
if (kind === "symlink" || kind === "irregular") {
|
|
17752
18034
|
return `${POLICY_FILE} is not a regular file; refusing to follow or replace a symlink or special file`;
|
|
17753
18035
|
}
|
|
17754
18036
|
const mode = existingMode(path, 420);
|
|
17755
|
-
const original = kind === "file" ?
|
|
18037
|
+
const original = kind === "file" ? readFileSync21(path, "utf8") : null;
|
|
17756
18038
|
let next;
|
|
17757
18039
|
try {
|
|
17758
18040
|
const doc = parseDocument(original && original.trim() ? original : "version: 1\n");
|
|
@@ -17775,7 +18057,7 @@ function writeVerifyCommand(cwd, command) {
|
|
|
17775
18057
|
}
|
|
17776
18058
|
}
|
|
17777
18059
|
async function runOnboard(opts, io = {}) {
|
|
17778
|
-
const requestedCwd =
|
|
18060
|
+
const requestedCwd = resolve12(opts.cwd ?? process.cwd());
|
|
17779
18061
|
const out3 = io.out ?? ((line) => void process.stdout.write(line + "\n"));
|
|
17780
18062
|
const errLine = (line) => void process.stderr.write(line + "\n");
|
|
17781
18063
|
const rawErr = (line) => errLine(terminalText(line));
|
|
@@ -17815,10 +18097,10 @@ async function runOnboard(opts, io = {}) {
|
|
|
17815
18097
|
fail(requestedCwd + " is not inside a Git repository. Run `git init` first.");
|
|
17816
18098
|
return 2;
|
|
17817
18099
|
}
|
|
17818
|
-
const cwd =
|
|
18100
|
+
const cwd = resolve12(rootText);
|
|
17819
18101
|
let sameRoot = cwd === requestedCwd;
|
|
17820
18102
|
try {
|
|
17821
|
-
sameRoot =
|
|
18103
|
+
sameRoot = realpathSync11(cwd) === realpathSync11(requestedCwd);
|
|
17822
18104
|
} catch {
|
|
17823
18105
|
}
|
|
17824
18106
|
if (!sameRoot) {
|
|
@@ -18141,13 +18423,13 @@ function runDemo(cwd, head, runners, out3) {
|
|
|
18141
18423
|
}
|
|
18142
18424
|
const before = treeFingerprint(cwd);
|
|
18143
18425
|
out3(`working tree fingerprint before: ${before.slice(0, 16)}`);
|
|
18144
|
-
const wt = mkdtempSync6(
|
|
18426
|
+
const wt = mkdtempSync6(join23(tmpdir7(), "tw-onboard-demo-"));
|
|
18145
18427
|
try {
|
|
18146
18428
|
if (git5(cwd, ["worktree", "add", "--detach", wt, head]) === null) {
|
|
18147
18429
|
out3("Demo skipped: could not create a temporary worktree (git worktree add failed).");
|
|
18148
18430
|
return;
|
|
18149
18431
|
}
|
|
18150
|
-
const targetPath =
|
|
18432
|
+
const targetPath = join23(wt, target.path);
|
|
18151
18433
|
const targetStat = lstatSync10(targetPath, { throwIfNoEntry: false });
|
|
18152
18434
|
if (!targetStat || !targetStat.isFile() || targetStat.isSymbolicLink()) {
|
|
18153
18435
|
out3("Demo skipped: the selected test is not a regular file in the disposable worktree.");
|
|
@@ -18156,8 +18438,8 @@ function runDemo(cwd, head, runners, out3) {
|
|
|
18156
18438
|
let worktreeRoot;
|
|
18157
18439
|
let realTarget;
|
|
18158
18440
|
try {
|
|
18159
|
-
worktreeRoot =
|
|
18160
|
-
realTarget =
|
|
18441
|
+
worktreeRoot = realpathSync11(wt);
|
|
18442
|
+
realTarget = realpathSync11(targetPath);
|
|
18161
18443
|
} catch {
|
|
18162
18444
|
out3("Demo skipped: the selected test could not be resolved safely inside the disposable worktree.");
|
|
18163
18445
|
return;
|
|
@@ -18232,7 +18514,7 @@ var init_onboard = __esm({
|
|
|
18232
18514
|
});
|
|
18233
18515
|
|
|
18234
18516
|
// src/research/adapter.ts
|
|
18235
|
-
import { isAbsolute as
|
|
18517
|
+
import { isAbsolute as isAbsolute13, resolve as resolve13 } from "node:path";
|
|
18236
18518
|
function taskEnv(task) {
|
|
18237
18519
|
return {
|
|
18238
18520
|
TAMPERWARD_RESEARCH_TASK: task.id,
|
|
@@ -18248,7 +18530,7 @@ function normalizeCommandArgv(argv, cwd = process.cwd()) {
|
|
|
18248
18530
|
throw new ResearchError('the command adapter needs an agent command after "--"');
|
|
18249
18531
|
}
|
|
18250
18532
|
const head = argv[0];
|
|
18251
|
-
return head.includes("/") && !
|
|
18533
|
+
return head.includes("/") && !isAbsolute13(head) ? [resolve13(cwd, head), ...argv.slice(1)] : [...argv];
|
|
18252
18534
|
}
|
|
18253
18535
|
function commandAdapter(argv, cwd = process.cwd()) {
|
|
18254
18536
|
const anchored = normalizeCommandArgv(argv, cwd);
|
|
@@ -18347,9 +18629,9 @@ var init_capture = __esm({
|
|
|
18347
18629
|
});
|
|
18348
18630
|
|
|
18349
18631
|
// src/research/manifest.ts
|
|
18350
|
-
import { createHash as
|
|
18351
|
-
import { readFileSync as
|
|
18352
|
-
import { dirname as
|
|
18632
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
18633
|
+
import { readFileSync as readFileSync22 } from "node:fs";
|
|
18634
|
+
import { dirname as dirname10, resolve as resolve14 } from "node:path";
|
|
18353
18635
|
function taskFrom(raw, index, manifestDir) {
|
|
18354
18636
|
const where = `task #${index + 1}`;
|
|
18355
18637
|
if (!isRecord(raw)) throw new ResearchError(`task manifest: ${where} is not an object`);
|
|
@@ -18372,17 +18654,17 @@ function taskFrom(raw, index, manifestDir) {
|
|
|
18372
18654
|
const isUrl = /^[a-z][a-z0-9+.-]*:\/\//i.test(repo) || /^[^/]+@[^:]+:/.test(repo);
|
|
18373
18655
|
return {
|
|
18374
18656
|
id,
|
|
18375
|
-
repo: isUrl ? repo :
|
|
18657
|
+
repo: isUrl ? repo : resolve14(manifestDir, repo),
|
|
18376
18658
|
base: base ?? "HEAD",
|
|
18377
18659
|
prompt,
|
|
18378
18660
|
verify: { command: verify.command, ...budget !== void 0 ? { budget } : {} }
|
|
18379
18661
|
};
|
|
18380
18662
|
}
|
|
18381
18663
|
function readManifest(path) {
|
|
18382
|
-
const abs =
|
|
18664
|
+
const abs = resolve14(path);
|
|
18383
18665
|
let bytes;
|
|
18384
18666
|
try {
|
|
18385
|
-
bytes =
|
|
18667
|
+
bytes = readFileSync22(abs);
|
|
18386
18668
|
} catch (e) {
|
|
18387
18669
|
throw new ResearchError(`cannot read task manifest ${abs}: ${e instanceof Error ? e.message : String(e)}`);
|
|
18388
18670
|
}
|
|
@@ -18399,13 +18681,13 @@ function readManifest(path) {
|
|
|
18399
18681
|
if (!Array.isArray(raw.tasks) || raw.tasks.length === 0) {
|
|
18400
18682
|
throw new ResearchError(`task manifest ${abs}: no tasks (tasks must be a non-empty array)`);
|
|
18401
18683
|
}
|
|
18402
|
-
const tasks = raw.tasks.map((t, i) => taskFrom(t, i,
|
|
18684
|
+
const tasks = raw.tasks.map((t, i) => taskFrom(t, i, dirname10(abs)));
|
|
18403
18685
|
const seen = /* @__PURE__ */ new Set();
|
|
18404
18686
|
for (const t of tasks) {
|
|
18405
18687
|
if (seen.has(t.id)) throw new ResearchError(`task manifest ${abs}: duplicate task id "${t.id}"`);
|
|
18406
18688
|
seen.add(t.id);
|
|
18407
18689
|
}
|
|
18408
|
-
return { path: abs, sha256:
|
|
18690
|
+
return { path: abs, sha256: createHash13("sha256").update(bytes).digest("hex"), tasks };
|
|
18409
18691
|
}
|
|
18410
18692
|
var MANIFEST_VERSION;
|
|
18411
18693
|
var init_manifest = __esm({
|
|
@@ -18594,18 +18876,18 @@ var init_record = __esm({
|
|
|
18594
18876
|
|
|
18595
18877
|
// src/research/run.ts
|
|
18596
18878
|
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
18597
|
-
import { existsSync as
|
|
18598
|
-
import { join as
|
|
18879
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync9, readFileSync as readFileSync23, renameSync as renameSync4, rmSync as rmSync11, writeFileSync as writeFileSync11 } from "node:fs";
|
|
18880
|
+
import { join as join24, resolve as resolve15 } from "node:path";
|
|
18599
18881
|
function git6(args, cwd) {
|
|
18600
18882
|
return execFileSync13("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
18601
18883
|
}
|
|
18602
18884
|
function pairRecordPath(ledger, task, pair) {
|
|
18603
|
-
return
|
|
18885
|
+
return join24(ledger, "pairs", `${task}--${pair}.json`);
|
|
18604
18886
|
}
|
|
18605
18887
|
function resumableRecord(path, expected) {
|
|
18606
18888
|
let raw;
|
|
18607
18889
|
try {
|
|
18608
|
-
raw = JSON.parse(
|
|
18890
|
+
raw = JSON.parse(readFileSync23(path, "utf8"));
|
|
18609
18891
|
} catch (e) {
|
|
18610
18892
|
throw new ResearchError(`ledger record ${path} is malformed (${errorMessage(e).split("\n")[0]}); remove it to regenerate the pair, or use a new --out`);
|
|
18611
18893
|
}
|
|
@@ -18646,9 +18928,9 @@ function writeRecordAtomically(path, text) {
|
|
|
18646
18928
|
renameSync4(tmp, path);
|
|
18647
18929
|
}
|
|
18648
18930
|
function freshWorkspace(ledger, task, pair, arm, sourceBase) {
|
|
18649
|
-
const ws3 =
|
|
18931
|
+
const ws3 = join24(ledger, "workspaces", `${task.id}--${pair}--${arm}`);
|
|
18650
18932
|
rmSync11(ws3, { recursive: true, force: true });
|
|
18651
|
-
|
|
18933
|
+
mkdirSync9(join24(ledger, "workspaces"), { recursive: true });
|
|
18652
18934
|
try {
|
|
18653
18935
|
execFileSync13("git", ["clone", "-q", "--no-hardlinks", task.repo, ws3], { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
|
|
18654
18936
|
} catch (e) {
|
|
@@ -18894,16 +19176,16 @@ function runResearch(opts) {
|
|
|
18894
19176
|
err2("tamperward research: the gated arm cannot start here (see `tamperward doctor`); run as a non-root user on Linux.");
|
|
18895
19177
|
return 2;
|
|
18896
19178
|
}
|
|
18897
|
-
const ledger =
|
|
19179
|
+
const ledger = resolve15(opts.out);
|
|
18898
19180
|
const pairs = opts.pairs ?? 1;
|
|
18899
|
-
|
|
19181
|
+
mkdirSync9(join24(ledger, "pairs"), { recursive: true });
|
|
18900
19182
|
for (const task of tasks) {
|
|
18901
19183
|
const existingRecords = /* @__PURE__ */ new Map();
|
|
18902
19184
|
let sourceBase = null;
|
|
18903
19185
|
try {
|
|
18904
19186
|
for (let pair = 1; pair <= pairs; pair++) {
|
|
18905
19187
|
const path = pairRecordPath(ledger, task.id, pair);
|
|
18906
|
-
if (!
|
|
19188
|
+
if (!existsSync17(path)) continue;
|
|
18907
19189
|
const existing = resumableRecord(path, {
|
|
18908
19190
|
task: task.id,
|
|
18909
19191
|
pair,
|
|
@@ -19028,10 +19310,10 @@ var init_run2 = __esm({
|
|
|
19028
19310
|
});
|
|
19029
19311
|
|
|
19030
19312
|
// src/research/summarize.ts
|
|
19031
|
-
import { readdirSync as readdirSync7, readFileSync as
|
|
19032
|
-
import { join as
|
|
19313
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync24 } from "node:fs";
|
|
19314
|
+
import { join as join25, resolve as resolve16 } from "node:path";
|
|
19033
19315
|
function readLedger2(dir) {
|
|
19034
|
-
const pairsDir =
|
|
19316
|
+
const pairsDir = join25(resolve16(dir), "pairs");
|
|
19035
19317
|
let names;
|
|
19036
19318
|
try {
|
|
19037
19319
|
names = readdirSync7(pairsDir).filter((n) => n.endsWith(".json")).sort();
|
|
@@ -19040,10 +19322,10 @@ function readLedger2(dir) {
|
|
|
19040
19322
|
}
|
|
19041
19323
|
if (names.length === 0) throw new ResearchError(`ledger ${pairsDir} holds no pair records`);
|
|
19042
19324
|
return names.map((n) => {
|
|
19043
|
-
const path =
|
|
19325
|
+
const path = join25(pairsDir, n);
|
|
19044
19326
|
let raw;
|
|
19045
19327
|
try {
|
|
19046
|
-
raw = JSON.parse(
|
|
19328
|
+
raw = JSON.parse(readFileSync24(path, "utf8"));
|
|
19047
19329
|
} catch (e) {
|
|
19048
19330
|
throw new ResearchError(`ledger record ${path} is not valid JSON: ${errorMessage(e)}`);
|
|
19049
19331
|
}
|
|
@@ -19284,6 +19566,17 @@ function parseDoctor(args) {
|
|
|
19284
19566
|
}
|
|
19285
19567
|
return o;
|
|
19286
19568
|
}
|
|
19569
|
+
function parseStats(args) {
|
|
19570
|
+
const o = {};
|
|
19571
|
+
for (let i = 0; i < args.length; i++) {
|
|
19572
|
+
const a = args[i];
|
|
19573
|
+
if (a === "--cwd") o.cwd = args[++i];
|
|
19574
|
+
else if (a === "--file") o.file = args[++i];
|
|
19575
|
+
else if (a === "--since") o.since = args[++i];
|
|
19576
|
+
else if (a === "--json") o.json = true;
|
|
19577
|
+
}
|
|
19578
|
+
return o;
|
|
19579
|
+
}
|
|
19287
19580
|
function parseCheck(args) {
|
|
19288
19581
|
const o = {};
|
|
19289
19582
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -19423,6 +19716,16 @@ function validateCliArgs(cmd, args) {
|
|
|
19423
19716
|
}
|
|
19424
19717
|
}).error;
|
|
19425
19718
|
}
|
|
19719
|
+
if (cmd === "stats") {
|
|
19720
|
+
return validateFlatArgs(args, {
|
|
19721
|
+
flags: ["--json"],
|
|
19722
|
+
values: {
|
|
19723
|
+
"--cwd": "string",
|
|
19724
|
+
"--file": "string",
|
|
19725
|
+
"--since": "string"
|
|
19726
|
+
}
|
|
19727
|
+
}).error;
|
|
19728
|
+
}
|
|
19426
19729
|
if (cmd === "onboard") {
|
|
19427
19730
|
const parsed = validateFlatArgs(args, {
|
|
19428
19731
|
flags: ["--yes", "--skip-demo", "--demo", "--no-github"],
|
|
@@ -19583,6 +19886,12 @@ Formats:
|
|
|
19583
19886
|
tamperward research summarize --ledger D aggregate measured pairs into model behaviour,
|
|
19584
19887
|
independent outcome, TamperWard hits/misses
|
|
19585
19888
|
and paired counts \u2014 no composite score
|
|
19889
|
+
tamperward stats [--file F] [--since 30d] aggregate privacy-safe hook/sweep audit
|
|
19890
|
+
[--json] [--cwd D] events by rule and enforcement surface.
|
|
19891
|
+
Defaults to the repository-local
|
|
19892
|
+
.git/tamperward/audit.jsonl (or
|
|
19893
|
+
TAMPERWARD_AUDIT_LOG). A finding is an
|
|
19894
|
+
integrity signal, not proof of agent intent.
|
|
19586
19895
|
tamperward allow <rule> --reason "..." record a human sign-off (local audit ledger)
|
|
19587
19896
|
[--file F] [--cwd D]
|
|
19588
19897
|
tamperward onboard [--yes] [--cwd D] guided first-run setup: preflight, the
|
|
@@ -19653,6 +19962,8 @@ function main(argv) {
|
|
|
19653
19962
|
return runInit(parseInit(rest));
|
|
19654
19963
|
case "doctor":
|
|
19655
19964
|
return runDoctor(parseDoctor(rest));
|
|
19965
|
+
case "stats":
|
|
19966
|
+
return runStats(parseStats(rest));
|
|
19656
19967
|
case "onboard":
|
|
19657
19968
|
return runOnboard(parseOnboard(rest));
|
|
19658
19969
|
case "watch":
|
|
@@ -19714,13 +20025,14 @@ var init_main = __esm({
|
|
|
19714
20025
|
init_watch();
|
|
19715
20026
|
init_onboard();
|
|
19716
20027
|
init_research();
|
|
20028
|
+
init_audit();
|
|
19717
20029
|
}
|
|
19718
20030
|
});
|
|
19719
20031
|
|
|
19720
20032
|
// src/cli/index.ts
|
|
19721
20033
|
init_hook_client();
|
|
19722
20034
|
init_exit();
|
|
19723
|
-
import { readFileSync as
|
|
20035
|
+
import { readFileSync as readFileSync25 } from "node:fs";
|
|
19724
20036
|
function loadMain() {
|
|
19725
20037
|
return Promise.resolve().then(() => (init_main(), main_exports));
|
|
19726
20038
|
}
|
|
@@ -19733,7 +20045,7 @@ async function launch(argv) {
|
|
|
19733
20045
|
if (kind && hookServiceEnabled()) {
|
|
19734
20046
|
let raw = null;
|
|
19735
20047
|
try {
|
|
19736
|
-
raw =
|
|
20048
|
+
raw = readFileSync25(0, "utf8");
|
|
19737
20049
|
} catch {
|
|
19738
20050
|
raw = null;
|
|
19739
20051
|
}
|