tamperward 2.40.0 → 2.42.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/dist/cli/index.js +658 -345
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -162,7 +162,39 @@ function runtimeWiringOf(path) {
|
|
|
162
162
|
function resolvesToRuntimeWiring(path) {
|
|
163
163
|
return runtimeWiringOf(path) ?? (isAbsolute(path) ? runtimeWiringOf(canonicalPath(path)) : null);
|
|
164
164
|
}
|
|
165
|
-
|
|
165
|
+
function codexWiringTables() {
|
|
166
|
+
const q = (cmd) => JSON.stringify(cmd);
|
|
167
|
+
return [
|
|
168
|
+
`# ${RUNTIME_WIRING_NOTE}`,
|
|
169
|
+
"[hooks]",
|
|
170
|
+
"[[hooks.PreToolUse]]",
|
|
171
|
+
'matcher = "*"',
|
|
172
|
+
"[[hooks.PreToolUse.hooks]]",
|
|
173
|
+
'type = "command"',
|
|
174
|
+
`command = ${q(CODEX_HOOK_CMD)}`,
|
|
175
|
+
"[[hooks.Stop]]",
|
|
176
|
+
"[[hooks.Stop.hooks]]",
|
|
177
|
+
'type = "command"',
|
|
178
|
+
`command = ${q(CODEX_SWEEP_CMD)}`,
|
|
179
|
+
""
|
|
180
|
+
].join("\n");
|
|
181
|
+
}
|
|
182
|
+
function copilotWiringDocument() {
|
|
183
|
+
return {
|
|
184
|
+
version: 1,
|
|
185
|
+
hooks: {
|
|
186
|
+
preToolUse: [{ type: "command", command: COPILOT_HOOK_CMD, timeout: 60 }],
|
|
187
|
+
agentStop: [{ type: "command", command: COPILOT_SWEEP_CMD, timeout: 60 }]
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function runtimeGatePins(text) {
|
|
192
|
+
return [...text.matchAll(RUNTIME_GATE_RE)].map((m) => m[1]);
|
|
193
|
+
}
|
|
194
|
+
function withoutRuntimeGatePins(text) {
|
|
195
|
+
return text.replace(RUNTIME_GATE_RE, "tamperward@<pin> $2 $3");
|
|
196
|
+
}
|
|
197
|
+
var PLAIN_SEMVER, RESOLVED_TW_VERSION, TW_VERSION, GIT_HEAD_RE, TW_COMMIT, PRE_TOOLS, PRE_MATCHER, MARKER, NPX_AUTHORITY, AUTHORITY_FAIL_CLOSED, HOOK_CMD, SWEEP_CMD, PRECOMMIT_CMD, OURS, INIT_SCRIPT, CLAUDE_SETTINGS, CODEX_HOOK_CONFIG, COPILOT_HOOK_CONFIG, CODEX_HOOK_CMD, CODEX_SWEEP_CMD, COPILOT_HOOK_CMD, COPILOT_SWEEP_CMD, RUNTIME_GATE_RE, RUNTIME_WIRING_NOTE, COPILOT_WIRING_NOTE;
|
|
166
198
|
var init_wiring = __esm({
|
|
167
199
|
"src/wiring.ts"() {
|
|
168
200
|
"use strict";
|
|
@@ -185,6 +217,13 @@ var init_wiring = __esm({
|
|
|
185
217
|
CLAUDE_SETTINGS = /(?:^|\/)(?:\.claude\/settings(?:\.local)?\.json|managed-settings\.json)$/;
|
|
186
218
|
CODEX_HOOK_CONFIG = /(?:^|\/)\.codex\/config\.toml$/;
|
|
187
219
|
COPILOT_HOOK_CONFIG = /(?:^|\/)\.github\/hooks\/tamperward\.json$/;
|
|
220
|
+
CODEX_HOOK_CMD = `${NPX_AUTHORITY} tamperward@${TW_VERSION} hook codex${AUTHORITY_FAIL_CLOSED}`;
|
|
221
|
+
CODEX_SWEEP_CMD = `${NPX_AUTHORITY} tamperward@${TW_VERSION} sweep codex${AUTHORITY_FAIL_CLOSED}`;
|
|
222
|
+
COPILOT_HOOK_CMD = `${NPX_AUTHORITY} tamperward@${TW_VERSION} hook copilot${AUTHORITY_FAIL_CLOSED}`;
|
|
223
|
+
COPILOT_SWEEP_CMD = `${NPX_AUTHORITY} tamperward@${TW_VERSION} sweep copilot${AUTHORITY_FAIL_CLOSED}`;
|
|
224
|
+
RUNTIME_GATE_RE = /\btamperward@(\S+) (hook|sweep) (codex|copilot)\b/g;
|
|
225
|
+
RUNTIME_WIRING_NOTE = "tamperward: experimental hook wiring. This runtime has not passed a pinned live qualification; run `tamperward runtime verify` to see what is proven. Editing these entries is editing the gate.";
|
|
226
|
+
COPILOT_WIRING_NOTE = RUNTIME_WIRING_NOTE;
|
|
188
227
|
}
|
|
189
228
|
});
|
|
190
229
|
|
|
@@ -2765,11 +2804,20 @@ function codexHooksTables(toml) {
|
|
|
2765
2804
|
}
|
|
2766
2805
|
return out3.join("\n");
|
|
2767
2806
|
}
|
|
2807
|
+
function runtimeGatePinRaise(was, now) {
|
|
2808
|
+
if (now === was) return "same";
|
|
2809
|
+
if (withoutRuntimeGatePins(now) !== withoutRuntimeGatePins(was)) return "changed";
|
|
2810
|
+
const before = runtimeGatePins(was);
|
|
2811
|
+
const after = runtimeGatePins(now);
|
|
2812
|
+
return after.every((pin, i) => pinNotBelow(pin, before[i])) ? "raised" : "lowered";
|
|
2813
|
+
}
|
|
2768
2814
|
function codexHooksWeakening(before, after) {
|
|
2769
2815
|
const was = codexHooksTables(before);
|
|
2770
2816
|
if (!was) return [];
|
|
2771
2817
|
const now = codexHooksTables(after);
|
|
2772
|
-
|
|
2818
|
+
const change = runtimeGatePinRaise(was, now);
|
|
2819
|
+
if (change === "same" || change === "raised") return [];
|
|
2820
|
+
if (change === "lowered") return ["the tamperward pin in the [hooks] tables was lowered or is no longer a plain version \u2014 the gate only ever raises its pin"];
|
|
2773
2821
|
if (GATE.test(was) && !GATE.test(now)) return ["the tamperward hook entries were removed from the [hooks] tables \u2014 Codex no longer runs the gate"];
|
|
2774
2822
|
return ["the [hooks] tables changed \u2014 the PreToolUse and Stop wiring Codex reads is no longer what it was"];
|
|
2775
2823
|
}
|
|
@@ -2788,7 +2836,9 @@ function copilotHooksWeakening(before, after) {
|
|
|
2788
2836
|
if (was === null || was.hooks === null) return [];
|
|
2789
2837
|
const now = hooksMember(after);
|
|
2790
2838
|
if (now === null) return ["the file no longer parses as JSON \u2014 the Copilot CLI loads no hook from it"];
|
|
2791
|
-
|
|
2839
|
+
const change = runtimeGatePinRaise(was.hooks, now.hooks ?? "");
|
|
2840
|
+
if (change === "same" || change === "raised") return [];
|
|
2841
|
+
if (change === "lowered") return ['the tamperward pin in "hooks" was lowered or is no longer a plain version \u2014 the gate only ever raises its pin'];
|
|
2792
2842
|
if (GATE.test(was.hooks) && !GATE.test(now.hooks ?? "")) return ['the tamperward hook entries were removed from "hooks" \u2014 the Copilot CLI no longer runs the gate'];
|
|
2793
2843
|
return ['the "hooks" entries changed \u2014 the preToolUse and agentStop wiring the Copilot CLI reads is no longer what it was'];
|
|
2794
2844
|
}
|
|
@@ -11278,7 +11328,7 @@ var init_signoff = __esm({
|
|
|
11278
11328
|
function machineOutput(payload) {
|
|
11279
11329
|
return { ...payload, schema_version: MACHINE_SCHEMA_VERSION };
|
|
11280
11330
|
}
|
|
11281
|
-
var MACHINE_SCHEMA_VERSION, VERIFY_VERDICTS, RUN_VERDICTS;
|
|
11331
|
+
var MACHINE_SCHEMA_VERSION, VERIFY_VERDICTS, VERIFY_CANNOT_VERIFY_REASONS, RUN_VERDICTS;
|
|
11282
11332
|
var init_machine_output = __esm({
|
|
11283
11333
|
"src/machine-output.ts"() {
|
|
11284
11334
|
"use strict";
|
|
@@ -11290,6 +11340,23 @@ var init_machine_output = __esm({
|
|
|
11290
11340
|
"BUDGET_EXCEEDED",
|
|
11291
11341
|
"CANNOT_VERIFY"
|
|
11292
11342
|
];
|
|
11343
|
+
VERIFY_CANNOT_VERIFY_REASONS = [
|
|
11344
|
+
"INVALID_ARGUMENTS",
|
|
11345
|
+
"POLICY_ERROR",
|
|
11346
|
+
"BASE_NOT_ANCESTOR",
|
|
11347
|
+
"NO_SUITE_COMMAND",
|
|
11348
|
+
"VERIFIER_BACKEND_UNAVAILABLE",
|
|
11349
|
+
"LOCAL_VERIFIER_UNSUPPORTED_PLATFORM",
|
|
11350
|
+
"DEPENDENCY_ENVIRONMENT_UNATTESTABLE",
|
|
11351
|
+
"BASE_UNRESOLVABLE",
|
|
11352
|
+
"DEPENDENCY_DRIFT",
|
|
11353
|
+
"MATERIALIZATION_FAILED",
|
|
11354
|
+
"VERIFIER_BACKEND_RUNTIME_FAILURE",
|
|
11355
|
+
"VERIFIER_RESOURCE_EXHAUSTED",
|
|
11356
|
+
"WORKTREE_CHANGED",
|
|
11357
|
+
"PRISTINE_INTEGRITY_CHANGED",
|
|
11358
|
+
"PATH_CASE_COLLISION"
|
|
11359
|
+
];
|
|
11293
11360
|
RUN_VERDICTS = [
|
|
11294
11361
|
"VERIFIED",
|
|
11295
11362
|
"AGENT_TIMEOUT",
|
|
@@ -14012,11 +14079,200 @@ var init_verifier_limits = __esm({
|
|
|
14012
14079
|
}
|
|
14013
14080
|
});
|
|
14014
14081
|
|
|
14082
|
+
// src/immutable.ts
|
|
14083
|
+
function isPlainData(value) {
|
|
14084
|
+
if (value === null || typeof value !== "object") return false;
|
|
14085
|
+
const proto = Object.getPrototypeOf(value);
|
|
14086
|
+
return proto === Object.prototype || proto === Array.prototype || proto === null;
|
|
14087
|
+
}
|
|
14088
|
+
function deepFreeze(value) {
|
|
14089
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
14090
|
+
const walk2 = (node) => {
|
|
14091
|
+
if (!isPlainData(node) || seen.has(node)) return;
|
|
14092
|
+
seen.add(node);
|
|
14093
|
+
Object.freeze(node);
|
|
14094
|
+
for (const key2 of Reflect.ownKeys(node)) walk2(Reflect.get(node, key2));
|
|
14095
|
+
};
|
|
14096
|
+
walk2(value);
|
|
14097
|
+
return value;
|
|
14098
|
+
}
|
|
14099
|
+
var init_immutable = __esm({
|
|
14100
|
+
"src/immutable.ts"() {
|
|
14101
|
+
"use strict";
|
|
14102
|
+
}
|
|
14103
|
+
});
|
|
14104
|
+
|
|
14105
|
+
// src/runtimes.ts
|
|
14106
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
|
|
14107
|
+
import { join as join15 } from "node:path";
|
|
14108
|
+
function isRecord4(x) {
|
|
14109
|
+
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
14110
|
+
}
|
|
14111
|
+
function hooksBeyondTamperward(hooks) {
|
|
14112
|
+
if (!isRecord4(hooks)) return hooks != null;
|
|
14113
|
+
for (const entries of Object.values(hooks)) {
|
|
14114
|
+
if (!Array.isArray(entries)) {
|
|
14115
|
+
if (entries != null) return true;
|
|
14116
|
+
continue;
|
|
14117
|
+
}
|
|
14118
|
+
for (const entry of entries) {
|
|
14119
|
+
const hs = isRecord4(entry) ? entry.hooks : null;
|
|
14120
|
+
if (!Array.isArray(hs)) {
|
|
14121
|
+
if (hs != null) return true;
|
|
14122
|
+
continue;
|
|
14123
|
+
}
|
|
14124
|
+
for (const h of hs) {
|
|
14125
|
+
const cmd = isRecord4(h) ? String(h.command ?? "") : "";
|
|
14126
|
+
if (!/tamperward/.test(cmd)) return true;
|
|
14127
|
+
}
|
|
14128
|
+
}
|
|
14129
|
+
}
|
|
14130
|
+
return false;
|
|
14131
|
+
}
|
|
14132
|
+
function claudeSettingsBeyondTamperward(cwd) {
|
|
14133
|
+
const path = join15(cwd, ".claude", "settings.json");
|
|
14134
|
+
let raw;
|
|
14135
|
+
try {
|
|
14136
|
+
if (!existsSync7(path)) return false;
|
|
14137
|
+
raw = readFileSync8(path, "utf8");
|
|
14138
|
+
} catch {
|
|
14139
|
+
return false;
|
|
14140
|
+
}
|
|
14141
|
+
let parsed;
|
|
14142
|
+
try {
|
|
14143
|
+
parsed = JSON.parse(raw);
|
|
14144
|
+
} catch {
|
|
14145
|
+
return raw.trim().length > 0;
|
|
14146
|
+
}
|
|
14147
|
+
if (!isRecord4(parsed)) return raw.trim().length > 0;
|
|
14148
|
+
for (const [key2, value] of Object.entries(parsed)) {
|
|
14149
|
+
if (key2 === "disableAllHooks") continue;
|
|
14150
|
+
if (key2 === "hooks") {
|
|
14151
|
+
if (hooksBeyondTamperward(value)) return true;
|
|
14152
|
+
continue;
|
|
14153
|
+
}
|
|
14154
|
+
return true;
|
|
14155
|
+
}
|
|
14156
|
+
return false;
|
|
14157
|
+
}
|
|
14158
|
+
function codexDirBeyondTamperward(cwd) {
|
|
14159
|
+
const dir = join15(cwd, ".codex");
|
|
14160
|
+
let entries;
|
|
14161
|
+
try {
|
|
14162
|
+
if (!existsSync7(dir) || !statSync3(dir).isDirectory()) return false;
|
|
14163
|
+
entries = readdirSync3(dir);
|
|
14164
|
+
} catch {
|
|
14165
|
+
return false;
|
|
14166
|
+
}
|
|
14167
|
+
if (entries.some((e) => e !== "config.toml")) return true;
|
|
14168
|
+
if (!entries.includes("config.toml")) return false;
|
|
14169
|
+
let raw;
|
|
14170
|
+
try {
|
|
14171
|
+
raw = readFileSync8(join15(dir, "config.toml"), "utf8");
|
|
14172
|
+
} catch {
|
|
14173
|
+
return false;
|
|
14174
|
+
}
|
|
14175
|
+
return raw.split("\n").some((line) => {
|
|
14176
|
+
const t = line.trim();
|
|
14177
|
+
return t !== "" && !t.startsWith("#") && !/^\[\[?hooks/.test(t) && !codexWiringLines().has(t);
|
|
14178
|
+
});
|
|
14179
|
+
}
|
|
14180
|
+
function codexWiringLines() {
|
|
14181
|
+
if (!wiringLines) wiringLines = new Set(codexWiringTables().split("\n").map((l) => l.trim()).filter(Boolean));
|
|
14182
|
+
return wiringLines;
|
|
14183
|
+
}
|
|
14184
|
+
function detectRuntimes(cwd) {
|
|
14185
|
+
const found = [];
|
|
14186
|
+
for (const rt of KNOWN_RUNTIMES) {
|
|
14187
|
+
const marker = rt.markers.find((m) => {
|
|
14188
|
+
try {
|
|
14189
|
+
return existsSync7(join15(cwd, m));
|
|
14190
|
+
} catch {
|
|
14191
|
+
return false;
|
|
14192
|
+
}
|
|
14193
|
+
});
|
|
14194
|
+
const matchedMarker = marker ?? rt.detectExtra?.(cwd) ?? void 0;
|
|
14195
|
+
if (matchedMarker !== void 0) found.push({ ...rt, matchedMarker });
|
|
14196
|
+
}
|
|
14197
|
+
return found;
|
|
14198
|
+
}
|
|
14199
|
+
function hasInLoopRuntime(detected) {
|
|
14200
|
+
return detected.some((r) => r.steering === "in-loop");
|
|
14201
|
+
}
|
|
14202
|
+
function detectionHeadline(detected) {
|
|
14203
|
+
if (detected.length === 0) {
|
|
14204
|
+
return "No agent runtime detected. Wiring Claude Code in-loop steering plus the agent-neutral layers (pre-commit + CI) that protect any agent or human.";
|
|
14205
|
+
}
|
|
14206
|
+
if (detected.length === 1) {
|
|
14207
|
+
const r = detected[0];
|
|
14208
|
+
return `Detected ${r.label} \u2014 ${r.note}`;
|
|
14209
|
+
}
|
|
14210
|
+
const parts = detected.map((r) => `${r.label} (${r.steering === "in-loop" ? "in-loop" : "neutral layers"})`);
|
|
14211
|
+
return `Detected ${detected.length} agent runtimes: ${parts.join(", ")}.`;
|
|
14212
|
+
}
|
|
14213
|
+
function neutralOnlyCaveat(detected) {
|
|
14214
|
+
const neutral = detected.filter((r) => r.steering === "neutral");
|
|
14215
|
+
if (neutral.length === 0) return null;
|
|
14216
|
+
const names = neutral.map((r) => r.label).join(", ");
|
|
14217
|
+
const claudeAlso = hasInLoopRuntime(detected);
|
|
14218
|
+
const lead = claudeAlso ? `In-loop steering (deny-before-execute) ships for Claude Code only today: Claude Code here gets it, but on ${names} your live protection is ` : `In-loop steering (deny-before-execute) ships for Claude Code only today, so on ${names} your live protection is `;
|
|
14219
|
+
const tail = claudeAlso ? "" : " The Claude hook wiring is still written and is inert unless Claude Code runs in this repository.";
|
|
14220
|
+
return `${lead}the agent-neutral layers: pre-commit and CI.${tail}`;
|
|
14221
|
+
}
|
|
14222
|
+
var wiringLines, KNOWN_RUNTIMES;
|
|
14223
|
+
var init_runtimes = __esm({
|
|
14224
|
+
"src/runtimes.ts"() {
|
|
14225
|
+
"use strict";
|
|
14226
|
+
init_immutable();
|
|
14227
|
+
init_wiring();
|
|
14228
|
+
wiringLines = null;
|
|
14229
|
+
KNOWN_RUNTIMES = deepFreeze([
|
|
14230
|
+
{
|
|
14231
|
+
id: "claude-code",
|
|
14232
|
+
// Markers are Claude Code files `init` never writes, so detection never mistakes
|
|
14233
|
+
// TamperWard's own generated `.claude/settings.json` for evidence a human runs
|
|
14234
|
+
// Claude here (#526). `detectExtra` looks at that generated file's CONTENT for
|
|
14235
|
+
// real use (configuration beyond the two hooks init merges in).
|
|
14236
|
+
label: "Claude Code",
|
|
14237
|
+
markers: ["CLAUDE.md", ".claude/settings.local.json", ".claude/commands", ".claude/agents"],
|
|
14238
|
+
detectExtra: (cwd) => claudeSettingsBeyondTamperward(cwd) ? ".claude/settings.json" : null,
|
|
14239
|
+
steering: "in-loop",
|
|
14240
|
+
note: "native in-loop steering (PreToolUse deny + Stop sweep) plus the agent-neutral layers."
|
|
14241
|
+
},
|
|
14242
|
+
{
|
|
14243
|
+
id: "cursor",
|
|
14244
|
+
label: "Cursor",
|
|
14245
|
+
markers: [".cursor", ".cursorrules"],
|
|
14246
|
+
steering: "neutral",
|
|
14247
|
+
note: "agent-neutral layers (pre-commit + CI) today; no in-loop adapter ships for Cursor."
|
|
14248
|
+
},
|
|
14249
|
+
{
|
|
14250
|
+
id: "copilot",
|
|
14251
|
+
label: "GitHub Copilot",
|
|
14252
|
+
markers: [".github/copilot-instructions.md", ".github/copilot"],
|
|
14253
|
+
steering: "neutral",
|
|
14254
|
+
note: "agent-neutral layers (pre-commit + CI) today; the experimental Copilot adapters are not qualified for in-loop steering (see `tamperward runtime verify`)."
|
|
14255
|
+
},
|
|
14256
|
+
{
|
|
14257
|
+
id: "codex",
|
|
14258
|
+
label: "an AGENTS.md-aware agent (e.g. Codex)",
|
|
14259
|
+
// `.codex/config.toml` is a file `init` writes (the hook wiring), so the directory
|
|
14260
|
+
// counts only for what else it holds, or for configuration in that file beyond the
|
|
14261
|
+
// hooks tables init appends (#526).
|
|
14262
|
+
markers: ["AGENTS.md"],
|
|
14263
|
+
detectExtra: (cwd) => codexDirBeyondTamperward(cwd) ? ".codex" : null,
|
|
14264
|
+
steering: "neutral",
|
|
14265
|
+
note: "agent-neutral layers (pre-commit + CI) today; the experimental Codex adapter is not qualified for in-loop steering (see `tamperward runtime verify`)."
|
|
14266
|
+
}
|
|
14267
|
+
]);
|
|
14268
|
+
}
|
|
14269
|
+
});
|
|
14270
|
+
|
|
14015
14271
|
// src/cli/init.ts
|
|
14016
14272
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
14017
14273
|
import { createHash as createHash8 } from "node:crypto";
|
|
14018
|
-
import { existsSync as
|
|
14019
|
-
import { basename as basename3, dirname as dirname5, isAbsolute as isAbsolute9, join as
|
|
14274
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync9, realpathSync as realpathSync5 } from "node:fs";
|
|
14275
|
+
import { basename as basename3, dirname as dirname5, isAbsolute as isAbsolute9, join as join16, relative as relative4, resolve as resolve6 } from "node:path";
|
|
14020
14276
|
function gitConfig(cwd, key2) {
|
|
14021
14277
|
try {
|
|
14022
14278
|
return execFileSync6("git", ["config", "--get", key2], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || null;
|
|
@@ -14109,34 +14365,34 @@ function verifierCandidates(cwd) {
|
|
|
14109
14365
|
if (!candidates.includes(command)) candidates.push(command);
|
|
14110
14366
|
};
|
|
14111
14367
|
try {
|
|
14112
|
-
const pkg = JSON.parse(
|
|
14368
|
+
const pkg = JSON.parse(readFileSync9(join16(cwd, "package.json"), "utf8"));
|
|
14113
14369
|
const test = isRecord(pkg) && isRecord(pkg.scripts) ? pkg.scripts.test : void 0;
|
|
14114
14370
|
if (typeof test === "string" && test.trim() !== "" && !/error:\s*no test specified/i.test(test)) add("npm test");
|
|
14115
14371
|
} catch {
|
|
14116
14372
|
}
|
|
14117
14373
|
try {
|
|
14118
|
-
const pyproject =
|
|
14374
|
+
const pyproject = readFileSync9(join16(cwd, "pyproject.toml"), "utf8");
|
|
14119
14375
|
if (/^\s*\[tool\.pytest(?:\.ini_options)?\]\s*$/m.test(pyproject)) add("pytest");
|
|
14120
14376
|
} catch {
|
|
14121
14377
|
}
|
|
14122
|
-
if (
|
|
14378
|
+
if (existsSync8(join16(cwd, "pytest.ini"))) add("pytest");
|
|
14123
14379
|
try {
|
|
14124
|
-
const setupCfg =
|
|
14380
|
+
const setupCfg = readFileSync9(join16(cwd, "setup.cfg"), "utf8");
|
|
14125
14381
|
if (/^\s*\[tool:pytest\]\s*$/m.test(setupCfg)) add("pytest");
|
|
14126
14382
|
} catch {
|
|
14127
14383
|
}
|
|
14128
14384
|
try {
|
|
14129
|
-
const tox =
|
|
14385
|
+
const tox = readFileSync9(join16(cwd, "tox.ini"), "utf8");
|
|
14130
14386
|
if (/^\s*\[tox\]\s*$/m.test(tox)) add("tox");
|
|
14131
14387
|
} catch {
|
|
14132
14388
|
}
|
|
14133
14389
|
try {
|
|
14134
|
-
const cargo =
|
|
14390
|
+
const cargo = readFileSync9(join16(cwd, "Cargo.toml"), "utf8");
|
|
14135
14391
|
if (/^\s*\[(?:package|workspace)\]\s*(?:#.*)?$/m.test(cargo)) add("cargo test");
|
|
14136
14392
|
} catch {
|
|
14137
14393
|
}
|
|
14138
14394
|
try {
|
|
14139
|
-
const goMod =
|
|
14395
|
+
const goMod = readFileSync9(join16(cwd, "go.mod"), "utf8");
|
|
14140
14396
|
if (/^\s*module\s+\S+\s*(?:\/\/.*)?$/m.test(goMod)) add("go test ./...");
|
|
14141
14397
|
} catch {
|
|
14142
14398
|
}
|
|
@@ -14176,10 +14432,10 @@ function verifierSetupMessage(cwd) {
|
|
|
14176
14432
|
return lines.join("\n");
|
|
14177
14433
|
}
|
|
14178
14434
|
function planPolicy(cwd) {
|
|
14179
|
-
const path =
|
|
14435
|
+
const path = join16(cwd, POLICY_FILE);
|
|
14180
14436
|
const refused = refuseNonRegular(path);
|
|
14181
14437
|
if (refused) return { item: "policy", path: POLICY_FILE, status: "error", detail: refused };
|
|
14182
|
-
if (
|
|
14438
|
+
if (existsSync8(path)) {
|
|
14183
14439
|
try {
|
|
14184
14440
|
loadPolicy(cwd);
|
|
14185
14441
|
} catch (e) {
|
|
@@ -14197,8 +14453,8 @@ function planPolicy(cwd) {
|
|
|
14197
14453
|
}
|
|
14198
14454
|
function planGitignore(cwd) {
|
|
14199
14455
|
const rel = ".gitignore";
|
|
14200
|
-
const path =
|
|
14201
|
-
if (!
|
|
14456
|
+
const path = join16(cwd, rel);
|
|
14457
|
+
if (!existsSync8(join16(cwd, "node_modules"))) return null;
|
|
14202
14458
|
let existing = "";
|
|
14203
14459
|
let kind;
|
|
14204
14460
|
try {
|
|
@@ -14208,7 +14464,7 @@ function planGitignore(cwd) {
|
|
|
14208
14464
|
}
|
|
14209
14465
|
const refused = refuseNonRegular(path);
|
|
14210
14466
|
if (refused) return { item: "gitignore", path: rel, status: "error", detail: refused };
|
|
14211
|
-
if (kind === "file") existing =
|
|
14467
|
+
if (kind === "file") existing = readFileSync9(path, "utf8");
|
|
14212
14468
|
try {
|
|
14213
14469
|
const tracked = execFileSync6("git", ["ls-files", "--cached", "--", "node_modules"], {
|
|
14214
14470
|
cwd,
|
|
@@ -14259,14 +14515,14 @@ function gateVerdicts(event, groups, needle) {
|
|
|
14259
14515
|
}
|
|
14260
14516
|
function planClaudeHooks(cwd) {
|
|
14261
14517
|
const rel = ".claude/settings.json";
|
|
14262
|
-
const path =
|
|
14518
|
+
const path = join16(cwd, rel);
|
|
14263
14519
|
const refused = refuseNonRegular(path);
|
|
14264
14520
|
if (refused) return { item: "agent", path: rel, status: "error", detail: refused };
|
|
14265
14521
|
let settings = {};
|
|
14266
|
-
if (
|
|
14522
|
+
if (existsSync8(path)) {
|
|
14267
14523
|
let parsed;
|
|
14268
14524
|
try {
|
|
14269
|
-
parsed = JSON.parse(
|
|
14525
|
+
parsed = JSON.parse(readFileSync9(path, "utf8"));
|
|
14270
14526
|
} catch {
|
|
14271
14527
|
return {
|
|
14272
14528
|
item: "agent",
|
|
@@ -14315,7 +14571,7 @@ function planClaudeHooks(cwd) {
|
|
|
14315
14571
|
return {
|
|
14316
14572
|
item: "agent",
|
|
14317
14573
|
path: rel,
|
|
14318
|
-
status:
|
|
14574
|
+
status: existsSync8(path) ? "update" : "create",
|
|
14319
14575
|
detail: [
|
|
14320
14576
|
needPre && "wire PreToolUse deny",
|
|
14321
14577
|
needStop && "wire Stop sweep",
|
|
@@ -14359,7 +14615,7 @@ function hooksDir(cwd) {
|
|
|
14359
14615
|
if (out3) return resolve6(cwd, out3);
|
|
14360
14616
|
} catch {
|
|
14361
14617
|
}
|
|
14362
|
-
return
|
|
14618
|
+
return existsSync8(join16(cwd, ".git")) ? join16(cwd, ".git", "hooks") : null;
|
|
14363
14619
|
}
|
|
14364
14620
|
function display(cwd, path) {
|
|
14365
14621
|
const rel = relative4(cwd, path);
|
|
@@ -14386,29 +14642,29 @@ function planPreCommit(cwd) {
|
|
|
14386
14642
|
const skip = (detail) => ({ item: "pre-commit", path: "(none)", status: "skip", detail });
|
|
14387
14643
|
let path;
|
|
14388
14644
|
let note;
|
|
14389
|
-
if (
|
|
14390
|
-
path =
|
|
14645
|
+
if (existsSync8(join16(cwd, ".husky"))) {
|
|
14646
|
+
path = join16(cwd, ".husky", "pre-commit");
|
|
14391
14647
|
note = "husky";
|
|
14392
14648
|
} else {
|
|
14393
|
-
const managed = MANAGED_HOOKS.find((m) => m.configs.some((c) =>
|
|
14649
|
+
const managed = MANAGED_HOOKS.find((m) => m.configs.some((c) => existsSync8(join16(cwd, c))));
|
|
14394
14650
|
if (managed) {
|
|
14395
14651
|
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}`);
|
|
14396
14652
|
}
|
|
14397
14653
|
const dir = hooksDir(cwd);
|
|
14398
14654
|
if (!dir) return skip("not a git repo and no .husky/ \u2014 nothing to wire");
|
|
14399
14655
|
if (basename3(dir) === "_" && basename3(dirname5(dir)) === ".husky") {
|
|
14400
|
-
path =
|
|
14656
|
+
path = join16(dirname5(dir), "pre-commit");
|
|
14401
14657
|
note = "husky";
|
|
14402
14658
|
} else {
|
|
14403
|
-
path =
|
|
14659
|
+
path = join16(dir, "pre-commit");
|
|
14404
14660
|
const rel2 = display(cwd, dir);
|
|
14405
|
-
note = rel2 ===
|
|
14661
|
+
note = rel2 === join16(".git", "hooks") ? "plain git hook (local-only: .git/hooks is not committed \u2014 consider husky to share it)" : `git hooks directory ${rel2} (core.hooksPath)`;
|
|
14406
14662
|
}
|
|
14407
14663
|
}
|
|
14408
14664
|
const rel = display(cwd, path);
|
|
14409
14665
|
const refused = refuseNonRegular(path);
|
|
14410
14666
|
if (refused) return { item: "pre-commit", path: rel, status: "error", detail: refused };
|
|
14411
|
-
const existing =
|
|
14667
|
+
const existing = existsSync8(path) ? readFileSync9(path, "utf8") : null;
|
|
14412
14668
|
const hookMode = () => existingMode(path, 493) | 73;
|
|
14413
14669
|
if (existing === null) {
|
|
14414
14670
|
return {
|
|
@@ -14494,12 +14750,12 @@ ${block.join("\n")}
|
|
|
14494
14750
|
function planCodeowners(cwd) {
|
|
14495
14751
|
const rel = ".github/CODEOWNERS";
|
|
14496
14752
|
const existingRel = [".github/CODEOWNERS", "CODEOWNERS", "docs/CODEOWNERS"].find(
|
|
14497
|
-
(r) => writeTargetKind(
|
|
14753
|
+
(r) => writeTargetKind(join16(cwd, r)) !== "absent"
|
|
14498
14754
|
);
|
|
14499
|
-
const path =
|
|
14755
|
+
const path = join16(cwd, existingRel ?? rel);
|
|
14500
14756
|
const refused = refuseNonRegular(path);
|
|
14501
14757
|
if (refused) return { item: "codeowners", path: existingRel ?? rel, status: "error", detail: refused };
|
|
14502
|
-
const existing = existingRel ?
|
|
14758
|
+
const existing = existingRel ? readFileSync9(path, "utf8") : null;
|
|
14503
14759
|
const missing = CODEOWNERS_PATHS.filter((p) => existing === null || !coveredBy(existing, p));
|
|
14504
14760
|
if (existingRel !== void 0 && missing.length === 0) {
|
|
14505
14761
|
return { item: "codeowners", path: existingRel, status: "ok", detail: "gate paths already have code owners" };
|
|
@@ -14532,14 +14788,14 @@ ${CODEOWNERS_MARK}
|
|
|
14532
14788
|
}
|
|
14533
14789
|
function planWorkflow(cwd, force) {
|
|
14534
14790
|
const rel = ".github/workflows/tamperward.yml";
|
|
14535
|
-
const path =
|
|
14791
|
+
const path = join16(cwd, rel);
|
|
14536
14792
|
const write = () => {
|
|
14537
14793
|
mkdirSync7(dirname5(path), { recursive: true });
|
|
14538
14794
|
atomicReplaceFile(path, workflowFile(WORKFLOW_CONTENT), existingMode(path, 420));
|
|
14539
14795
|
};
|
|
14540
14796
|
const refused = refuseNonRegular(path);
|
|
14541
14797
|
if (refused) return { item: "ci", path: rel, status: "error", detail: refused };
|
|
14542
|
-
if (!
|
|
14798
|
+
if (!existsSync8(path)) {
|
|
14543
14799
|
return {
|
|
14544
14800
|
item: "ci",
|
|
14545
14801
|
path: rel,
|
|
@@ -14550,7 +14806,7 @@ function planWorkflow(cwd, force) {
|
|
|
14550
14806
|
}
|
|
14551
14807
|
let src;
|
|
14552
14808
|
try {
|
|
14553
|
-
src =
|
|
14809
|
+
src = readFileSync9(path, "utf8");
|
|
14554
14810
|
} catch {
|
|
14555
14811
|
return { item: "ci", path: rel, status: "error", detail: "exists but cannot be read \u2014 refusing to overwrite" };
|
|
14556
14812
|
}
|
|
@@ -14598,6 +14854,96 @@ function sameDir(a, b) {
|
|
|
14598
14854
|
return false;
|
|
14599
14855
|
}
|
|
14600
14856
|
}
|
|
14857
|
+
function planCodexHooks(cwd) {
|
|
14858
|
+
const rel = ".codex/config.toml";
|
|
14859
|
+
const path = join16(cwd, rel);
|
|
14860
|
+
const item = "codex-hooks";
|
|
14861
|
+
const refused = refuseNonRegular(path);
|
|
14862
|
+
if (refused) return { item, path: rel, status: "error", detail: refused };
|
|
14863
|
+
const warning = RUNTIME_WARNING("Codex", "codex");
|
|
14864
|
+
const tables = codexWiringTables();
|
|
14865
|
+
if (!existsSync8(path)) {
|
|
14866
|
+
return {
|
|
14867
|
+
item,
|
|
14868
|
+
path: rel,
|
|
14869
|
+
status: "create",
|
|
14870
|
+
detail: "wire the PreToolUse deny and Stop sweep [hooks] tables (experimental)",
|
|
14871
|
+
warning,
|
|
14872
|
+
apply: () => {
|
|
14873
|
+
mkdirSync7(dirname5(path), { recursive: true });
|
|
14874
|
+
atomicReplaceFile(path, tables, 420);
|
|
14875
|
+
}
|
|
14876
|
+
};
|
|
14877
|
+
}
|
|
14878
|
+
const current = readFileSync9(path, "utf8");
|
|
14879
|
+
const existing = codexHooksTables(current);
|
|
14880
|
+
if (!existing) {
|
|
14881
|
+
const next = current + (current.endsWith("\n") || current === "" ? "" : "\n") + "\n" + tables;
|
|
14882
|
+
return {
|
|
14883
|
+
item,
|
|
14884
|
+
path: rel,
|
|
14885
|
+
status: "update",
|
|
14886
|
+
detail: "append the PreToolUse deny and Stop sweep [hooks] tables (experimental)",
|
|
14887
|
+
warning,
|
|
14888
|
+
apply: () => atomicReplaceFile(path, next, existingMode(path, 420))
|
|
14889
|
+
};
|
|
14890
|
+
}
|
|
14891
|
+
if (existing === codexHooksTables(tables)) return { item, path: rel, status: "ok", detail: "PreToolUse + Stop hooks already wired" };
|
|
14892
|
+
if (withoutRuntimeGatePins(existing) === withoutRuntimeGatePins(codexHooksTables(tables))) {
|
|
14893
|
+
const pins = [...new Set(runtimeGatePins(existing))];
|
|
14894
|
+
const next = current.replace(/\btamperward@\S+ (hook|sweep) codex\b/g, `tamperward@${TW_VERSION} $1 codex`);
|
|
14895
|
+
return {
|
|
14896
|
+
item,
|
|
14897
|
+
path: rel,
|
|
14898
|
+
status: "update",
|
|
14899
|
+
detail: `re-pin the hook commands to tamperward@${TW_VERSION} (was ${pins.join(", ")})`,
|
|
14900
|
+
warning,
|
|
14901
|
+
apply: () => atomicReplaceFile(path, next, existingMode(path, 420))
|
|
14902
|
+
};
|
|
14903
|
+
}
|
|
14904
|
+
return {
|
|
14905
|
+
item,
|
|
14906
|
+
path: rel,
|
|
14907
|
+
status: "error",
|
|
14908
|
+
detail: "the [hooks] tables are not the shape init writes \u2014 remove them or add the tamperward entries by hand, then re-run init (refusing to rewrite a hand-written hooks block)"
|
|
14909
|
+
};
|
|
14910
|
+
}
|
|
14911
|
+
function planCopilotHooks(cwd) {
|
|
14912
|
+
const rel = ".github/hooks/tamperward.json";
|
|
14913
|
+
const path = join16(cwd, rel);
|
|
14914
|
+
const item = "copilot-hooks";
|
|
14915
|
+
const refused = refuseNonRegular(path);
|
|
14916
|
+
if (refused) return { item, path: rel, status: "error", detail: refused };
|
|
14917
|
+
const warning = RUNTIME_WARNING("Copilot CLI", "copilot");
|
|
14918
|
+
const document = copilotWiringDocument();
|
|
14919
|
+
const content = JSON.stringify({ $comment: COPILOT_WIRING_NOTE, ...document }, null, 2) + "\n";
|
|
14920
|
+
const write = () => {
|
|
14921
|
+
mkdirSync7(dirname5(path), { recursive: true });
|
|
14922
|
+
atomicReplaceFile(path, content, existingMode(path, 420));
|
|
14923
|
+
};
|
|
14924
|
+
if (!existsSync8(path)) {
|
|
14925
|
+
return { item, path: rel, status: "create", detail: "wire the preToolUse deny and agentStop sweep hooks (experimental)", warning, apply: write };
|
|
14926
|
+
}
|
|
14927
|
+
let parsed;
|
|
14928
|
+
try {
|
|
14929
|
+
parsed = JSON.parse(readFileSync9(path, "utf8"));
|
|
14930
|
+
} catch {
|
|
14931
|
+
return { item, path: rel, status: "error", detail: "exists but is not valid JSON \u2014 fix it, then re-run init (refusing to overwrite)" };
|
|
14932
|
+
}
|
|
14933
|
+
const hooks = isRecord(parsed) ? JSON.stringify(parsed.hooks ?? null) : "null";
|
|
14934
|
+
const wanted = JSON.stringify(document.hooks);
|
|
14935
|
+
if (hooks === wanted) return { item, path: rel, status: "ok", detail: "preToolUse + agentStop hooks already wired" };
|
|
14936
|
+
if (withoutRuntimeGatePins(hooks) === withoutRuntimeGatePins(wanted)) {
|
|
14937
|
+
const pins = [...new Set(runtimeGatePins(hooks))];
|
|
14938
|
+
return { item, path: rel, status: "update", detail: `re-pin the hook commands to tamperward@${TW_VERSION} (was ${pins.join(", ")})`, warning, apply: write };
|
|
14939
|
+
}
|
|
14940
|
+
return {
|
|
14941
|
+
item,
|
|
14942
|
+
path: rel,
|
|
14943
|
+
status: "error",
|
|
14944
|
+
detail: "the hooks are not the shape init writes \u2014 remove the file or restore the tamperward entries by hand, then re-run init (refusing to rewrite a hand-written hooks file)"
|
|
14945
|
+
};
|
|
14946
|
+
}
|
|
14601
14947
|
function planInit(cwd, opts = {}) {
|
|
14602
14948
|
requireShippedVersion();
|
|
14603
14949
|
const gitignore = planGitignore(cwd);
|
|
@@ -14605,11 +14951,24 @@ function planInit(cwd, opts = {}) {
|
|
|
14605
14951
|
planned("policy", POLICY_FILE, () => planPolicy(cwd)),
|
|
14606
14952
|
...gitignore ? [gitignore] : [],
|
|
14607
14953
|
planned("agent", ".claude/settings.json", () => planClaudeHooks(cwd)),
|
|
14954
|
+
...runtimeWiringPlans(cwd),
|
|
14608
14955
|
planned("pre-commit", "(hooks)", () => planPreCommit(cwd)),
|
|
14609
14956
|
planned("ci", ".github/workflows/tamperward.yml", () => planWorkflow(cwd, opts.forceWorkflow ?? false)),
|
|
14610
14957
|
planned("codeowners", ".github/CODEOWNERS", () => planCodeowners(cwd))
|
|
14611
14958
|
];
|
|
14612
14959
|
}
|
|
14960
|
+
function runtimeWiringPlans(cwd) {
|
|
14961
|
+
let detected;
|
|
14962
|
+
try {
|
|
14963
|
+
detected = new Set(detectRuntimes(cwd).map((r) => r.id));
|
|
14964
|
+
} catch {
|
|
14965
|
+
detected = /* @__PURE__ */ new Set();
|
|
14966
|
+
}
|
|
14967
|
+
return [
|
|
14968
|
+
...detected.has("codex") ? [planned("codex-hooks", ".codex/config.toml", () => planCodexHooks(cwd))] : [],
|
|
14969
|
+
...detected.has("copilot") ? [planned("copilot-hooks", ".github/hooks/tamperward.json", () => planCopilotHooks(cwd))] : []
|
|
14970
|
+
];
|
|
14971
|
+
}
|
|
14613
14972
|
function runInit(opts) {
|
|
14614
14973
|
const requested = resolve6(opts.cwd ?? process.cwd());
|
|
14615
14974
|
const cwd = repoRoot(requested);
|
|
@@ -14664,7 +15023,7 @@ tamperward init: ${changed} change(s) applied. Commit them so the gate travels w
|
|
|
14664
15023
|
);
|
|
14665
15024
|
return 0;
|
|
14666
15025
|
}
|
|
14667
|
-
var errText3, isPlainObject, HOOK_RE, SWEEP_RE, PRECOMMIT_RE, LEGACY_OURS, PRE_GLOBAL_OURS, CODEOWNERS_MARK, CODEOWNERS_PATHS, POLICY_CONTENT, WORKFLOW_CONTENT, WORKFLOW_MARK, WORKFLOW_MARK_RE, MANAGED_HOOKS;
|
|
15026
|
+
var errText3, isPlainObject, HOOK_RE, SWEEP_RE, PRECOMMIT_RE, LEGACY_OURS, PRE_GLOBAL_OURS, CODEOWNERS_MARK, CODEOWNERS_PATHS, POLICY_CONTENT, WORKFLOW_CONTENT, WORKFLOW_MARK, WORKFLOW_MARK_RE, MANAGED_HOOKS, RUNTIME_WARNING;
|
|
14668
15027
|
var init_init = __esm({
|
|
14669
15028
|
"src/cli/init.ts"() {
|
|
14670
15029
|
"use strict";
|
|
@@ -14672,6 +15031,8 @@ var init_init = __esm({
|
|
|
14672
15031
|
init_policy_load();
|
|
14673
15032
|
init_verifier_limits();
|
|
14674
15033
|
init_wiring();
|
|
15034
|
+
init_hook_wiring();
|
|
15035
|
+
init_runtimes();
|
|
14675
15036
|
init_narrow();
|
|
14676
15037
|
init_hook_tampering();
|
|
14677
15038
|
init_repo_context();
|
|
@@ -14857,6 +15218,7 @@ jobs:
|
|
|
14857
15218
|
always_run: true`
|
|
14858
15219
|
}
|
|
14859
15220
|
];
|
|
15221
|
+
RUNTIME_WARNING = (label, id) => `EXPERIMENTAL: the ${label} hook wiring was written because this repository shows ${label} markers, but the ${label} adapter has not passed a pinned live qualification. \`tamperward runtime verify --runtime ${id}\` grades what is proven; until it reports in-loop steering, the agent-neutral layers (pre-commit + CI) are the live protection.`;
|
|
14860
15222
|
}
|
|
14861
15223
|
});
|
|
14862
15224
|
|
|
@@ -14897,20 +15259,20 @@ var init_fingerprint = __esm({
|
|
|
14897
15259
|
// src/dependency-env.ts
|
|
14898
15260
|
import { createHash as createHash10 } from "node:crypto";
|
|
14899
15261
|
import {
|
|
14900
|
-
existsSync as
|
|
15262
|
+
existsSync as existsSync9,
|
|
14901
15263
|
lstatSync as lstatSync9,
|
|
14902
|
-
readdirSync as
|
|
14903
|
-
readFileSync as
|
|
15264
|
+
readdirSync as readdirSync4,
|
|
15265
|
+
readFileSync as readFileSync10,
|
|
14904
15266
|
readlinkSync as readlinkSync2,
|
|
14905
15267
|
realpathSync as realpathSync6,
|
|
14906
|
-
statSync as
|
|
15268
|
+
statSync as statSync4
|
|
14907
15269
|
} from "node:fs";
|
|
14908
15270
|
import {
|
|
14909
15271
|
basename as basename4,
|
|
14910
15272
|
delimiter,
|
|
14911
15273
|
dirname as dirname6,
|
|
14912
15274
|
isAbsolute as isAbsolute10,
|
|
14913
|
-
join as
|
|
15275
|
+
join as join17,
|
|
14914
15276
|
relative as relative5,
|
|
14915
15277
|
resolve as resolve7,
|
|
14916
15278
|
sep as sep3
|
|
@@ -14945,7 +15307,7 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
14945
15307
|
root.kind + " root moved from " + JSON.stringify(root.realPath) + " to " + JSON.stringify(currentReal)
|
|
14946
15308
|
);
|
|
14947
15309
|
}
|
|
14948
|
-
if (!
|
|
15310
|
+
if (!statSync4(root.path).isDirectory()) {
|
|
14949
15311
|
throw new Unattestable(root.kind + " root " + JSON.stringify(root.path) + " is not a directory");
|
|
14950
15312
|
}
|
|
14951
15313
|
h.update("root\0" + root.kind + "\0" + root.path + "\0" + root.realPath + "\0" + String(rootStat.mode) + "\0");
|
|
@@ -14955,10 +15317,10 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
14955
15317
|
const hashFile = (path, label) => {
|
|
14956
15318
|
let st;
|
|
14957
15319
|
try {
|
|
14958
|
-
st =
|
|
15320
|
+
st = statSync4(path);
|
|
14959
15321
|
if (!st.isFile()) throw new Error("not a regular file");
|
|
14960
15322
|
h.update(label + "\0external-file\0" + path + "\0" + String(st.mode) + "\0");
|
|
14961
|
-
h.update(
|
|
15323
|
+
h.update(readFileSync10(path));
|
|
14962
15324
|
h.update("\0");
|
|
14963
15325
|
} catch (e) {
|
|
14964
15326
|
throw new Unattestable(
|
|
@@ -14972,14 +15334,14 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
14972
15334
|
visited.add(realDir);
|
|
14973
15335
|
let entries;
|
|
14974
15336
|
try {
|
|
14975
|
-
entries =
|
|
15337
|
+
entries = readdirSync4(dir, { withFileTypes: true });
|
|
14976
15338
|
} catch (e) {
|
|
14977
15339
|
throw new Unattestable(
|
|
14978
15340
|
"dependency directory " + JSON.stringify(dir) + " cannot be read (" + (e instanceof Error ? e.message : String(e)) + ")"
|
|
14979
15341
|
);
|
|
14980
15342
|
}
|
|
14981
15343
|
for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
14982
|
-
const path =
|
|
15344
|
+
const path = join17(dir, entry.name);
|
|
14983
15345
|
const rel = namespace ? namespace + "/" + entry.name : entry.name;
|
|
14984
15346
|
let ident;
|
|
14985
15347
|
try {
|
|
@@ -14992,7 +15354,7 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
14992
15354
|
h.update(rel + "\0" + ident.kind + "\0" + String(ident.mode) + "\0");
|
|
14993
15355
|
if (ident.kind === "file") {
|
|
14994
15356
|
try {
|
|
14995
|
-
h.update(
|
|
15357
|
+
h.update(readFileSync10(path));
|
|
14996
15358
|
h.update("\0");
|
|
14997
15359
|
} catch (e) {
|
|
14998
15360
|
throw new Unattestable(
|
|
@@ -15021,7 +15383,7 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
15021
15383
|
} catch {
|
|
15022
15384
|
continue;
|
|
15023
15385
|
}
|
|
15024
|
-
const followed =
|
|
15386
|
+
const followed = statSync4(path);
|
|
15025
15387
|
if (followed.isFile()) {
|
|
15026
15388
|
hashFile(resolved, "@target:" + rel);
|
|
15027
15389
|
continue;
|
|
@@ -15051,7 +15413,7 @@ function snapshot(roots, probes, cwd) {
|
|
|
15051
15413
|
const h = createHash10("sha256");
|
|
15052
15414
|
for (const probe of probes) {
|
|
15053
15415
|
h.update(
|
|
15054
|
-
"probe\0" + probe.kind + "\0" + probe.path + "\0" + (
|
|
15416
|
+
"probe\0" + probe.kind + "\0" + probe.path + "\0" + (existsSync9(probe.path) ? "present" : "absent") + "\0"
|
|
15055
15417
|
);
|
|
15056
15418
|
}
|
|
15057
15419
|
for (const root of roots) fingerprintRoot(root, cwd, h);
|
|
@@ -15069,7 +15431,7 @@ function measuredSnapshot(roots, probes, cwd, metrics) {
|
|
|
15069
15431
|
}
|
|
15070
15432
|
function freezeRoot(kind, path) {
|
|
15071
15433
|
try {
|
|
15072
|
-
if (!
|
|
15434
|
+
if (!statSync4(path).isDirectory()) {
|
|
15073
15435
|
throw new Error("not a directory");
|
|
15074
15436
|
}
|
|
15075
15437
|
return { kind, path: resolve7(path), realPath: realpathSync6(path) };
|
|
@@ -15110,7 +15472,7 @@ function resolveExecutable(token, cwd, env) {
|
|
|
15110
15472
|
if (token.includes("/") || token.includes("\\")) {
|
|
15111
15473
|
const path = isAbsolute10(token) ? token : resolve7(cwd, token);
|
|
15112
15474
|
try {
|
|
15113
|
-
return
|
|
15475
|
+
return existsSync9(path) ? realpathSync6(path) : null;
|
|
15114
15476
|
} catch {
|
|
15115
15477
|
return null;
|
|
15116
15478
|
}
|
|
@@ -15118,7 +15480,7 @@ function resolveExecutable(token, cwd, env) {
|
|
|
15118
15480
|
for (const entry of (env.PATH ?? "").split(delimiter).filter(Boolean)) {
|
|
15119
15481
|
const candidate = resolve7(entry, token);
|
|
15120
15482
|
try {
|
|
15121
|
-
if (
|
|
15483
|
+
if (existsSync9(candidate) && statSync4(candidate).isFile()) return realpathSync6(candidate);
|
|
15122
15484
|
} catch {
|
|
15123
15485
|
}
|
|
15124
15486
|
}
|
|
@@ -15141,7 +15503,7 @@ function inferredVenvFromToken(token, cwd) {
|
|
|
15141
15503
|
const parentName = basename4(parent).toLowerCase();
|
|
15142
15504
|
if (parentName !== "bin" && parentName !== "scripts") return null;
|
|
15143
15505
|
const root = dirname6(parent);
|
|
15144
|
-
return
|
|
15506
|
+
return existsSync9(join17(root, "pyvenv.cfg")) ? root : null;
|
|
15145
15507
|
}
|
|
15146
15508
|
function unsupportedEcosystem(command, token) {
|
|
15147
15509
|
const name = token ? basename4(token).toLowerCase() : "";
|
|
@@ -15157,13 +15519,13 @@ function inferredVenv(executable) {
|
|
|
15157
15519
|
const parentName = basename4(parent).toLowerCase();
|
|
15158
15520
|
if (parentName !== "bin" && parentName !== "scripts") return null;
|
|
15159
15521
|
const root = dirname6(parent);
|
|
15160
|
-
return
|
|
15522
|
+
return existsSync9(join17(root, "pyvenv.cfg")) ? root : null;
|
|
15161
15523
|
}
|
|
15162
15524
|
function venvProblem(root) {
|
|
15163
|
-
const cfg =
|
|
15164
|
-
if (!
|
|
15525
|
+
const cfg = join17(root, "pyvenv.cfg");
|
|
15526
|
+
if (!existsSync9(cfg)) return "VIRTUAL_ENV " + JSON.stringify(root) + " has no pyvenv.cfg";
|
|
15165
15527
|
try {
|
|
15166
|
-
const body =
|
|
15528
|
+
const body = readFileSync10(cfg, "utf8");
|
|
15167
15529
|
if (/^\s*include-system-site-packages\s*=\s*true\s*$/im.test(body)) {
|
|
15168
15530
|
return "Python environment " + JSON.stringify(root) + " enables system site-packages; those external package roots are not bounded";
|
|
15169
15531
|
}
|
|
@@ -15187,8 +15549,8 @@ function discoverDependencyEnvironment(cwdInput, command, env = process.env) {
|
|
|
15187
15549
|
}
|
|
15188
15550
|
};
|
|
15189
15551
|
try {
|
|
15190
|
-
const nodeModules =
|
|
15191
|
-
if (
|
|
15552
|
+
const nodeModules = join17(cwd, "node_modules");
|
|
15553
|
+
if (existsSync9(nodeModules)) addRoot("node_modules", nodeModules);
|
|
15192
15554
|
else probes.push({ kind: "node_modules", path: nodeModules });
|
|
15193
15555
|
const token = firstExecutable(command);
|
|
15194
15556
|
const executable = resolveExecutable(token, cwd, env);
|
|
@@ -15200,8 +15562,8 @@ function discoverDependencyEnvironment(cwdInput, command, env = process.env) {
|
|
|
15200
15562
|
venv = resolve7(env.VIRTUAL_ENV);
|
|
15201
15563
|
} else {
|
|
15202
15564
|
venv = inferredVenv(executable) ?? inferredVenvFromToken(token, cwd);
|
|
15203
|
-
if (!venv && /(?:^|\s)uv\s+run(?:\s|$)/.test(command) &&
|
|
15204
|
-
venv =
|
|
15565
|
+
if (!venv && /(?:^|\s)uv\s+run(?:\s|$)/.test(command) && existsSync9(join17(cwd, ".venv", "pyvenv.cfg"))) {
|
|
15566
|
+
venv = join17(cwd, ".venv");
|
|
15205
15567
|
}
|
|
15206
15568
|
}
|
|
15207
15569
|
if (venv) {
|
|
@@ -15307,7 +15669,7 @@ var init_dependency_env = __esm({
|
|
|
15307
15669
|
import { spawnSync } from "node:child_process";
|
|
15308
15670
|
import { mkdtempSync as mkdtempSync2, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
|
|
15309
15671
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
15310
|
-
import { join as
|
|
15672
|
+
import { join as join18 } from "node:path";
|
|
15311
15673
|
function emptySuiteDiagnostics() {
|
|
15312
15674
|
return {
|
|
15313
15675
|
stdout: { ...EMPTY_STREAM },
|
|
@@ -15387,8 +15749,8 @@ function parseCapturedSupervisorResult(stdoutText) {
|
|
|
15387
15749
|
};
|
|
15388
15750
|
}
|
|
15389
15751
|
function runCapturedProcessSync(executable, args, opts) {
|
|
15390
|
-
const stateDir2 = mkdtempSync2(
|
|
15391
|
-
const configFile =
|
|
15752
|
+
const stateDir2 = mkdtempSync2(join18(tmpdir3(), "tw-suite-capture-"));
|
|
15753
|
+
const configFile = join18(stateDir2, "config.json");
|
|
15392
15754
|
const backstop = opts.backstopMs ?? 3e4;
|
|
15393
15755
|
try {
|
|
15394
15756
|
writeFileSync7(
|
|
@@ -15679,8 +16041,8 @@ if (child) {
|
|
|
15679
16041
|
// src/verifier-backend.ts
|
|
15680
16042
|
import { createHash as createHash11, randomUUID } from "node:crypto";
|
|
15681
16043
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
15682
|
-
import { accessSync as accessSync2, constants as constants4, readFileSync as
|
|
15683
|
-
import { delimiter as delimiter2, dirname as dirname7, join as
|
|
16044
|
+
import { accessSync as accessSync2, constants as constants4, readFileSync as readFileSync11, realpathSync as realpathSync7, statSync as statSync5 } from "node:fs";
|
|
16045
|
+
import { delimiter as delimiter2, dirname as dirname7, join as join19, resolve as resolve8 } from "node:path";
|
|
15684
16046
|
function identity() {
|
|
15685
16047
|
const uid = typeof process.getuid === "function" ? process.getuid() : null;
|
|
15686
16048
|
const groups = typeof process.getgroups === "function" ? process.getgroups() : [];
|
|
@@ -15688,7 +16050,7 @@ function identity() {
|
|
|
15688
16050
|
}
|
|
15689
16051
|
function writableByCaller(path) {
|
|
15690
16052
|
try {
|
|
15691
|
-
const st =
|
|
16053
|
+
const st = statSync5(path);
|
|
15692
16054
|
const { uid, groups } = identity();
|
|
15693
16055
|
if (uid === 0) return true;
|
|
15694
16056
|
if ((st.mode & 2) !== 0) return true;
|
|
@@ -15714,7 +16076,7 @@ function replaceableByCaller(path) {
|
|
|
15714
16076
|
function findExecutable(name) {
|
|
15715
16077
|
for (const raw of (process.env.PATH ?? "").split(delimiter2)) {
|
|
15716
16078
|
if (!raw) continue;
|
|
15717
|
-
const candidate =
|
|
16079
|
+
const candidate = join19(raw, name);
|
|
15718
16080
|
try {
|
|
15719
16081
|
accessSync2(candidate, constants4.X_OK);
|
|
15720
16082
|
return realpathSync7(candidate);
|
|
@@ -15724,7 +16086,7 @@ function findExecutable(name) {
|
|
|
15724
16086
|
return null;
|
|
15725
16087
|
}
|
|
15726
16088
|
function fileSha256(path) {
|
|
15727
|
-
return createHash11("sha256").update(
|
|
16089
|
+
return createHash11("sha256").update(readFileSync11(path)).digest("hex");
|
|
15728
16090
|
}
|
|
15729
16091
|
function engineClientEnv() {
|
|
15730
16092
|
return {
|
|
@@ -16158,8 +16520,8 @@ var init_verifier_backend = __esm({
|
|
|
16158
16520
|
// src/verification-state.ts
|
|
16159
16521
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
16160
16522
|
import { createHash as createHash12 } from "node:crypto";
|
|
16161
|
-
import { mkdirSync as mkdirSync8, readFileSync as
|
|
16162
|
-
import { dirname as dirname8, join as
|
|
16523
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync12, rmSync as rmSync7 } from "node:fs";
|
|
16524
|
+
import { dirname as dirname8, join as join20 } from "node:path";
|
|
16163
16525
|
function git2(args, cwd) {
|
|
16164
16526
|
return execFileSync8("git", args, {
|
|
16165
16527
|
cwd,
|
|
@@ -16209,19 +16571,19 @@ function surfacePaths(base, cwd, policy) {
|
|
|
16209
16571
|
function interventionWiring(cwd) {
|
|
16210
16572
|
const dir = claudeConfigDir();
|
|
16211
16573
|
const sources = [
|
|
16212
|
-
["repo",
|
|
16213
|
-
["repo.local",
|
|
16214
|
-
["user",
|
|
16215
|
-
["user.local",
|
|
16574
|
+
["repo", join20(cwd, ".claude", "settings.json")],
|
|
16575
|
+
["repo.local", join20(cwd, ".claude", "settings.local.json")],
|
|
16576
|
+
["user", join20(dir, "settings.json")],
|
|
16577
|
+
["user.local", join20(dir, "settings.local.json")]
|
|
16216
16578
|
];
|
|
16217
16579
|
return sources.map(([source, path]) => {
|
|
16218
16580
|
let parsed;
|
|
16219
16581
|
try {
|
|
16220
|
-
parsed = JSON.parse(
|
|
16582
|
+
parsed = JSON.parse(readFileSync12(path, "utf8"));
|
|
16221
16583
|
} catch {
|
|
16222
16584
|
return { source, present: false };
|
|
16223
16585
|
}
|
|
16224
|
-
if (!
|
|
16586
|
+
if (!isRecord5(parsed)) return { source, present: true, shape: "non-object" };
|
|
16225
16587
|
return {
|
|
16226
16588
|
source,
|
|
16227
16589
|
present: true,
|
|
@@ -16279,11 +16641,11 @@ function stateDir(cwd) {
|
|
|
16279
16641
|
}
|
|
16280
16642
|
function verificationRecordPath(cwd) {
|
|
16281
16643
|
const dir = stateDir(cwd);
|
|
16282
|
-
return dir ?
|
|
16644
|
+
return dir ? join20(dir, "verification-state.json") : null;
|
|
16283
16645
|
}
|
|
16284
16646
|
function verifyingMarkerPath(cwd) {
|
|
16285
16647
|
const dir = stateDir(cwd);
|
|
16286
|
-
return dir ?
|
|
16648
|
+
return dir ? join20(dir, "verifying.json") : null;
|
|
16287
16649
|
}
|
|
16288
16650
|
function recordVerification(cwd, inputs, treeFingerprintValue) {
|
|
16289
16651
|
const path = verificationRecordPath(cwd);
|
|
@@ -16330,24 +16692,24 @@ function invalidateVerificationRecordIfCurrent(cwd) {
|
|
|
16330
16692
|
return false;
|
|
16331
16693
|
}
|
|
16332
16694
|
}
|
|
16333
|
-
function
|
|
16695
|
+
function isRecord5(v) {
|
|
16334
16696
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
16335
16697
|
}
|
|
16336
16698
|
function isBinding(v) {
|
|
16337
|
-
if (!
|
|
16699
|
+
if (!isRecord5(v)) return false;
|
|
16338
16700
|
return BINDING_INPUTS.every((k) => {
|
|
16339
16701
|
const val = v[k];
|
|
16340
16702
|
return typeof val === "string" && val.length > 0;
|
|
16341
16703
|
});
|
|
16342
16704
|
}
|
|
16343
16705
|
function parseVerificationRecord(value) {
|
|
16344
|
-
if (!
|
|
16706
|
+
if (!isRecord5(value)) return null;
|
|
16345
16707
|
if (value.schema_version !== VERIFICATION_STATE_SCHEMA_VERSION) return null;
|
|
16346
16708
|
if (value.verdict !== "VERIFIED") return null;
|
|
16347
16709
|
if (typeof value.verified_at !== "string") return null;
|
|
16348
16710
|
if (typeof value.tw_version !== "string") return null;
|
|
16349
16711
|
const inputs = value.inputs;
|
|
16350
|
-
if (!
|
|
16712
|
+
if (!isRecord5(inputs)) return null;
|
|
16351
16713
|
if (typeof inputs.base_ref !== "string" || inputs.base_ref.length === 0) return null;
|
|
16352
16714
|
if (typeof inputs.explicit_base !== "boolean") return null;
|
|
16353
16715
|
if (inputs.command_source !== "policy" && inputs.command_source !== "flag") return null;
|
|
@@ -16389,7 +16751,7 @@ function pidAlive3(pid) {
|
|
|
16389
16751
|
process.kill(pid, 0);
|
|
16390
16752
|
return true;
|
|
16391
16753
|
} catch (e) {
|
|
16392
|
-
return
|
|
16754
|
+
return isRecord5(e) && e.code === "EPERM";
|
|
16393
16755
|
}
|
|
16394
16756
|
}
|
|
16395
16757
|
function beginVerifying(cwd) {
|
|
@@ -16427,7 +16789,7 @@ function readVerifyingMarker(cwd) {
|
|
|
16427
16789
|
} catch {
|
|
16428
16790
|
return null;
|
|
16429
16791
|
}
|
|
16430
|
-
if (!
|
|
16792
|
+
if (!isRecord5(parsed)) return null;
|
|
16431
16793
|
if (parsed.schema_version !== VERIFICATION_STATE_SCHEMA_VERSION) return null;
|
|
16432
16794
|
if (typeof parsed.pid !== "number" || !Number.isInteger(parsed.pid) || parsed.pid <= 0) return null;
|
|
16433
16795
|
if (typeof parsed.started_at !== "string") return null;
|
|
@@ -16572,8 +16934,8 @@ var init_verification_state = __esm({
|
|
|
16572
16934
|
// src/verification-receipt.ts
|
|
16573
16935
|
import { createHash as createHash13 } from "node:crypto";
|
|
16574
16936
|
import { mkdirSync as mkdirSync9, rmSync as rmSync8 } from "node:fs";
|
|
16575
|
-
import { dirname as dirname9, join as
|
|
16576
|
-
function
|
|
16937
|
+
import { dirname as dirname9, join as join21 } from "node:path";
|
|
16938
|
+
function isRecord6(v) {
|
|
16577
16939
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
16578
16940
|
}
|
|
16579
16941
|
function canonicalJson2(value) {
|
|
@@ -16611,7 +16973,7 @@ function receiptFromRecord(record) {
|
|
|
16611
16973
|
}
|
|
16612
16974
|
function receiptPath(cwd) {
|
|
16613
16975
|
const ctx = repoContext(cwd);
|
|
16614
|
-
return ctx ?
|
|
16976
|
+
return ctx ? join21(stateDirectory(ctx.gitDir), "verification-receipt.json") : null;
|
|
16615
16977
|
}
|
|
16616
16978
|
function storeReceipt(cwd, receipt) {
|
|
16617
16979
|
try {
|
|
@@ -16636,10 +16998,10 @@ function isOneOf(list3, v) {
|
|
|
16636
16998
|
return typeof v === "string" && list3.includes(v);
|
|
16637
16999
|
}
|
|
16638
17000
|
function isStages(v) {
|
|
16639
|
-
return
|
|
17001
|
+
return isRecord6(v) && isOneOf(RECEIPT_STAGE_RESULTS, v.candidate) && isOneOf(RECEIPT_STAGE_RESULTS, v.pristine) && isOneOf(RECEIPT_INTEGRITY_RESULTS, v.integrity);
|
|
16640
17002
|
}
|
|
16641
17003
|
function isBinding2(v) {
|
|
16642
|
-
if (!
|
|
17004
|
+
if (!isRecord6(v)) return false;
|
|
16643
17005
|
return BINDING_INPUTS.every((k) => {
|
|
16644
17006
|
const val = v[k];
|
|
16645
17007
|
return typeof val === "string" && val.length > 0;
|
|
@@ -16649,7 +17011,7 @@ function classifyReceipt(value) {
|
|
|
16649
17011
|
if (value === null || value === void 0) {
|
|
16650
17012
|
return { disposition: "ABSENT", detail: "no local verification receipt was provided" };
|
|
16651
17013
|
}
|
|
16652
|
-
if (!
|
|
17014
|
+
if (!isRecord6(value)) {
|
|
16653
17015
|
return { disposition: "UNKNOWN_SCHEMA", detail: "receipt is not a JSON object" };
|
|
16654
17016
|
}
|
|
16655
17017
|
if (value.schema_version !== RECEIPT_SCHEMA_VERSION) {
|
|
@@ -16803,9 +17165,9 @@ var init_verification_receipt = __esm({
|
|
|
16803
17165
|
// src/cli/verify.ts
|
|
16804
17166
|
import { execFileSync as execFileSync9, spawnSync as spawnSync3 } from "node:child_process";
|
|
16805
17167
|
import { createHash as createHash14 } from "node:crypto";
|
|
16806
|
-
import { chmodSync as chmodSync2, cpSync, lstatSync as lstatSync10, mkdirSync as mkdirSync10, mkdtempSync as mkdtempSync3, readFileSync as
|
|
17168
|
+
import { chmodSync as chmodSync2, cpSync, lstatSync as lstatSync10, mkdirSync as mkdirSync10, mkdtempSync as mkdtempSync3, readFileSync as readFileSync13, readlinkSync as readlinkSync3, rmSync as rmSync9, symlinkSync, writeFileSync as writeFileSync8, existsSync as existsSync10 } from "node:fs";
|
|
16807
17169
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
16808
|
-
import { dirname as dirname10, isAbsolute as isAbsolute11, join as
|
|
17170
|
+
import { dirname as dirname10, isAbsolute as isAbsolute11, join as join22, relative as relative6, resolve as resolve9, sep as sep4 } from "node:path";
|
|
16809
17171
|
function oracleAssuranceReport() {
|
|
16810
17172
|
return {
|
|
16811
17173
|
level: "suite-exit-only",
|
|
@@ -16894,7 +17256,7 @@ function validateSymlinkGraph(target, out3, root, dependencyRoot, label) {
|
|
|
16894
17256
|
continue;
|
|
16895
17257
|
}
|
|
16896
17258
|
const base = domain === "deps" && dependencyRoot ? dependencyRoot : root;
|
|
16897
|
-
const current =
|
|
17259
|
+
const current = join22(base, ...stack);
|
|
16898
17260
|
let st;
|
|
16899
17261
|
try {
|
|
16900
17262
|
st = lstatSync10(current);
|
|
@@ -16936,7 +17298,7 @@ function rejectLinkedParent(cwd, rel) {
|
|
|
16936
17298
|
const parts = rel.split("/").slice(0, -1);
|
|
16937
17299
|
let current = cwd;
|
|
16938
17300
|
for (const part of parts) {
|
|
16939
|
-
current =
|
|
17301
|
+
current = join22(current, part);
|
|
16940
17302
|
let st;
|
|
16941
17303
|
try {
|
|
16942
17304
|
st = lstatSync10(current);
|
|
@@ -16961,7 +17323,7 @@ function materialize(cwd, dest, dependencyRoot) {
|
|
|
16961
17323
|
}
|
|
16962
17324
|
const links = [];
|
|
16963
17325
|
for (const rel of listed) {
|
|
16964
|
-
const src =
|
|
17326
|
+
const src = join22(cwd, rel);
|
|
16965
17327
|
rejectLinkedParent(cwd, rel);
|
|
16966
17328
|
let st;
|
|
16967
17329
|
try {
|
|
@@ -16969,7 +17331,7 @@ function materialize(cwd, dest, dependencyRoot) {
|
|
|
16969
17331
|
} catch {
|
|
16970
17332
|
continue;
|
|
16971
17333
|
}
|
|
16972
|
-
const out3 =
|
|
17334
|
+
const out3 = join22(dest, rel);
|
|
16973
17335
|
mkdirSync10(dirname10(out3), { recursive: true });
|
|
16974
17336
|
dropSymlink(out3);
|
|
16975
17337
|
if (st.isSymbolicLink()) {
|
|
@@ -16989,17 +17351,17 @@ function materialize(cwd, dest, dependencyRoot) {
|
|
|
16989
17351
|
for (const link of links) {
|
|
16990
17352
|
validateSymlinkGraph(link.target, link.out, dest, dependencyRoot, link.rel);
|
|
16991
17353
|
}
|
|
16992
|
-
if (dependencyRoot) symlinkSync(dependencyRoot,
|
|
17354
|
+
if (dependencyRoot) symlinkSync(dependencyRoot, join22(dest, "node_modules"), "dir");
|
|
16993
17355
|
}
|
|
16994
17356
|
function workingTreePaths(cwd) {
|
|
16995
17357
|
return git3(["ls-files", "-z", "--cached", "--others", "--exclude-standard"], cwd).split("\0").filter(Boolean);
|
|
16996
17358
|
}
|
|
16997
17359
|
function probeFilesystemCaseSensitivity(dir) {
|
|
16998
|
-
const upper =
|
|
16999
|
-
const lower =
|
|
17360
|
+
const upper = join22(dir, ".tw-case-probe-A");
|
|
17361
|
+
const lower = join22(dir, ".tw-case-probe-a");
|
|
17000
17362
|
try {
|
|
17001
17363
|
writeFileSync8(upper, "", { flag: "wx" });
|
|
17002
|
-
return !
|
|
17364
|
+
return !existsSync10(lower);
|
|
17003
17365
|
} finally {
|
|
17004
17366
|
rmSync9(upper, { force: true });
|
|
17005
17367
|
}
|
|
@@ -17059,11 +17421,11 @@ function overlayDigest(dest, paths) {
|
|
|
17059
17421
|
for (const rel of [...paths].sort()) {
|
|
17060
17422
|
h.update(rel);
|
|
17061
17423
|
h.update("\0");
|
|
17062
|
-
const p =
|
|
17424
|
+
const p = join22(dest, rel);
|
|
17063
17425
|
try {
|
|
17064
17426
|
const st = lstatSync10(p);
|
|
17065
17427
|
h.update(String(st.mode));
|
|
17066
|
-
h.update(st.isSymbolicLink() ? readlinkSync3(p) :
|
|
17428
|
+
h.update(st.isSymbolicLink() ? readlinkSync3(p) : readFileSync13(p));
|
|
17067
17429
|
} catch {
|
|
17068
17430
|
h.update("<gone>");
|
|
17069
17431
|
}
|
|
@@ -17093,7 +17455,7 @@ function overlayPristine(cwd, base, dest, policy, cmd, dependencyRoot, caseSensi
|
|
|
17093
17455
|
throw new Error(`the base's ${e.path} is a submodule; the pristine copy cannot reproduce it`);
|
|
17094
17456
|
}
|
|
17095
17457
|
const content = execFileSync9("git", ["show", `${base}:${e.path}`], { cwd, maxBuffer: 1 << 28, env: trustedGitEnv() });
|
|
17096
|
-
const out3 =
|
|
17458
|
+
const out3 = join22(dest, e.path);
|
|
17097
17459
|
mkdirSync10(dirname10(out3), { recursive: true });
|
|
17098
17460
|
rmSync9(out3, { force: true });
|
|
17099
17461
|
if (e.mode === "120000") {
|
|
@@ -17110,7 +17472,7 @@ function overlayPristine(cwd, base, dest, policy, cmd, dependencyRoot, caseSensi
|
|
|
17110
17472
|
const inCopy = workingTreePaths(cwd);
|
|
17111
17473
|
for (const rel of inCopy) {
|
|
17112
17474
|
if (!isOverlay(rel) || baseProtected.has(fold(rel))) continue;
|
|
17113
|
-
rmSync9(
|
|
17475
|
+
rmSync9(join22(dest, rel), { force: true });
|
|
17114
17476
|
removed++;
|
|
17115
17477
|
}
|
|
17116
17478
|
for (const link of restoredLinks) {
|
|
@@ -17124,8 +17486,8 @@ function suiteEnv(scratch) {
|
|
|
17124
17486
|
if (DROPPED_ENV.has(k) || PINNED_NPM.test(k)) continue;
|
|
17125
17487
|
env[k] = v;
|
|
17126
17488
|
}
|
|
17127
|
-
const userRc =
|
|
17128
|
-
const globalRc =
|
|
17489
|
+
const userRc = join22(scratch, "npmrc-user");
|
|
17490
|
+
const globalRc = join22(scratch, "npmrc-global");
|
|
17129
17491
|
for (const f of [userRc, globalRc]) writeFileSync8(f, "", { mode: 292 });
|
|
17130
17492
|
env.npm_config_userconfig = userRc;
|
|
17131
17493
|
env.npm_config_globalconfig = globalRc;
|
|
@@ -17157,7 +17519,7 @@ function runLocalSuite(dir, cmd, budgetSecs) {
|
|
|
17157
17519
|
reason: `checkpointed-local verifier is unsupported on ${process.platform}; no trusted local shell contract is defined`
|
|
17158
17520
|
};
|
|
17159
17521
|
}
|
|
17160
|
-
const scratch = mkdtempSync3(
|
|
17522
|
+
const scratch = mkdtempSync3(join22(tmpdir4(), "tw-verify-run-"));
|
|
17161
17523
|
try {
|
|
17162
17524
|
const r = runCapturedProcessSync(shell.executable, shell.args, {
|
|
17163
17525
|
cwd: dir,
|
|
@@ -17403,8 +17765,8 @@ function runVerifyImpl(opts) {
|
|
|
17403
17765
|
} catch {
|
|
17404
17766
|
return cannotVerify("BASE_UNRESOLVABLE", `cannot resolve base rev "${opts.base ?? "HEAD"}"`);
|
|
17405
17767
|
}
|
|
17406
|
-
const visRoot = mkdtempSync3(
|
|
17407
|
-
const visDir =
|
|
17768
|
+
const visRoot = mkdtempSync3(join22(tmpdir4(), "tw-verify-vis-"));
|
|
17769
|
+
const visDir = join22(visRoot, "t");
|
|
17408
17770
|
const cleanup = (dirs) => {
|
|
17409
17771
|
if (opts.keep) return;
|
|
17410
17772
|
for (const d of dirs) rmSync9(d, { recursive: true, force: true });
|
|
@@ -17413,7 +17775,7 @@ function runVerifyImpl(opts) {
|
|
|
17413
17775
|
if (!filesystemCaseSensitive) {
|
|
17414
17776
|
const present = (rel) => {
|
|
17415
17777
|
try {
|
|
17416
|
-
lstatSync10(
|
|
17778
|
+
lstatSync10(join22(cwd, rel));
|
|
17417
17779
|
return true;
|
|
17418
17780
|
} catch {
|
|
17419
17781
|
return false;
|
|
@@ -17546,8 +17908,8 @@ function runVerifyImpl(opts) {
|
|
|
17546
17908
|
{ stage: "visible", dependency_environment: dependencyReport(), verifier_backend: backendReport(), oracle_assurance: oracleAssuranceReport() }
|
|
17547
17909
|
);
|
|
17548
17910
|
}
|
|
17549
|
-
const priRoot = mkdtempSync3(
|
|
17550
|
-
const priDir =
|
|
17911
|
+
const priRoot = mkdtempSync3(join22(tmpdir4(), "tw-verify-pri-"));
|
|
17912
|
+
const priDir = join22(priRoot, "t");
|
|
17551
17913
|
let restored = [];
|
|
17552
17914
|
let removedAdded = 0;
|
|
17553
17915
|
try {
|
|
@@ -17877,9 +18239,9 @@ var init_verify = __esm({
|
|
|
17877
18239
|
// src/cli/run.ts
|
|
17878
18240
|
import { execFileSync as execFileSync10, spawn, spawnSync as spawnSync4 } from "node:child_process";
|
|
17879
18241
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
17880
|
-
import { accessSync as accessSync3, constants as fsConstants, mkdtempSync as mkdtempSync4, readdirSync as
|
|
18242
|
+
import { accessSync as accessSync3, constants as fsConstants, mkdtempSync as mkdtempSync4, readdirSync as readdirSync5, readFileSync as readFileSync14, readlinkSync as readlinkSync4, realpathSync as realpathSync8, rmSync as rmSync10, statSync as statSync6, writeFileSync as writeFileSync9 } from "node:fs";
|
|
17881
18243
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
17882
|
-
import { dirname as dirname11, join as
|
|
18244
|
+
import { dirname as dirname11, join as join23, resolve as resolve10 } from "node:path";
|
|
17883
18245
|
function authoritativeRunLifecyclePlatform(platform = process.platform) {
|
|
17884
18246
|
return platform === "linux";
|
|
17885
18247
|
}
|
|
@@ -17933,7 +18295,7 @@ function trustedLinuxPython(candidates = DEFAULT_TRUSTED_PYTHON_CANDIDATES) {
|
|
|
17933
18295
|
for (const candidate of candidates) {
|
|
17934
18296
|
try {
|
|
17935
18297
|
const real = realpathSync8(candidate);
|
|
17936
|
-
const st =
|
|
18298
|
+
const st = statSync6(real);
|
|
17937
18299
|
if (!st.isFile() || writableByCaller2(real)) continue;
|
|
17938
18300
|
const probe = spawnSync4(
|
|
17939
18301
|
real,
|
|
@@ -17955,9 +18317,9 @@ function trustedLinuxPython(candidates = DEFAULT_TRUSTED_PYTHON_CANDIDATES) {
|
|
|
17955
18317
|
};
|
|
17956
18318
|
}
|
|
17957
18319
|
function runAgentSupervised(argv, cwd, budgetSecs, linuxPythonCandidates, lifecycleTestMode, machineMode = false) {
|
|
17958
|
-
const stateDir2 = mkdtempSync4(
|
|
17959
|
-
const resultFile =
|
|
17960
|
-
const agentEnvFile =
|
|
18320
|
+
const stateDir2 = mkdtempSync4(join23(tmpdir5(), "tw-agent-supervisor-"));
|
|
18321
|
+
const resultFile = join23(stateDir2, "result.json");
|
|
18322
|
+
const agentEnvFile = join23(stateDir2, "agent-env.json");
|
|
17961
18323
|
try {
|
|
17962
18324
|
const linux = process.platform === "linux";
|
|
17963
18325
|
let executable;
|
|
@@ -18031,7 +18393,7 @@ function runAgentSupervised(argv, cwd, budgetSecs, linuxPythonCandidates, lifecy
|
|
|
18031
18393
|
}
|
|
18032
18394
|
let state = {};
|
|
18033
18395
|
try {
|
|
18034
|
-
state = JSON.parse(
|
|
18396
|
+
state = JSON.parse(readFileSync14(resultFile, "utf8"));
|
|
18035
18397
|
} catch {
|
|
18036
18398
|
return {
|
|
18037
18399
|
exit: 1,
|
|
@@ -18075,7 +18437,7 @@ function observerExited(pid) {
|
|
|
18075
18437
|
if (!pidAlive4(pid)) return true;
|
|
18076
18438
|
if (process.platform !== "linux") return false;
|
|
18077
18439
|
try {
|
|
18078
|
-
const stat =
|
|
18440
|
+
const stat = readFileSync14(`/proc/${pid}/stat`, "utf8");
|
|
18079
18441
|
const state = stat.slice(stat.lastIndexOf(")") + 2).split(" ")[0];
|
|
18080
18442
|
return state === "Z" || state === "X";
|
|
18081
18443
|
} catch (e) {
|
|
@@ -18083,8 +18445,8 @@ function observerExited(pid) {
|
|
|
18083
18445
|
}
|
|
18084
18446
|
}
|
|
18085
18447
|
function supervisedObserverLog(cwd) {
|
|
18086
|
-
const gd = gitDir(cwd) ??
|
|
18087
|
-
return
|
|
18448
|
+
const gd = gitDir(cwd) ?? join23(cwd, ".git");
|
|
18449
|
+
return join23(
|
|
18088
18450
|
stateDirectory(gd),
|
|
18089
18451
|
`run-observer-${process.pid}-${Date.now()}-${randomBytes3(4).toString("hex")}.jsonl`
|
|
18090
18452
|
);
|
|
@@ -18196,7 +18558,7 @@ function git4(args, cwd) {
|
|
|
18196
18558
|
}
|
|
18197
18559
|
function nowTicks() {
|
|
18198
18560
|
try {
|
|
18199
|
-
return Math.floor(parseFloat(
|
|
18561
|
+
return Math.floor(parseFloat(readFileSync14("/proc/uptime", "utf8").split(" ")[0]) * 100);
|
|
18200
18562
|
} catch {
|
|
18201
18563
|
return Number.POSITIVE_INFINITY;
|
|
18202
18564
|
}
|
|
@@ -18211,7 +18573,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
|
|
|
18211
18573
|
}
|
|
18212
18574
|
let pids;
|
|
18213
18575
|
try {
|
|
18214
|
-
pids =
|
|
18576
|
+
pids = readdirSync5("/proc").filter((d) => /^\d+$/.test(d));
|
|
18215
18577
|
} catch {
|
|
18216
18578
|
return out3;
|
|
18217
18579
|
}
|
|
@@ -18219,7 +18581,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
|
|
|
18219
18581
|
const n2 = Number(pid);
|
|
18220
18582
|
if (n2 === process.pid) continue;
|
|
18221
18583
|
try {
|
|
18222
|
-
const stat =
|
|
18584
|
+
const stat = readFileSync14(`/proc/${pid}/stat`, "utf8");
|
|
18223
18585
|
const startTicks = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[19]);
|
|
18224
18586
|
if (!Number.isFinite(startTicks) || startTicks < spawnedAfterTicks) continue;
|
|
18225
18587
|
} catch {
|
|
@@ -18227,7 +18589,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
|
|
|
18227
18589
|
}
|
|
18228
18590
|
const procLinks = [`/proc/${pid}/cwd`, `/proc/${pid}/exe`];
|
|
18229
18591
|
try {
|
|
18230
|
-
for (const fd of
|
|
18592
|
+
for (const fd of readdirSync5(`/proc/${pid}/fd`)) procLinks.push(`/proc/${pid}/fd/${fd}`);
|
|
18231
18593
|
} catch {
|
|
18232
18594
|
}
|
|
18233
18595
|
let holds = false;
|
|
@@ -18792,8 +19154,8 @@ raise SystemExit(0)
|
|
|
18792
19154
|
|
|
18793
19155
|
// src/cli/doctor.ts
|
|
18794
19156
|
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
18795
|
-
import { existsSync as
|
|
18796
|
-
import { join as
|
|
19157
|
+
import { existsSync as existsSync11, readFileSync as readFileSync15, readdirSync as readdirSync6 } from "node:fs";
|
|
19158
|
+
import { join as join24, resolve as resolve11 } from "node:path";
|
|
18797
19159
|
function lifecyclePlatformCheck(platform = process.platform, linuxPython = platform === "linux" ? trustedLinuxPython() : null) {
|
|
18798
19160
|
if (platform === "linux") {
|
|
18799
19161
|
if (linuxPython?.path) {
|
|
@@ -18848,10 +19210,10 @@ function wiringState(status, authority) {
|
|
|
18848
19210
|
return authority ? "BROKEN" : "WARN";
|
|
18849
19211
|
}
|
|
18850
19212
|
function pinsInFile(path) {
|
|
18851
|
-
if (!
|
|
19213
|
+
if (!existsSync11(path)) return [];
|
|
18852
19214
|
let src = "";
|
|
18853
19215
|
try {
|
|
18854
|
-
src =
|
|
19216
|
+
src = readFileSync15(path, "utf8");
|
|
18855
19217
|
} catch {
|
|
18856
19218
|
return [];
|
|
18857
19219
|
}
|
|
@@ -18872,13 +19234,13 @@ function workflowPermissionCheck(cwd, workflowRels = [".github/workflows/tamperw
|
|
|
18872
19234
|
const warnings = [];
|
|
18873
19235
|
for (const rel of workflowRels) {
|
|
18874
19236
|
const path = resolve11(cwd, rel);
|
|
18875
|
-
if (!
|
|
19237
|
+
if (!existsSync11(path)) {
|
|
18876
19238
|
broken.push(`${rel} is missing`);
|
|
18877
19239
|
continue;
|
|
18878
19240
|
}
|
|
18879
19241
|
let doc;
|
|
18880
19242
|
try {
|
|
18881
|
-
doc = yaml.parse(
|
|
19243
|
+
doc = yaml.parse(readFileSync15(path, "utf8"));
|
|
18882
19244
|
} catch (e) {
|
|
18883
19245
|
broken.push(`${rel} is not valid YAML (${e instanceof Error ? e.message : String(e)})`);
|
|
18884
19246
|
continue;
|
|
@@ -18991,9 +19353,9 @@ function collectLocalPosture(cwd, policy, authorityWorkflows, explicitWorkflow =
|
|
|
18991
19353
|
addWiring("codeowners", "codeowners", true);
|
|
18992
19354
|
checks.push(workflowPermissionCheck(cwd, authorityWorkflows));
|
|
18993
19355
|
const pins = [
|
|
18994
|
-
...pinsInFile(
|
|
18995
|
-
...pinsInFile(
|
|
18996
|
-
...pinsInFile(
|
|
19356
|
+
...pinsInFile(join24(cwd, ".claude", "settings.json")),
|
|
19357
|
+
...pinsInFile(join24(cwd, ".git", "hooks", "pre-commit")),
|
|
19358
|
+
...pinsInFile(join24(cwd, ".husky", "pre-commit"))
|
|
18997
19359
|
];
|
|
18998
19360
|
const below = pins.filter((pin) => (compareVersions(pin, TW_VERSION) ?? 0) < 0);
|
|
18999
19361
|
const above = pins.filter((pin) => (compareVersions(TW_VERSION, pin) ?? 0) < 0);
|
|
@@ -19277,19 +19639,19 @@ function diagnose(opts = {}) {
|
|
|
19277
19639
|
} else {
|
|
19278
19640
|
const workflowDirRel = ".github/workflows";
|
|
19279
19641
|
const workflowDir = resolve11(cwd, workflowDirRel);
|
|
19280
|
-
if (!
|
|
19642
|
+
if (!existsSync11(workflowDir)) {
|
|
19281
19643
|
return fail("ci-verifier", `${workflowDirRel}: workflow directory does not exist`);
|
|
19282
19644
|
}
|
|
19283
19645
|
let entries;
|
|
19284
19646
|
try {
|
|
19285
|
-
entries =
|
|
19647
|
+
entries = readdirSync6(workflowDir).filter((name) => /\.ya?ml$/i.test(name)).sort();
|
|
19286
19648
|
} catch (e) {
|
|
19287
19649
|
return fail(
|
|
19288
19650
|
"ci-verifier",
|
|
19289
19651
|
`${workflowDirRel}: could not enumerate workflows (${e instanceof Error ? e.message : String(e)})`
|
|
19290
19652
|
);
|
|
19291
19653
|
}
|
|
19292
|
-
for (const name of entries) workflowRels.push(
|
|
19654
|
+
for (const name of entries) workflowRels.push(join24(workflowDirRel, name));
|
|
19293
19655
|
}
|
|
19294
19656
|
const requiredSecs = requiredVerifierAuthoritySeconds(policy.verify.budget);
|
|
19295
19657
|
const requiredMinutes = Math.ceil(requiredSecs / 60);
|
|
@@ -19297,12 +19659,12 @@ function diagnose(opts = {}) {
|
|
|
19297
19659
|
const authorityWorkflows = [];
|
|
19298
19660
|
for (const workflowRel of workflowRels) {
|
|
19299
19661
|
const workflowPath = resolve11(cwd, workflowRel);
|
|
19300
|
-
if (!
|
|
19662
|
+
if (!existsSync11(workflowPath)) {
|
|
19301
19663
|
return fail("ci-verifier", `${workflowRel}: workflow does not exist`);
|
|
19302
19664
|
}
|
|
19303
19665
|
let doc;
|
|
19304
19666
|
try {
|
|
19305
|
-
doc = yaml.parse(
|
|
19667
|
+
doc = yaml.parse(readFileSync15(workflowPath, "utf8"));
|
|
19306
19668
|
} catch (e) {
|
|
19307
19669
|
return fail(
|
|
19308
19670
|
"ci-verifier",
|
|
@@ -19616,16 +19978,16 @@ var init_status2 = __esm({
|
|
|
19616
19978
|
// src/cli/trace-verify.ts
|
|
19617
19979
|
import { execFileSync as execFileSync12, spawnSync as spawnSync5 } from "node:child_process";
|
|
19618
19980
|
import {
|
|
19619
|
-
existsSync as
|
|
19981
|
+
existsSync as existsSync12,
|
|
19620
19982
|
mkdtempSync as mkdtempSync5,
|
|
19621
|
-
readFileSync as
|
|
19622
|
-
readdirSync as
|
|
19983
|
+
readFileSync as readFileSync16,
|
|
19984
|
+
readdirSync as readdirSync7,
|
|
19623
19985
|
realpathSync as realpathSync9,
|
|
19624
19986
|
rmSync as rmSync11,
|
|
19625
19987
|
symlinkSync as symlinkSync2
|
|
19626
19988
|
} from "node:fs";
|
|
19627
19989
|
import { tmpdir as tmpdir6 } from "node:os";
|
|
19628
|
-
import { isAbsolute as isAbsolute12, join as
|
|
19990
|
+
import { isAbsolute as isAbsolute12, join as join25, relative as relative7, resolve as resolve12, sep as sep5 } from "node:path";
|
|
19629
19991
|
function unescapeStraceString(s) {
|
|
19630
19992
|
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, "\\");
|
|
19631
19993
|
}
|
|
@@ -19873,16 +20235,16 @@ function materializeBase(base, cwd, dest) {
|
|
|
19873
20235
|
`tar extraction failed${untar.stderr?.length ? `: ${String(untar.stderr).trim()}` : ""}`
|
|
19874
20236
|
);
|
|
19875
20237
|
}
|
|
19876
|
-
const hostModules =
|
|
19877
|
-
const tracedModules =
|
|
19878
|
-
if (
|
|
20238
|
+
const hostModules = join25(cwd, "node_modules");
|
|
20239
|
+
const tracedModules = join25(dest, "node_modules");
|
|
20240
|
+
if (existsSync12(hostModules) && !existsSync12(tracedModules)) {
|
|
19879
20241
|
symlinkSync2(resolve12(hostModules), tracedModules, "dir");
|
|
19880
20242
|
}
|
|
19881
20243
|
}
|
|
19882
20244
|
function traceFiles(prefix) {
|
|
19883
20245
|
const dir = resolve12(prefix, "..");
|
|
19884
20246
|
const base = prefix.split(sep5).at(-1) ?? prefix;
|
|
19885
|
-
return
|
|
20247
|
+
return readdirSync7(dir).filter((name) => name === base || name.startsWith(base + ".")).map((name) => join25(dir, name)).sort();
|
|
19886
20248
|
}
|
|
19887
20249
|
function rewriteTraceRoot(accesses, actualRoot) {
|
|
19888
20250
|
return accesses.map((item) => {
|
|
@@ -19897,13 +20259,13 @@ function rewriteTraceRoot(accesses, actualRoot) {
|
|
|
19897
20259
|
if (!inside3(actualRoot, real)) return { ...item, path: real };
|
|
19898
20260
|
} catch {
|
|
19899
20261
|
}
|
|
19900
|
-
return { ...item, path:
|
|
20262
|
+
return { ...item, path: join25(TRACE_ROOT, ...rel.split("/")) };
|
|
19901
20263
|
});
|
|
19902
20264
|
}
|
|
19903
20265
|
function straceOnce(root, command, budget) {
|
|
19904
|
-
const traceDir = mkdtempSync5(
|
|
20266
|
+
const traceDir = mkdtempSync5(join25(tmpdir6(), "tw-trace-log-"));
|
|
19905
20267
|
try {
|
|
19906
|
-
const prefix =
|
|
20268
|
+
const prefix = join25(traceDir, "trace");
|
|
19907
20269
|
const traced = spawnSync5(
|
|
19908
20270
|
"strace",
|
|
19909
20271
|
[
|
|
@@ -19932,7 +20294,7 @@ function straceOnce(root, command, budget) {
|
|
|
19932
20294
|
}
|
|
19933
20295
|
);
|
|
19934
20296
|
const logs = traced.error ? [] : traceFiles(prefix);
|
|
19935
|
-
const accesses = logs.flatMap((path) => parseStraceFileAccess(
|
|
20297
|
+
const accesses = logs.flatMap((path) => parseStraceFileAccess(readFileSync16(path, "utf8"), TRACE_ROOT));
|
|
19936
20298
|
const raw = {
|
|
19937
20299
|
spawnError: traced.error ? traced.error.message : null,
|
|
19938
20300
|
status: traced.status,
|
|
@@ -19947,7 +20309,7 @@ function straceOnce(root, command, budget) {
|
|
|
19947
20309
|
}
|
|
19948
20310
|
}
|
|
19949
20311
|
function runOneTrace(cwd, base, command, budget) {
|
|
19950
|
-
const root = mkdtempSync5(
|
|
20312
|
+
const root = mkdtempSync5(join25(tmpdir6(), "tw-trace-base-"));
|
|
19951
20313
|
try {
|
|
19952
20314
|
materializeBase(base, cwd, root);
|
|
19953
20315
|
const { raw, accesses } = straceOnce(root, command, budget);
|
|
@@ -19957,7 +20319,7 @@ function runOneTrace(cwd, base, command, budget) {
|
|
|
19957
20319
|
}
|
|
19958
20320
|
}
|
|
19959
20321
|
function tracerPreflight(budget = 10) {
|
|
19960
|
-
const probeRoot = mkdtempSync5(
|
|
20322
|
+
const probeRoot = mkdtempSync5(join25(tmpdir6(), "tw-trace-probe-"));
|
|
19961
20323
|
try {
|
|
19962
20324
|
const { raw } = straceOnce(probeRoot, "true", Math.max(1, Math.min(budget, 10)));
|
|
19963
20325
|
return classifyTraceRun(raw);
|
|
@@ -20163,163 +20525,6 @@ var init_trace_verify = __esm({
|
|
|
20163
20525
|
}
|
|
20164
20526
|
});
|
|
20165
20527
|
|
|
20166
|
-
// src/immutable.ts
|
|
20167
|
-
function isPlainData(value) {
|
|
20168
|
-
if (value === null || typeof value !== "object") return false;
|
|
20169
|
-
const proto = Object.getPrototypeOf(value);
|
|
20170
|
-
return proto === Object.prototype || proto === Array.prototype || proto === null;
|
|
20171
|
-
}
|
|
20172
|
-
function deepFreeze(value) {
|
|
20173
|
-
const seen = /* @__PURE__ */ new WeakSet();
|
|
20174
|
-
const walk2 = (node) => {
|
|
20175
|
-
if (!isPlainData(node) || seen.has(node)) return;
|
|
20176
|
-
seen.add(node);
|
|
20177
|
-
Object.freeze(node);
|
|
20178
|
-
for (const key2 of Reflect.ownKeys(node)) walk2(Reflect.get(node, key2));
|
|
20179
|
-
};
|
|
20180
|
-
walk2(value);
|
|
20181
|
-
return value;
|
|
20182
|
-
}
|
|
20183
|
-
var init_immutable = __esm({
|
|
20184
|
-
"src/immutable.ts"() {
|
|
20185
|
-
"use strict";
|
|
20186
|
-
}
|
|
20187
|
-
});
|
|
20188
|
-
|
|
20189
|
-
// src/runtimes.ts
|
|
20190
|
-
import { existsSync as existsSync12, readFileSync as readFileSync16 } from "node:fs";
|
|
20191
|
-
import { join as join25 } from "node:path";
|
|
20192
|
-
function isRecord6(x) {
|
|
20193
|
-
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
20194
|
-
}
|
|
20195
|
-
function hooksBeyondTamperward(hooks) {
|
|
20196
|
-
if (!isRecord6(hooks)) return hooks != null;
|
|
20197
|
-
for (const entries of Object.values(hooks)) {
|
|
20198
|
-
if (!Array.isArray(entries)) {
|
|
20199
|
-
if (entries != null) return true;
|
|
20200
|
-
continue;
|
|
20201
|
-
}
|
|
20202
|
-
for (const entry of entries) {
|
|
20203
|
-
const hs = isRecord6(entry) ? entry.hooks : null;
|
|
20204
|
-
if (!Array.isArray(hs)) {
|
|
20205
|
-
if (hs != null) return true;
|
|
20206
|
-
continue;
|
|
20207
|
-
}
|
|
20208
|
-
for (const h of hs) {
|
|
20209
|
-
const cmd = isRecord6(h) ? String(h.command ?? "") : "";
|
|
20210
|
-
if (!/tamperward/.test(cmd)) return true;
|
|
20211
|
-
}
|
|
20212
|
-
}
|
|
20213
|
-
}
|
|
20214
|
-
return false;
|
|
20215
|
-
}
|
|
20216
|
-
function claudeSettingsBeyondTamperward(cwd) {
|
|
20217
|
-
const path = join25(cwd, ".claude", "settings.json");
|
|
20218
|
-
let raw;
|
|
20219
|
-
try {
|
|
20220
|
-
if (!existsSync12(path)) return false;
|
|
20221
|
-
raw = readFileSync16(path, "utf8");
|
|
20222
|
-
} catch {
|
|
20223
|
-
return false;
|
|
20224
|
-
}
|
|
20225
|
-
let parsed;
|
|
20226
|
-
try {
|
|
20227
|
-
parsed = JSON.parse(raw);
|
|
20228
|
-
} catch {
|
|
20229
|
-
return raw.trim().length > 0;
|
|
20230
|
-
}
|
|
20231
|
-
if (!isRecord6(parsed)) return raw.trim().length > 0;
|
|
20232
|
-
for (const [key2, value] of Object.entries(parsed)) {
|
|
20233
|
-
if (key2 === "disableAllHooks") continue;
|
|
20234
|
-
if (key2 === "hooks") {
|
|
20235
|
-
if (hooksBeyondTamperward(value)) return true;
|
|
20236
|
-
continue;
|
|
20237
|
-
}
|
|
20238
|
-
return true;
|
|
20239
|
-
}
|
|
20240
|
-
return false;
|
|
20241
|
-
}
|
|
20242
|
-
function detectRuntimes(cwd) {
|
|
20243
|
-
const found = [];
|
|
20244
|
-
for (const rt of KNOWN_RUNTIMES) {
|
|
20245
|
-
const marker = rt.markers.find((m) => {
|
|
20246
|
-
try {
|
|
20247
|
-
return existsSync12(join25(cwd, m));
|
|
20248
|
-
} catch {
|
|
20249
|
-
return false;
|
|
20250
|
-
}
|
|
20251
|
-
});
|
|
20252
|
-
const matchedMarker = marker ?? rt.detectExtra?.(cwd) ?? void 0;
|
|
20253
|
-
if (matchedMarker !== void 0) found.push({ ...rt, matchedMarker });
|
|
20254
|
-
}
|
|
20255
|
-
return found;
|
|
20256
|
-
}
|
|
20257
|
-
function hasInLoopRuntime(detected) {
|
|
20258
|
-
return detected.some((r) => r.steering === "in-loop");
|
|
20259
|
-
}
|
|
20260
|
-
function detectionHeadline(detected) {
|
|
20261
|
-
if (detected.length === 0) {
|
|
20262
|
-
return "No agent runtime detected. Wiring Claude Code in-loop steering plus the agent-neutral layers (pre-commit + CI) that protect any agent or human.";
|
|
20263
|
-
}
|
|
20264
|
-
if (detected.length === 1) {
|
|
20265
|
-
const r = detected[0];
|
|
20266
|
-
return `Detected ${r.label} \u2014 ${r.note}`;
|
|
20267
|
-
}
|
|
20268
|
-
const parts = detected.map((r) => `${r.label} (${r.steering === "in-loop" ? "in-loop" : "neutral layers"})`);
|
|
20269
|
-
return `Detected ${detected.length} agent runtimes: ${parts.join(", ")}.`;
|
|
20270
|
-
}
|
|
20271
|
-
function neutralOnlyCaveat(detected) {
|
|
20272
|
-
const neutral = detected.filter((r) => r.steering === "neutral");
|
|
20273
|
-
if (neutral.length === 0) return null;
|
|
20274
|
-
const names = neutral.map((r) => r.label).join(", ");
|
|
20275
|
-
const claudeAlso = hasInLoopRuntime(detected);
|
|
20276
|
-
const lead = claudeAlso ? `In-loop steering (deny-before-execute) ships for Claude Code only today: Claude Code here gets it, but on ${names} your live protection is ` : `In-loop steering (deny-before-execute) ships for Claude Code only today, so on ${names} your live protection is `;
|
|
20277
|
-
const tail = claudeAlso ? "" : " The Claude hook wiring is still written and is inert unless Claude Code runs in this repository.";
|
|
20278
|
-
return `${lead}the agent-neutral layers: pre-commit and CI.${tail}`;
|
|
20279
|
-
}
|
|
20280
|
-
var KNOWN_RUNTIMES;
|
|
20281
|
-
var init_runtimes = __esm({
|
|
20282
|
-
"src/runtimes.ts"() {
|
|
20283
|
-
"use strict";
|
|
20284
|
-
init_immutable();
|
|
20285
|
-
KNOWN_RUNTIMES = deepFreeze([
|
|
20286
|
-
{
|
|
20287
|
-
id: "claude-code",
|
|
20288
|
-
// Markers are Claude Code files `init` never writes, so detection never mistakes
|
|
20289
|
-
// TamperWard's own generated `.claude/settings.json` for evidence a human runs
|
|
20290
|
-
// Claude here (#526). `detectExtra` looks at that generated file's CONTENT for
|
|
20291
|
-
// real use (configuration beyond the two hooks init merges in).
|
|
20292
|
-
label: "Claude Code",
|
|
20293
|
-
markers: ["CLAUDE.md", ".claude/settings.local.json", ".claude/commands", ".claude/agents"],
|
|
20294
|
-
detectExtra: (cwd) => claudeSettingsBeyondTamperward(cwd) ? ".claude/settings.json" : null,
|
|
20295
|
-
steering: "in-loop",
|
|
20296
|
-
note: "native in-loop steering (PreToolUse deny + Stop sweep) plus the agent-neutral layers."
|
|
20297
|
-
},
|
|
20298
|
-
{
|
|
20299
|
-
id: "cursor",
|
|
20300
|
-
label: "Cursor",
|
|
20301
|
-
markers: [".cursor", ".cursorrules"],
|
|
20302
|
-
steering: "neutral",
|
|
20303
|
-
note: "agent-neutral layers (pre-commit + CI) today; no in-loop adapter ships for Cursor."
|
|
20304
|
-
},
|
|
20305
|
-
{
|
|
20306
|
-
id: "copilot",
|
|
20307
|
-
label: "GitHub Copilot",
|
|
20308
|
-
markers: [".github/copilot-instructions.md", ".github/copilot"],
|
|
20309
|
-
steering: "neutral",
|
|
20310
|
-
note: "agent-neutral layers (pre-commit + CI) today; the experimental Copilot adapters are not qualified for in-loop steering (see `tamperward runtime verify`)."
|
|
20311
|
-
},
|
|
20312
|
-
{
|
|
20313
|
-
id: "codex",
|
|
20314
|
-
label: "an AGENTS.md-aware agent (e.g. Codex)",
|
|
20315
|
-
markers: [".codex", "AGENTS.md"],
|
|
20316
|
-
steering: "neutral",
|
|
20317
|
-
note: "agent-neutral layers (pre-commit + CI) today; the experimental Codex adapter is not qualified for in-loop steering (see `tamperward runtime verify`)."
|
|
20318
|
-
}
|
|
20319
|
-
]);
|
|
20320
|
-
}
|
|
20321
|
-
});
|
|
20322
|
-
|
|
20323
20528
|
// src/adapters/contract.ts
|
|
20324
20529
|
function immutableRuntimeCapabilities(caps) {
|
|
20325
20530
|
return Object.freeze({
|
|
@@ -23231,6 +23436,62 @@ var init_onboard = __esm({
|
|
|
23231
23436
|
}
|
|
23232
23437
|
});
|
|
23233
23438
|
|
|
23439
|
+
// src/cli/runtime-hook.ts
|
|
23440
|
+
import { readFileSync as readFileSync20 } from "node:fs";
|
|
23441
|
+
function isRuntimeHookAgent(agent) {
|
|
23442
|
+
return RUNTIME_HOOK_AGENTS.some((known) => known === agent);
|
|
23443
|
+
}
|
|
23444
|
+
function runRuntimeHookFromRaw(kind, agent, raw, cwd = process.cwd()) {
|
|
23445
|
+
if (!isRuntimeHookAgent(agent)) {
|
|
23446
|
+
return { exitCode: 2, stdout: "", stderr: `tamperward: unsupported ${kind === "Stop" ? "sweep" : "hook"} agent "${agent}" (claude, ${RUNTIME_HOOK_AGENTS.join(", ")})
|
|
23447
|
+
` };
|
|
23448
|
+
}
|
|
23449
|
+
const adapter = adapterFor(agent);
|
|
23450
|
+
if (!adapter) {
|
|
23451
|
+
return { exitCode: 2, stdout: "", stderr: `tamperward: no adapter is registered for "${agent}"
|
|
23452
|
+
` };
|
|
23453
|
+
}
|
|
23454
|
+
const phase = kind === "Stop" ? "end-of-turn" : "pre-action";
|
|
23455
|
+
try {
|
|
23456
|
+
const r = adapter.decide(raw, phase, cwd);
|
|
23457
|
+
return { exitCode: 0, stdout: r.wire ?? "", stderr: "" };
|
|
23458
|
+
} catch (e) {
|
|
23459
|
+
const detail = e instanceof Error ? e.message : String(e);
|
|
23460
|
+
const wire = adapter.denyPayload([steeringUnavailableFinding(detail)], phase);
|
|
23461
|
+
return { exitCode: 0, stdout: wire, stderr: "" };
|
|
23462
|
+
}
|
|
23463
|
+
}
|
|
23464
|
+
function runRuntimeHook(kind, agent) {
|
|
23465
|
+
let raw;
|
|
23466
|
+
try {
|
|
23467
|
+
raw = readFileSync20(0, "utf8");
|
|
23468
|
+
} catch (e) {
|
|
23469
|
+
raw = "";
|
|
23470
|
+
const r2 = runRuntimeHookFromRaw(kind, agent, "", process.cwd());
|
|
23471
|
+
if (r2.exitCode !== 0) {
|
|
23472
|
+
process.stderr.write(r2.stderr);
|
|
23473
|
+
return r2.exitCode;
|
|
23474
|
+
}
|
|
23475
|
+
const adapter = adapterFor(agent);
|
|
23476
|
+
const detail = `cannot read the hook payload on stdin: ${e instanceof Error ? e.message : String(e)}`;
|
|
23477
|
+
process.stdout.write(adapter ? adapter.denyPayload([steeringUnavailableFinding(detail)], kind === "Stop" ? "end-of-turn" : "pre-action") : "");
|
|
23478
|
+
return 0;
|
|
23479
|
+
}
|
|
23480
|
+
const r = runRuntimeHookFromRaw(kind, agent, raw, process.cwd());
|
|
23481
|
+
if (r.stdout) process.stdout.write(r.stdout);
|
|
23482
|
+
if (r.stderr) process.stderr.write(r.stderr);
|
|
23483
|
+
return r.exitCode;
|
|
23484
|
+
}
|
|
23485
|
+
var RUNTIME_HOOK_AGENTS;
|
|
23486
|
+
var init_runtime_hook = __esm({
|
|
23487
|
+
"src/cli/runtime-hook.ts"() {
|
|
23488
|
+
"use strict";
|
|
23489
|
+
init_registry();
|
|
23490
|
+
init_contract();
|
|
23491
|
+
RUNTIME_HOOK_AGENTS = Object.freeze(["codex", "copilot"]);
|
|
23492
|
+
}
|
|
23493
|
+
});
|
|
23494
|
+
|
|
23234
23495
|
// src/research/adapter.ts
|
|
23235
23496
|
import { isAbsolute as isAbsolute17, resolve as resolve17 } from "node:path";
|
|
23236
23497
|
function taskEnv(task) {
|
|
@@ -23372,7 +23633,7 @@ var init_capture = __esm({
|
|
|
23372
23633
|
|
|
23373
23634
|
// src/research/manifest.ts
|
|
23374
23635
|
import { createHash as createHash16 } from "node:crypto";
|
|
23375
|
-
import { readFileSync as
|
|
23636
|
+
import { readFileSync as readFileSync21 } from "node:fs";
|
|
23376
23637
|
import { dirname as dirname14, resolve as resolve18 } from "node:path";
|
|
23377
23638
|
function taskFrom(raw, index, manifestDir) {
|
|
23378
23639
|
const where = `task #${index + 1}`;
|
|
@@ -23406,7 +23667,7 @@ function readManifest(path) {
|
|
|
23406
23667
|
const abs = resolve18(path);
|
|
23407
23668
|
let bytes;
|
|
23408
23669
|
try {
|
|
23409
|
-
bytes =
|
|
23670
|
+
bytes = readFileSync21(abs);
|
|
23410
23671
|
} catch (e) {
|
|
23411
23672
|
throw new ResearchError(`cannot read task manifest ${abs}: ${e instanceof Error ? e.message : String(e)}`);
|
|
23412
23673
|
}
|
|
@@ -23825,7 +24086,7 @@ var init_record = __esm({
|
|
|
23825
24086
|
// src/research/run.ts
|
|
23826
24087
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
23827
24088
|
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
23828
|
-
import { closeSync as closeSync5, existsSync as existsSync15, linkSync, mkdirSync as mkdirSync12, openSync as openSync4, readFileSync as
|
|
24089
|
+
import { closeSync as closeSync5, existsSync as existsSync15, linkSync, mkdirSync as mkdirSync12, openSync as openSync4, readFileSync as readFileSync22, renameSync as renameSync4, rmSync as rmSync14, unlinkSync, writeFileSync as writeFileSync11, writeSync as writeSync2 } from "node:fs";
|
|
23829
24090
|
import { hostname } from "node:os";
|
|
23830
24091
|
import { join as join29, resolve as resolve19 } from "node:path";
|
|
23831
24092
|
function git7(args, cwd) {
|
|
@@ -23839,7 +24100,7 @@ function researchLockPath(ledger) {
|
|
|
23839
24100
|
}
|
|
23840
24101
|
function readLockOwner(path) {
|
|
23841
24102
|
try {
|
|
23842
|
-
const parsed = JSON.parse(
|
|
24103
|
+
const parsed = JSON.parse(readFileSync22(path, "utf8"));
|
|
23843
24104
|
if (!isRecord(parsed) || typeof parsed.pid !== "number" || !Number.isInteger(parsed.pid) || parsed.pid <= 0) return null;
|
|
23844
24105
|
if (typeof parsed.host !== "string" || typeof parsed.started_at !== "string" || typeof parsed.token !== "string") return null;
|
|
23845
24106
|
return { pid: parsed.pid, host: parsed.host, started_at: parsed.started_at, token: parsed.token };
|
|
@@ -23924,7 +24185,7 @@ function acquireResearchLock(ledger, breakStaleLock = false) {
|
|
|
23924
24185
|
if (released) return;
|
|
23925
24186
|
released = true;
|
|
23926
24187
|
try {
|
|
23927
|
-
if (
|
|
24188
|
+
if (readFileSync22(path, "utf8") === text) unlinkSync(path);
|
|
23928
24189
|
} catch {
|
|
23929
24190
|
}
|
|
23930
24191
|
}
|
|
@@ -23933,7 +24194,7 @@ function acquireResearchLock(ledger, breakStaleLock = false) {
|
|
|
23933
24194
|
function resumableRecord(path, expected) {
|
|
23934
24195
|
let raw;
|
|
23935
24196
|
try {
|
|
23936
|
-
raw = JSON.parse(
|
|
24197
|
+
raw = JSON.parse(readFileSync22(path, "utf8"));
|
|
23937
24198
|
} catch (e) {
|
|
23938
24199
|
throw new ResearchError(`ledger record ${path} is malformed (${errorMessage(e).split("\n")[0]}); remove it to regenerate the pair, or use a new --out`);
|
|
23939
24200
|
}
|
|
@@ -24355,7 +24616,7 @@ var init_run2 = __esm({
|
|
|
24355
24616
|
});
|
|
24356
24617
|
|
|
24357
24618
|
// src/research/ledger-files.ts
|
|
24358
|
-
import { closeSync as closeSync6, constants as constants5, fstatSync as fstatSync3, lstatSync as lstatSync12, openSync as openSync5, readFileSync as
|
|
24619
|
+
import { closeSync as closeSync6, constants as constants5, fstatSync as fstatSync3, lstatSync as lstatSync12, openSync as openSync5, readFileSync as readFileSync23, readdirSync as readdirSync8, realpathSync as realpathSync12 } from "node:fs";
|
|
24359
24620
|
import { join as join30, resolve as resolve20 } from "node:path";
|
|
24360
24621
|
function lstatOrNull(path) {
|
|
24361
24622
|
try {
|
|
@@ -24398,7 +24659,7 @@ function readRegularFile(path, realPairsDir, name) {
|
|
|
24398
24659
|
if (!opened.isFile() || !sameInode || opened.nlink !== 1) {
|
|
24399
24660
|
throw new ResearchError(`ledger record ${path} changed between listing and reading; refusing to read it`);
|
|
24400
24661
|
}
|
|
24401
|
-
return
|
|
24662
|
+
return readFileSync23(fd);
|
|
24402
24663
|
} finally {
|
|
24403
24664
|
closeSync6(fd);
|
|
24404
24665
|
}
|
|
@@ -24414,7 +24675,7 @@ function readPairFiles(ledger) {
|
|
|
24414
24675
|
const realPairsDir = realpathSync12.native(pairsDir);
|
|
24415
24676
|
let names;
|
|
24416
24677
|
try {
|
|
24417
|
-
names =
|
|
24678
|
+
names = readdirSync8(pairsDir).filter((n2) => n2.endsWith(".json")).sort();
|
|
24418
24679
|
} catch (e) {
|
|
24419
24680
|
throw new ResearchError(`cannot read ledger ${pairsDir}: ${errorMessage(e)}`);
|
|
24420
24681
|
}
|
|
@@ -24571,7 +24832,7 @@ var init_summarize = __esm({
|
|
|
24571
24832
|
|
|
24572
24833
|
// src/research/init.ts
|
|
24573
24834
|
import { createHash as createHash17 } from "node:crypto";
|
|
24574
|
-
import { existsSync as existsSync16, mkdirSync as mkdirSync13, readFileSync as
|
|
24835
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync13, readFileSync as readFileSync24, writeFileSync as writeFileSync12 } from "node:fs";
|
|
24575
24836
|
import { basename as basename6, dirname as dirname15, resolve as resolve21 } from "node:path";
|
|
24576
24837
|
function idFrom(repo) {
|
|
24577
24838
|
const clean = repo.replace(/[\\/]$/, "").split(/[\\/]/).pop() || "task";
|
|
@@ -24623,7 +24884,7 @@ function runResearchInit(opts) {
|
|
|
24623
24884
|
}
|
|
24624
24885
|
}
|
|
24625
24886
|
function requireHash(path) {
|
|
24626
|
-
return createHash17("sha256").update(
|
|
24887
|
+
return createHash17("sha256").update(readFileSync24(path)).digest("hex");
|
|
24627
24888
|
}
|
|
24628
24889
|
var init_init2 = __esm({
|
|
24629
24890
|
"src/research/init.ts"() {
|
|
@@ -24691,7 +24952,7 @@ var init_report2 = __esm({
|
|
|
24691
24952
|
import { createHash as createHash18 } from "node:crypto";
|
|
24692
24953
|
import { isDeepStrictEqual } from "node:util";
|
|
24693
24954
|
import { gzipSync, gunzipSync } from "node:zlib";
|
|
24694
|
-
import { mkdirSync as mkdirSync14, readFileSync as
|
|
24955
|
+
import { mkdirSync as mkdirSync14, readFileSync as readFileSync25, writeFileSync as writeFileSync13 } from "node:fs";
|
|
24695
24956
|
import { dirname as dirname16, resolve as resolve22 } from "node:path";
|
|
24696
24957
|
function octal(value, width) {
|
|
24697
24958
|
return value.toString(8).padStart(width - 1, "0") + "\0";
|
|
@@ -24797,7 +25058,7 @@ function jsonBytes(value) {
|
|
|
24797
25058
|
function validateResearchBundle(path) {
|
|
24798
25059
|
let files;
|
|
24799
25060
|
try {
|
|
24800
|
-
files = parseTar(gunzipSync(
|
|
25061
|
+
files = parseTar(gunzipSync(readFileSync25(resolve22(path))));
|
|
24801
25062
|
} catch (e) {
|
|
24802
25063
|
throw new ResearchError(`cannot read research bundle ${path}: ${e instanceof Error ? e.message : String(e)}`);
|
|
24803
25064
|
}
|
|
@@ -24867,7 +25128,7 @@ function createResearchBundle(opts) {
|
|
|
24867
25128
|
const { records, entries } = packagedRecords(ledger);
|
|
24868
25129
|
const summary = summarizeRecords(records);
|
|
24869
25130
|
if (opts.manifest) {
|
|
24870
|
-
const manifest =
|
|
25131
|
+
const manifest = readFileSync25(resolve22(opts.manifest));
|
|
24871
25132
|
const sha3 = createHash18("sha256").update(manifest).digest("hex");
|
|
24872
25133
|
if (sha3 !== summary.manifest_sha256) throw new ResearchError(`manifest sha256 ${sha3} does not match ledger ${summary.manifest_sha256}`);
|
|
24873
25134
|
entries.push({ name: "manifest.json", bytes: manifest });
|
|
@@ -25037,8 +25298,43 @@ var init_research = __esm({
|
|
|
25037
25298
|
}
|
|
25038
25299
|
});
|
|
25039
25300
|
|
|
25301
|
+
// src/cli/render/verify-github.ts
|
|
25302
|
+
function safeDetail(value) {
|
|
25303
|
+
const normalized = value.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").replace(/\s+/g, " ").trim();
|
|
25304
|
+
if (normalized.length <= DETAIL_LIMIT) return normalized;
|
|
25305
|
+
return normalized.slice(0, DETAIL_LIMIT - 1).trimEnd() + "\u2026";
|
|
25306
|
+
}
|
|
25307
|
+
function workflowData(value) {
|
|
25308
|
+
return value.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
|
|
25309
|
+
}
|
|
25310
|
+
function verifyWorkflowCommand(verdict2, exitCode, details = {}, env = process.env) {
|
|
25311
|
+
if (env.GITHUB_ACTIONS !== "true" || verdict2 === "VERIFIED") return void 0;
|
|
25312
|
+
if (verdict2 === "MASKED_FAILURE" && exitCode === 0) {
|
|
25313
|
+
return "::warning title=TamperWard verify::MASKED_FAILURE: a trusted out-of-band sign-off was accepted; this is not a VERIFIED result. Review the verification summary.";
|
|
25314
|
+
}
|
|
25315
|
+
const parts = [ERROR_MESSAGES[verdict2]];
|
|
25316
|
+
if (verdict2 === "CANNOT_VERIFY") {
|
|
25317
|
+
if (details.reason) parts.push(`Reason: ${details.reason}`);
|
|
25318
|
+
if (details.detail) parts.push(`Detail: ${safeDetail(details.detail)}`);
|
|
25319
|
+
}
|
|
25320
|
+
return `::error title=TamperWard verify::${workflowData(parts.join("\n"))}`;
|
|
25321
|
+
}
|
|
25322
|
+
var ERROR_MESSAGES, DETAIL_LIMIT;
|
|
25323
|
+
var init_verify_github = __esm({
|
|
25324
|
+
"src/cli/render/verify-github.ts"() {
|
|
25325
|
+
"use strict";
|
|
25326
|
+
ERROR_MESSAGES = {
|
|
25327
|
+
MASKED_FAILURE: "MASKED_FAILURE: the visible suite passed, but the suite restored from the trusted base failed. Review the test changes and verification summary.",
|
|
25328
|
+
SUITE_RED: "SUITE_RED: the visible suite failed. Fix the failing change before merging.",
|
|
25329
|
+
BUDGET_EXCEEDED: "BUDGET_EXCEEDED: verification exceeded its configured budget; CI could not establish a clean result.",
|
|
25330
|
+
CANNOT_VERIFY: "CANNOT_VERIFY: CI could not establish a trusted verification result."
|
|
25331
|
+
};
|
|
25332
|
+
DETAIL_LIMIT = 320;
|
|
25333
|
+
}
|
|
25334
|
+
});
|
|
25335
|
+
|
|
25040
25336
|
// src/cli/receipt.ts
|
|
25041
|
-
import { appendFileSync as appendFileSync3, readFileSync as
|
|
25337
|
+
import { appendFileSync as appendFileSync3, readFileSync as readFileSync26, writeFileSync as writeFileSync14 } from "node:fs";
|
|
25042
25338
|
function parseReceipt(args) {
|
|
25043
25339
|
const [sub, ...rest] = args;
|
|
25044
25340
|
const exportOpts = {};
|
|
@@ -25165,16 +25461,21 @@ function ciAdjudicate(cwd, opts) {
|
|
|
25165
25461
|
if (opts.ciResult !== void 0) {
|
|
25166
25462
|
let doc;
|
|
25167
25463
|
try {
|
|
25168
|
-
doc = JSON.parse(
|
|
25464
|
+
doc = JSON.parse(readFileSync26(opts.ciResult, "utf8"));
|
|
25169
25465
|
} catch (e) {
|
|
25170
25466
|
return {
|
|
25171
25467
|
ci: { verdict: "CANNOT_VERIFY", binding, binding_error: `unreadable CI verify result: ${e instanceof Error ? e.message : String(e)}` },
|
|
25172
|
-
exit: 2
|
|
25468
|
+
exit: 2,
|
|
25469
|
+
annotation: { detail: "The CI verify result could not be read. Check the verify step output and artifact path." }
|
|
25173
25470
|
};
|
|
25174
25471
|
}
|
|
25175
25472
|
const check2 = readCiVerify(doc, binding, cwd, opts);
|
|
25176
25473
|
if (check2.reason !== void 0) {
|
|
25177
|
-
return {
|
|
25474
|
+
return {
|
|
25475
|
+
ci: { verdict: "CANNOT_VERIFY", binding, binding_error: check2.reason },
|
|
25476
|
+
exit: 2,
|
|
25477
|
+
annotation: { detail: check2.reason }
|
|
25478
|
+
};
|
|
25178
25479
|
}
|
|
25179
25480
|
const oob = oobFromEnv();
|
|
25180
25481
|
const oobHead = oobHeadFromEnv();
|
|
@@ -25189,7 +25490,8 @@ function ciAdjudicate(cwd, opts) {
|
|
|
25189
25490
|
...check2.adjudicated_tree !== void 0 ? { adjudicated_tree: check2.adjudicated_tree } : {},
|
|
25190
25491
|
...bindingError ? { binding_error: bindingError } : {}
|
|
25191
25492
|
},
|
|
25192
|
-
exit: verdictExit(check2.verdict, signedOff)
|
|
25493
|
+
exit: verdictExit(check2.verdict, signedOff),
|
|
25494
|
+
annotation: { reason: check2.verifierReason, detail: check2.detail }
|
|
25193
25495
|
};
|
|
25194
25496
|
}
|
|
25195
25497
|
const impl = opts.runVerifyImpl ?? runVerify;
|
|
@@ -25214,7 +25516,8 @@ function ciAdjudicate(cwd, opts) {
|
|
|
25214
25516
|
...binding ? { adjudicated_tree: binding.tree } : {},
|
|
25215
25517
|
...bindingError ? { binding_error: bindingError } : {}
|
|
25216
25518
|
},
|
|
25217
|
-
exit
|
|
25519
|
+
exit,
|
|
25520
|
+
annotation: { reason: summary?.reason, detail: summary?.detail }
|
|
25218
25521
|
};
|
|
25219
25522
|
}
|
|
25220
25523
|
function isCommitId(v) {
|
|
@@ -25248,7 +25551,9 @@ function readCiVerify(doc, binding, cwd, opts) {
|
|
|
25248
25551
|
}
|
|
25249
25552
|
}
|
|
25250
25553
|
const adjudicated_tree = typeof d.adjudicated_tree === "string" && d.adjudicated_tree.length > 0 ? d.adjudicated_tree : void 0;
|
|
25251
|
-
|
|
25554
|
+
const verifierReason = isOneOf(VERIFY_CANNOT_VERIFY_REASONS, d.reason) ? d.reason : void 0;
|
|
25555
|
+
const detail = typeof d.detail === "string" ? d.detail : void 0;
|
|
25556
|
+
return { verdict: d.verdict, verifierReason, detail, adjudicated_tree };
|
|
25252
25557
|
}
|
|
25253
25558
|
function isRecordObject(v) {
|
|
25254
25559
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
@@ -25311,7 +25616,7 @@ function loadClaimedReceipt(path) {
|
|
|
25311
25616
|
}
|
|
25312
25617
|
let raw;
|
|
25313
25618
|
try {
|
|
25314
|
-
raw =
|
|
25619
|
+
raw = readFileSync26(path, "utf8");
|
|
25315
25620
|
} catch (e) {
|
|
25316
25621
|
return { disposition: "MALFORMED", detail: `receipt file could not be read (${e instanceof Error ? e.message : String(e)})` };
|
|
25317
25622
|
}
|
|
@@ -25327,7 +25632,7 @@ function runReceiptReconcile(opts = {}) {
|
|
|
25327
25632
|
const cwd = opts.cwd ?? process.cwd();
|
|
25328
25633
|
const guard = needRepo(cwd, "reconcile");
|
|
25329
25634
|
if (guard !== null) return guard;
|
|
25330
|
-
const { ci, exit } = ciAdjudicate(cwd, opts);
|
|
25635
|
+
const { ci, exit, annotation: annotationDetails } = ciAdjudicate(cwd, opts);
|
|
25331
25636
|
const claimed = loadClaimedReceipt(opts.receipt);
|
|
25332
25637
|
const r = reconcile(ci, claimed);
|
|
25333
25638
|
const claimedReceipt = claimed.disposition === "PRESENT" ? claimed.receipt : null;
|
|
@@ -25337,6 +25642,16 @@ function runReceiptReconcile(opts = {}) {
|
|
|
25337
25642
|
process.stdout.write(renderReconcile(r, claimedReceipt, ci.binding, ci.adjudicated_tree, colourEnabled(process.env, process.stdout)));
|
|
25338
25643
|
}
|
|
25339
25644
|
writeJobSummary(r, claimedReceipt, ci.binding, ci.adjudicated_tree);
|
|
25645
|
+
if (!opts.json) {
|
|
25646
|
+
const annotation = verifyWorkflowCommand(ci.verdict, exit, annotationDetails);
|
|
25647
|
+
if (annotation) process.stdout.write(`${annotation}
|
|
25648
|
+
`);
|
|
25649
|
+
}
|
|
25650
|
+
if (!opts.json) {
|
|
25651
|
+
const annotation = verifyWorkflowCommand(ci.verdict, exit);
|
|
25652
|
+
if (annotation) process.stdout.write(`${annotation}
|
|
25653
|
+
`);
|
|
25654
|
+
}
|
|
25340
25655
|
return exit;
|
|
25341
25656
|
}
|
|
25342
25657
|
function writeJobSummary(r, receipt, ciBinding, adjudicatedTree) {
|
|
@@ -25377,6 +25692,7 @@ var init_receipt = __esm({
|
|
|
25377
25692
|
init_verify();
|
|
25378
25693
|
init_signoff();
|
|
25379
25694
|
init_text();
|
|
25695
|
+
init_verify_github();
|
|
25380
25696
|
init_status();
|
|
25381
25697
|
RECEIPT_SUBCOMMANDS = ["export", "reconcile"];
|
|
25382
25698
|
AGREEMENT_SEVERITY = {
|
|
@@ -25435,13 +25751,9 @@ function parseAllow(args) {
|
|
|
25435
25751
|
return o;
|
|
25436
25752
|
}
|
|
25437
25753
|
function runAgentCommand(kind, args) {
|
|
25438
|
-
const agent = args[0];
|
|
25439
|
-
if (agent
|
|
25440
|
-
|
|
25441
|
-
`);
|
|
25442
|
-
return 2;
|
|
25443
|
-
}
|
|
25444
|
-
return kind === "hook" ? runHookClaude() : runSweepClaude();
|
|
25754
|
+
const agent = args[0] ?? "";
|
|
25755
|
+
if (agent === "claude") return kind === "hook" ? runHookClaude() : runSweepClaude();
|
|
25756
|
+
return runRuntimeHook(kind === "hook" ? "PreToolUse" : "Stop", agent);
|
|
25445
25757
|
}
|
|
25446
25758
|
function parseInit(args) {
|
|
25447
25759
|
const o = {};
|
|
@@ -26161,6 +26473,7 @@ var init_main = __esm({
|
|
|
26161
26473
|
init_run();
|
|
26162
26474
|
init_watch();
|
|
26163
26475
|
init_onboard();
|
|
26476
|
+
init_runtime_hook();
|
|
26164
26477
|
init_research();
|
|
26165
26478
|
init_runtime();
|
|
26166
26479
|
init_receipt();
|
|
@@ -26172,7 +26485,7 @@ var init_main = __esm({
|
|
|
26172
26485
|
// src/cli/index.ts
|
|
26173
26486
|
init_hook_client();
|
|
26174
26487
|
init_exit();
|
|
26175
|
-
import { readFileSync as
|
|
26488
|
+
import { readFileSync as readFileSync27 } from "node:fs";
|
|
26176
26489
|
function loadMain() {
|
|
26177
26490
|
return Promise.resolve().then(() => (init_main(), main_exports));
|
|
26178
26491
|
}
|
|
@@ -26185,7 +26498,7 @@ async function launch(argv) {
|
|
|
26185
26498
|
if (kind && hookServiceEnabled()) {
|
|
26186
26499
|
let raw = null;
|
|
26187
26500
|
try {
|
|
26188
|
-
raw =
|
|
26501
|
+
raw = readFileSync27(0, "utf8");
|
|
26189
26502
|
} catch {
|
|
26190
26503
|
raw = null;
|
|
26191
26504
|
}
|