tamperward 2.34.0 → 2.36.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 +746 -192
- package/package.json +1 -1
- package/schemas/status-v1.schema.json +205 -0
package/dist/cli/index.js
CHANGED
|
@@ -10872,8 +10872,8 @@ import { dirname as dirname2, join as join9 } from "node:path";
|
|
|
10872
10872
|
function compactOobToken(want, head) {
|
|
10873
10873
|
const normalizedHead = head.trim().toLowerCase();
|
|
10874
10874
|
if (!want || !FULL_OBJECT_ID.test(normalizedHead)) return null;
|
|
10875
|
-
const
|
|
10876
|
-
return `${COMPACT_OOB_PREFIX}${
|
|
10875
|
+
const digest2 = createHash3("sha256").update(`tamperward:oob:v1\0${want}\0${normalizedHead}`).digest("base64url");
|
|
10876
|
+
return `${COMPACT_OOB_PREFIX}${digest2}`;
|
|
10877
10877
|
}
|
|
10878
10878
|
function ledgerEntryFrom(value) {
|
|
10879
10879
|
if (!isRecord(value)) return null;
|
|
@@ -14883,8 +14883,8 @@ function parseCapturedSupervisorResult(stdoutText) {
|
|
|
14883
14883
|
};
|
|
14884
14884
|
}
|
|
14885
14885
|
function runCapturedProcessSync(executable, args, opts) {
|
|
14886
|
-
const
|
|
14887
|
-
const configFile = join17(
|
|
14886
|
+
const stateDir2 = mkdtempSync2(join17(tmpdir3(), "tw-suite-capture-"));
|
|
14887
|
+
const configFile = join17(stateDir2, "config.json");
|
|
14888
14888
|
const backstop = opts.backstopMs ?? 3e4;
|
|
14889
14889
|
try {
|
|
14890
14890
|
writeFileSync8(
|
|
@@ -14932,7 +14932,7 @@ function runCapturedProcessSync(executable, args, opts) {
|
|
|
14932
14932
|
}
|
|
14933
14933
|
return parsed;
|
|
14934
14934
|
} finally {
|
|
14935
|
-
rmSync6(
|
|
14935
|
+
rmSync6(stateDir2, { recursive: true, force: true });
|
|
14936
14936
|
}
|
|
14937
14937
|
}
|
|
14938
14938
|
var DIAGNOSTIC_TAIL_BYTES, EMPTY_STREAM, CLEANUP_HELPERS_SRC, CAPTURE_SUPERVISOR;
|
|
@@ -15651,12 +15651,375 @@ var init_verifier_backend = __esm({
|
|
|
15651
15651
|
}
|
|
15652
15652
|
});
|
|
15653
15653
|
|
|
15654
|
-
// src/
|
|
15655
|
-
import { execFileSync as execFileSync8
|
|
15654
|
+
// src/verification-state.ts
|
|
15655
|
+
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
15656
15656
|
import { createHash as createHash12 } from "node:crypto";
|
|
15657
|
-
import {
|
|
15657
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync16, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
|
|
15658
|
+
import { dirname as dirname8, join as join19 } from "node:path";
|
|
15659
|
+
function git2(args, cwd) {
|
|
15660
|
+
return execFileSync8("git", args, {
|
|
15661
|
+
cwd,
|
|
15662
|
+
encoding: "utf8",
|
|
15663
|
+
maxBuffer: 1 << 28,
|
|
15664
|
+
env: trustedGitEnv()
|
|
15665
|
+
});
|
|
15666
|
+
}
|
|
15667
|
+
function resolveBaseRev(baseRef, cwd) {
|
|
15668
|
+
assertRev(baseRef);
|
|
15669
|
+
const rev2 = git2(["rev-parse", "--verify", `${baseRef}^{commit}`], cwd).trim();
|
|
15670
|
+
try {
|
|
15671
|
+
return git2(["merge-base", rev2, "HEAD"], cwd).trim();
|
|
15672
|
+
} catch {
|
|
15673
|
+
return rev2;
|
|
15674
|
+
}
|
|
15675
|
+
}
|
|
15676
|
+
function canonicalJson(value) {
|
|
15677
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
|
|
15678
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
15679
|
+
const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
15680
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(",")}}`;
|
|
15681
|
+
}
|
|
15682
|
+
function digest(value) {
|
|
15683
|
+
return createHash12("sha256").update(canonicalJson(value)).digest("hex");
|
|
15684
|
+
}
|
|
15685
|
+
function surfacePaths(base, cwd, policy) {
|
|
15686
|
+
let listing;
|
|
15687
|
+
try {
|
|
15688
|
+
listing = git2(["ls-tree", "-r", "--name-only", "-z", base], cwd);
|
|
15689
|
+
} catch {
|
|
15690
|
+
return [];
|
|
15691
|
+
}
|
|
15692
|
+
const inputs = policy.verify?.inputs ?? [];
|
|
15693
|
+
return listing.split("\0").filter(Boolean).filter((p) => isProtected(p, policy) || matchesAny(p, inputs)).sort();
|
|
15694
|
+
}
|
|
15695
|
+
function interventionWiring(cwd) {
|
|
15696
|
+
const dir = claudeConfigDir();
|
|
15697
|
+
const sources = [
|
|
15698
|
+
["repo", join19(cwd, ".claude", "settings.json")],
|
|
15699
|
+
["repo.local", join19(cwd, ".claude", "settings.local.json")],
|
|
15700
|
+
["user", join19(dir, "settings.json")],
|
|
15701
|
+
["user.local", join19(dir, "settings.local.json")]
|
|
15702
|
+
];
|
|
15703
|
+
return sources.map(([source, path]) => {
|
|
15704
|
+
let parsed;
|
|
15705
|
+
try {
|
|
15706
|
+
parsed = JSON.parse(readFileSync16(path, "utf8"));
|
|
15707
|
+
} catch {
|
|
15708
|
+
return { source, present: false };
|
|
15709
|
+
}
|
|
15710
|
+
if (!isRecord4(parsed)) return { source, present: true, shape: "non-object" };
|
|
15711
|
+
return {
|
|
15712
|
+
source,
|
|
15713
|
+
present: true,
|
|
15714
|
+
hooks: parsed.hooks ?? null,
|
|
15715
|
+
disableAllHooks: parsed.disableAllHooks ?? null
|
|
15716
|
+
};
|
|
15717
|
+
});
|
|
15718
|
+
}
|
|
15719
|
+
function dependenciesBinding(root, policy, command) {
|
|
15720
|
+
const backend = policy.verify?.backend ?? "local";
|
|
15721
|
+
if (backend === "container") {
|
|
15722
|
+
return digest({ backend, image: policy.verify?.image ?? null });
|
|
15723
|
+
}
|
|
15724
|
+
const dep = discoverDependencyEnvironment(root, command);
|
|
15725
|
+
return digest({ backend, status: dep.status, fingerprint: dep.fingerprint ?? null });
|
|
15726
|
+
}
|
|
15727
|
+
function computeBinding(cwd, inputs, precomputed) {
|
|
15728
|
+
const root = repoRoot(cwd);
|
|
15729
|
+
const head = git2(["rev-parse", "--verify", "HEAD^{commit}"], root).trim();
|
|
15730
|
+
const base = resolveBaseRev(inputs.base_ref, root);
|
|
15731
|
+
const policy = inputs.explicit_base ? loadPolicyAt(base, root) ?? defaultPolicy() : loadPolicy(root);
|
|
15732
|
+
const command = inputs.command_source === "flag" ? inputs.command : policy.verify?.command ?? "";
|
|
15733
|
+
if (!command) {
|
|
15734
|
+
throw new Error("no verifier command is configured for the recorded verification");
|
|
15735
|
+
}
|
|
15736
|
+
const budget = inputs.budget_source === "flag" ? inputs.budget : policy.verify?.budget ?? 300;
|
|
15737
|
+
const protectedIgnored = (rel) => isProtected(rel, policy);
|
|
15738
|
+
return {
|
|
15739
|
+
// NOTE (#500/#600 follow-up): `tree` hashes every tracked and ignored-protected
|
|
15740
|
+
// file and `dependencies` walks the attested dependency roots, on EVERY call.
|
|
15741
|
+
// Right for `verify` (once per run) and reused there via `precomputed.tree`,
|
|
15742
|
+
// but `status` is meant to be polled continuously by a status bar/dashboard,
|
|
15743
|
+
// where a large repository makes each poll a full read. A stat-cache identity
|
|
15744
|
+
// (git's index plus hashing only what changed) is a deliberate follow-up, not
|
|
15745
|
+
// built here.
|
|
15746
|
+
tree: precomputed?.tree ?? treeFingerprint(root, protectedIgnored),
|
|
15747
|
+
head,
|
|
15748
|
+
base,
|
|
15749
|
+
policy: digest(policy),
|
|
15750
|
+
verifier: digest({
|
|
15751
|
+
command,
|
|
15752
|
+
budget,
|
|
15753
|
+
inputs: policy.verify?.inputs ?? [],
|
|
15754
|
+
backend: policy.verify?.backend ?? "local",
|
|
15755
|
+
image: policy.verify?.image ?? null
|
|
15756
|
+
}),
|
|
15757
|
+
surface: digest(surfacePaths(base, root, policy)),
|
|
15758
|
+
intervention: digest(interventionWiring(root)),
|
|
15759
|
+
dependencies: dependenciesBinding(root, policy, command)
|
|
15760
|
+
};
|
|
15761
|
+
}
|
|
15762
|
+
function stateDir(cwd) {
|
|
15763
|
+
const ctx = repoContext(cwd);
|
|
15764
|
+
return ctx ? join19(ctx.gitDir, "tamperward") : null;
|
|
15765
|
+
}
|
|
15766
|
+
function verificationRecordPath(cwd) {
|
|
15767
|
+
const dir = stateDir(cwd);
|
|
15768
|
+
return dir ? join19(dir, "verification-state.json") : null;
|
|
15769
|
+
}
|
|
15770
|
+
function verifyingMarkerPath(cwd) {
|
|
15771
|
+
const dir = stateDir(cwd);
|
|
15772
|
+
return dir ? join19(dir, "verifying.json") : null;
|
|
15773
|
+
}
|
|
15774
|
+
function recordVerification(cwd, inputs, treeFingerprintValue) {
|
|
15775
|
+
const path = verificationRecordPath(cwd);
|
|
15776
|
+
if (!path) return false;
|
|
15777
|
+
const binding = computeBinding(
|
|
15778
|
+
repoRoot(cwd),
|
|
15779
|
+
inputs,
|
|
15780
|
+
treeFingerprintValue ? { tree: treeFingerprintValue } : void 0
|
|
15781
|
+
);
|
|
15782
|
+
const record = {
|
|
15783
|
+
schema_version: VERIFICATION_STATE_SCHEMA_VERSION,
|
|
15784
|
+
verdict: "VERIFIED",
|
|
15785
|
+
verified_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15786
|
+
tw_version: TW_VERSION,
|
|
15787
|
+
inputs,
|
|
15788
|
+
binding
|
|
15789
|
+
};
|
|
15790
|
+
mkdirSync8(dirname8(path), { recursive: true });
|
|
15791
|
+
writeFileSync9(path, JSON.stringify(record) + "\n");
|
|
15792
|
+
return true;
|
|
15793
|
+
}
|
|
15794
|
+
function invalidateVerificationRecordIfCurrent(cwd) {
|
|
15795
|
+
const record = readVerificationRecord(cwd);
|
|
15796
|
+
if (!record) return false;
|
|
15797
|
+
let live;
|
|
15798
|
+
try {
|
|
15799
|
+
live = computeBinding(cwd, record.inputs);
|
|
15800
|
+
} catch {
|
|
15801
|
+
return false;
|
|
15802
|
+
}
|
|
15803
|
+
if (firstMismatch(record.binding, live)) return false;
|
|
15804
|
+
const path = verificationRecordPath(cwd);
|
|
15805
|
+
if (!path) return false;
|
|
15806
|
+
try {
|
|
15807
|
+
rmSync7(path, { force: true });
|
|
15808
|
+
return true;
|
|
15809
|
+
} catch {
|
|
15810
|
+
return false;
|
|
15811
|
+
}
|
|
15812
|
+
}
|
|
15813
|
+
function isRecord4(v) {
|
|
15814
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
15815
|
+
}
|
|
15816
|
+
function isBinding(v) {
|
|
15817
|
+
if (!isRecord4(v)) return false;
|
|
15818
|
+
return BINDING_INPUTS.every((k) => {
|
|
15819
|
+
const val = v[k];
|
|
15820
|
+
return typeof val === "string" && val.length > 0;
|
|
15821
|
+
});
|
|
15822
|
+
}
|
|
15823
|
+
function parseVerificationRecord(value) {
|
|
15824
|
+
if (!isRecord4(value)) return null;
|
|
15825
|
+
if (value.schema_version !== VERIFICATION_STATE_SCHEMA_VERSION) return null;
|
|
15826
|
+
if (value.verdict !== "VERIFIED") return null;
|
|
15827
|
+
if (typeof value.verified_at !== "string") return null;
|
|
15828
|
+
if (typeof value.tw_version !== "string") return null;
|
|
15829
|
+
const inputs = value.inputs;
|
|
15830
|
+
if (!isRecord4(inputs)) return null;
|
|
15831
|
+
if (typeof inputs.base_ref !== "string" || inputs.base_ref.length === 0) return null;
|
|
15832
|
+
if (typeof inputs.explicit_base !== "boolean") return null;
|
|
15833
|
+
if (inputs.command_source !== "policy" && inputs.command_source !== "flag") return null;
|
|
15834
|
+
if (typeof inputs.command !== "string") return null;
|
|
15835
|
+
if (inputs.budget_source !== "policy" && inputs.budget_source !== "flag") return null;
|
|
15836
|
+
if (typeof inputs.budget !== "number" || !Number.isFinite(inputs.budget)) return null;
|
|
15837
|
+
if (!isBinding(value.binding)) return null;
|
|
15838
|
+
return {
|
|
15839
|
+
schema_version: VERIFICATION_STATE_SCHEMA_VERSION,
|
|
15840
|
+
verdict: "VERIFIED",
|
|
15841
|
+
verified_at: value.verified_at,
|
|
15842
|
+
tw_version: value.tw_version,
|
|
15843
|
+
inputs: {
|
|
15844
|
+
base_ref: inputs.base_ref,
|
|
15845
|
+
explicit_base: inputs.explicit_base,
|
|
15846
|
+
command_source: inputs.command_source,
|
|
15847
|
+
command: inputs.command,
|
|
15848
|
+
budget_source: inputs.budget_source,
|
|
15849
|
+
budget: inputs.budget
|
|
15850
|
+
},
|
|
15851
|
+
binding: value.binding
|
|
15852
|
+
};
|
|
15853
|
+
}
|
|
15854
|
+
function readVerificationRecord(cwd) {
|
|
15855
|
+
const path = verificationRecordPath(cwd);
|
|
15856
|
+
if (!path) return null;
|
|
15857
|
+
let raw;
|
|
15858
|
+
try {
|
|
15859
|
+
raw = readFileSync16(path, "utf8");
|
|
15860
|
+
} catch {
|
|
15861
|
+
return null;
|
|
15862
|
+
}
|
|
15863
|
+
let parsed;
|
|
15864
|
+
try {
|
|
15865
|
+
parsed = JSON.parse(raw);
|
|
15866
|
+
} catch {
|
|
15867
|
+
return null;
|
|
15868
|
+
}
|
|
15869
|
+
return parseVerificationRecord(parsed);
|
|
15870
|
+
}
|
|
15871
|
+
function pidAlive3(pid) {
|
|
15872
|
+
try {
|
|
15873
|
+
process.kill(pid, 0);
|
|
15874
|
+
return true;
|
|
15875
|
+
} catch (e) {
|
|
15876
|
+
return isRecord4(e) && e.code === "EPERM";
|
|
15877
|
+
}
|
|
15878
|
+
}
|
|
15879
|
+
function beginVerifying(cwd) {
|
|
15880
|
+
const path = verifyingMarkerPath(cwd);
|
|
15881
|
+
if (!path) return false;
|
|
15882
|
+
try {
|
|
15883
|
+
const marker = {
|
|
15884
|
+
schema_version: VERIFICATION_STATE_SCHEMA_VERSION,
|
|
15885
|
+
pid: process.pid,
|
|
15886
|
+
started_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
15887
|
+
};
|
|
15888
|
+
mkdirSync8(dirname8(path), { recursive: true });
|
|
15889
|
+
writeFileSync9(path, JSON.stringify(marker) + "\n");
|
|
15890
|
+
return true;
|
|
15891
|
+
} catch {
|
|
15892
|
+
return false;
|
|
15893
|
+
}
|
|
15894
|
+
}
|
|
15895
|
+
function endVerifying(cwd) {
|
|
15896
|
+
const path = verifyingMarkerPath(cwd);
|
|
15897
|
+
if (!path) return;
|
|
15898
|
+
try {
|
|
15899
|
+
rmSync7(path, { force: true });
|
|
15900
|
+
} catch {
|
|
15901
|
+
}
|
|
15902
|
+
}
|
|
15903
|
+
function readVerifyingMarker(cwd) {
|
|
15904
|
+
const path = verifyingMarkerPath(cwd);
|
|
15905
|
+
if (!path) return null;
|
|
15906
|
+
let parsed;
|
|
15907
|
+
try {
|
|
15908
|
+
parsed = JSON.parse(readFileSync16(path, "utf8"));
|
|
15909
|
+
} catch {
|
|
15910
|
+
return null;
|
|
15911
|
+
}
|
|
15912
|
+
if (!isRecord4(parsed)) return null;
|
|
15913
|
+
if (parsed.schema_version !== VERIFICATION_STATE_SCHEMA_VERSION) return null;
|
|
15914
|
+
if (typeof parsed.pid !== "number" || !Number.isInteger(parsed.pid) || parsed.pid <= 0) return null;
|
|
15915
|
+
if (typeof parsed.started_at !== "string") return null;
|
|
15916
|
+
if (!pidAlive3(parsed.pid)) return null;
|
|
15917
|
+
return { schema_version: VERIFICATION_STATE_SCHEMA_VERSION, pid: parsed.pid, started_at: parsed.started_at };
|
|
15918
|
+
}
|
|
15919
|
+
function firstMismatch(recorded, live) {
|
|
15920
|
+
for (const input of MISMATCH_PRIORITY) {
|
|
15921
|
+
if (recorded[input] !== live[input]) return input;
|
|
15922
|
+
}
|
|
15923
|
+
return null;
|
|
15924
|
+
}
|
|
15925
|
+
function evaluateVerificationState(cwd) {
|
|
15926
|
+
if (readVerifyingMarker(cwd)) {
|
|
15927
|
+
return { state: "VERIFYING", reason: "a verification is in progress" };
|
|
15928
|
+
}
|
|
15929
|
+
const record = readVerificationRecord(cwd);
|
|
15930
|
+
if (!record) {
|
|
15931
|
+
return {
|
|
15932
|
+
state: "UNVERIFIED",
|
|
15933
|
+
reason: "no successful verification has been recorded for this repository"
|
|
15934
|
+
};
|
|
15935
|
+
}
|
|
15936
|
+
let live;
|
|
15937
|
+
try {
|
|
15938
|
+
live = computeBinding(cwd, record.inputs);
|
|
15939
|
+
} catch (e) {
|
|
15940
|
+
return {
|
|
15941
|
+
state: "BROKEN",
|
|
15942
|
+
reason: "verification authority wiring is invalid or unavailable",
|
|
15943
|
+
detail: e instanceof Error ? e.message : String(e),
|
|
15944
|
+
verified_at: record.verified_at,
|
|
15945
|
+
verifier_command: record.inputs.command,
|
|
15946
|
+
record
|
|
15947
|
+
};
|
|
15948
|
+
}
|
|
15949
|
+
const changed = firstMismatch(record.binding, live);
|
|
15950
|
+
if (!changed) {
|
|
15951
|
+
return {
|
|
15952
|
+
state: "CURRENT",
|
|
15953
|
+
verified_at: record.verified_at,
|
|
15954
|
+
base: live.base,
|
|
15955
|
+
head: live.head,
|
|
15956
|
+
verifier_command: record.inputs.command,
|
|
15957
|
+
record
|
|
15958
|
+
};
|
|
15959
|
+
}
|
|
15960
|
+
return {
|
|
15961
|
+
state: "STALE",
|
|
15962
|
+
changed_input: changed,
|
|
15963
|
+
reason: STALE_REASON[changed],
|
|
15964
|
+
verified_at: record.verified_at,
|
|
15965
|
+
base: live.base,
|
|
15966
|
+
head: live.head,
|
|
15967
|
+
verifier_command: record.inputs.command,
|
|
15968
|
+
record
|
|
15969
|
+
};
|
|
15970
|
+
}
|
|
15971
|
+
var VERIFICATION_STATE_SCHEMA_VERSION, BINDING_INPUTS, STALE_REASON, MISMATCH_PRIORITY;
|
|
15972
|
+
var init_verification_state = __esm({
|
|
15973
|
+
"src/verification-state.ts"() {
|
|
15974
|
+
"use strict";
|
|
15975
|
+
init_build();
|
|
15976
|
+
init_trusted();
|
|
15977
|
+
init_fingerprint();
|
|
15978
|
+
init_policy();
|
|
15979
|
+
init_policy_load();
|
|
15980
|
+
init_repo_context();
|
|
15981
|
+
init_wiring();
|
|
15982
|
+
init_dependency_env();
|
|
15983
|
+
VERIFICATION_STATE_SCHEMA_VERSION = 1;
|
|
15984
|
+
BINDING_INPUTS = [
|
|
15985
|
+
"tree",
|
|
15986
|
+
"head",
|
|
15987
|
+
"base",
|
|
15988
|
+
"policy",
|
|
15989
|
+
"verifier",
|
|
15990
|
+
"surface",
|
|
15991
|
+
"intervention",
|
|
15992
|
+
"dependencies"
|
|
15993
|
+
];
|
|
15994
|
+
STALE_REASON = {
|
|
15995
|
+
tree: "candidate tree changed since verification",
|
|
15996
|
+
head: "HEAD commit changed since verification",
|
|
15997
|
+
base: "trusted base commit changed since verification",
|
|
15998
|
+
policy: "TamperWard policy changed since verification",
|
|
15999
|
+
verifier: "verifier configuration changed since verification",
|
|
16000
|
+
surface: "protected verification surface changed since verification",
|
|
16001
|
+
intervention: "runtime steering wiring changed since verification",
|
|
16002
|
+
dependencies: "dependency environment changed since verification"
|
|
16003
|
+
};
|
|
16004
|
+
MISMATCH_PRIORITY = [
|
|
16005
|
+
"head",
|
|
16006
|
+
"base",
|
|
16007
|
+
"verifier",
|
|
16008
|
+
"surface",
|
|
16009
|
+
"intervention",
|
|
16010
|
+
"dependencies",
|
|
16011
|
+
"policy",
|
|
16012
|
+
"tree"
|
|
16013
|
+
];
|
|
16014
|
+
}
|
|
16015
|
+
});
|
|
16016
|
+
|
|
16017
|
+
// src/cli/verify.ts
|
|
16018
|
+
import { execFileSync as execFileSync9, spawnSync as spawnSync3 } from "node:child_process";
|
|
16019
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
16020
|
+
import { chmodSync as chmodSync2, cpSync, lstatSync as lstatSync9, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync3, readFileSync as readFileSync17, readlinkSync as readlinkSync3, rmSync as rmSync8, symlinkSync, writeFileSync as writeFileSync10, existsSync as existsSync14 } from "node:fs";
|
|
15658
16021
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
15659
|
-
import { dirname as
|
|
16022
|
+
import { dirname as dirname9, isAbsolute as isAbsolute11, join as join20, relative as relative6, resolve as resolve9, sep as sep4 } from "node:path";
|
|
15660
16023
|
function oracleAssuranceReport() {
|
|
15661
16024
|
return {
|
|
15662
16025
|
level: "suite-exit-only",
|
|
@@ -15665,14 +16028,14 @@ function oracleAssuranceReport() {
|
|
|
15665
16028
|
limitation: "candidate source executes inside the configured suite process and may terminate or interpose on that in-process oracle; isolated-container is execution-domain isolation, not semantic/oracle isolation"
|
|
15666
16029
|
};
|
|
15667
16030
|
}
|
|
15668
|
-
function
|
|
15669
|
-
return
|
|
16031
|
+
function git3(args, cwd) {
|
|
16032
|
+
return execFileSync9("git", args, { cwd, encoding: "utf8", maxBuffer: 1 << 28, env: trustedGitEnv() });
|
|
15670
16033
|
}
|
|
15671
16034
|
function resolveBase(base, cwd) {
|
|
15672
16035
|
assertRev(base);
|
|
15673
|
-
const rev2 =
|
|
16036
|
+
const rev2 = git3(["rev-parse", "--verify", `${base}^{commit}`], cwd).trim();
|
|
15674
16037
|
try {
|
|
15675
|
-
return
|
|
16038
|
+
return git3(["merge-base", rev2, "HEAD"], cwd).trim();
|
|
15676
16039
|
} catch {
|
|
15677
16040
|
return rev2;
|
|
15678
16041
|
}
|
|
@@ -15720,15 +16083,15 @@ function rootedLinkTarget(target) {
|
|
|
15720
16083
|
return isAbsolute11(target) || driveRelativeLinkTarget(target);
|
|
15721
16084
|
}
|
|
15722
16085
|
function safeSymlink(target, out3, root, label) {
|
|
15723
|
-
if (rootedLinkTarget(target) || !inside2(root, resolve9(
|
|
16086
|
+
if (rootedLinkTarget(target) || !inside2(root, resolve9(dirname9(out3), target))) {
|
|
15724
16087
|
throw linkEscape(label, target);
|
|
15725
16088
|
}
|
|
15726
|
-
|
|
16089
|
+
rmSync8(out3, { force: true });
|
|
15727
16090
|
symlinkSync(target, out3);
|
|
15728
16091
|
}
|
|
15729
16092
|
function validateSymlinkGraph(target, out3, root, dependencyRoot, label) {
|
|
15730
16093
|
let domain = "tree";
|
|
15731
|
-
let stack = linkParts(relative6(root,
|
|
16094
|
+
let stack = linkParts(relative6(root, dirname9(out3)));
|
|
15732
16095
|
const pending = linkParts(target);
|
|
15733
16096
|
const seen = /* @__PURE__ */ new Set();
|
|
15734
16097
|
let hops = 0;
|
|
@@ -15745,7 +16108,7 @@ function validateSymlinkGraph(target, out3, root, dependencyRoot, label) {
|
|
|
15745
16108
|
continue;
|
|
15746
16109
|
}
|
|
15747
16110
|
const base = domain === "deps" && dependencyRoot ? dependencyRoot : root;
|
|
15748
|
-
const current =
|
|
16111
|
+
const current = join20(base, ...stack);
|
|
15749
16112
|
let st;
|
|
15750
16113
|
try {
|
|
15751
16114
|
st = lstatSync9(current);
|
|
@@ -15787,7 +16150,7 @@ function rejectLinkedParent(cwd, rel) {
|
|
|
15787
16150
|
const parts = rel.split("/").slice(0, -1);
|
|
15788
16151
|
let current = cwd;
|
|
15789
16152
|
for (const part of parts) {
|
|
15790
|
-
current =
|
|
16153
|
+
current = join20(current, part);
|
|
15791
16154
|
let st;
|
|
15792
16155
|
try {
|
|
15793
16156
|
st = lstatSync9(current);
|
|
@@ -15812,7 +16175,7 @@ function materialize(cwd, dest, dependencyRoot) {
|
|
|
15812
16175
|
}
|
|
15813
16176
|
const links = [];
|
|
15814
16177
|
for (const rel of listed) {
|
|
15815
|
-
const src =
|
|
16178
|
+
const src = join20(cwd, rel);
|
|
15816
16179
|
rejectLinkedParent(cwd, rel);
|
|
15817
16180
|
let st;
|
|
15818
16181
|
try {
|
|
@@ -15820,8 +16183,8 @@ function materialize(cwd, dest, dependencyRoot) {
|
|
|
15820
16183
|
} catch {
|
|
15821
16184
|
continue;
|
|
15822
16185
|
}
|
|
15823
|
-
const out3 =
|
|
15824
|
-
|
|
16186
|
+
const out3 = join20(dest, rel);
|
|
16187
|
+
mkdirSync9(dirname9(out3), { recursive: true });
|
|
15825
16188
|
dropSymlink(out3);
|
|
15826
16189
|
if (st.isSymbolicLink()) {
|
|
15827
16190
|
const target = readlinkSync3(src);
|
|
@@ -15840,19 +16203,19 @@ function materialize(cwd, dest, dependencyRoot) {
|
|
|
15840
16203
|
for (const link of links) {
|
|
15841
16204
|
validateSymlinkGraph(link.target, link.out, dest, dependencyRoot, link.rel);
|
|
15842
16205
|
}
|
|
15843
|
-
if (dependencyRoot) symlinkSync(dependencyRoot,
|
|
16206
|
+
if (dependencyRoot) symlinkSync(dependencyRoot, join20(dest, "node_modules"), "dir");
|
|
15844
16207
|
}
|
|
15845
16208
|
function workingTreePaths(cwd) {
|
|
15846
|
-
return
|
|
16209
|
+
return git3(["ls-files", "-z", "--cached", "--others", "--exclude-standard"], cwd).split("\0").filter(Boolean);
|
|
15847
16210
|
}
|
|
15848
16211
|
function probeFilesystemCaseSensitivity(dir) {
|
|
15849
|
-
const upper =
|
|
15850
|
-
const lower =
|
|
16212
|
+
const upper = join20(dir, ".tw-case-probe-A");
|
|
16213
|
+
const lower = join20(dir, ".tw-case-probe-a");
|
|
15851
16214
|
try {
|
|
15852
|
-
|
|
16215
|
+
writeFileSync10(upper, "", { flag: "wx" });
|
|
15853
16216
|
return !existsSync14(lower);
|
|
15854
16217
|
} finally {
|
|
15855
|
-
|
|
16218
|
+
rmSync8(upper, { force: true });
|
|
15856
16219
|
}
|
|
15857
16220
|
}
|
|
15858
16221
|
function foldCase(p) {
|
|
@@ -15870,7 +16233,7 @@ function caseFoldCollision(paths) {
|
|
|
15870
16233
|
}
|
|
15871
16234
|
function dropSymlink(p) {
|
|
15872
16235
|
try {
|
|
15873
|
-
if (lstatSync9(p).isSymbolicLink())
|
|
16236
|
+
if (lstatSync9(p).isSymbolicLink()) rmSync8(p, { force: true });
|
|
15874
16237
|
} catch {
|
|
15875
16238
|
}
|
|
15876
16239
|
}
|
|
@@ -15899,22 +16262,22 @@ function verifierCoveredInputs(cmd, atBase, policy) {
|
|
|
15899
16262
|
return covered;
|
|
15900
16263
|
}
|
|
15901
16264
|
function baseEntries(base, cwd) {
|
|
15902
|
-
return
|
|
16265
|
+
return git3(["ls-tree", "-r", "-z", base], cwd).split("\0").filter(Boolean).map((rec) => {
|
|
15903
16266
|
const tab = rec.indexOf(" ");
|
|
15904
16267
|
const [mode, type] = rec.slice(0, tab).split(" ");
|
|
15905
16268
|
return { mode, type, path: rec.slice(tab + 1) };
|
|
15906
16269
|
});
|
|
15907
16270
|
}
|
|
15908
16271
|
function overlayDigest(dest, paths) {
|
|
15909
|
-
const h =
|
|
16272
|
+
const h = createHash13("sha256");
|
|
15910
16273
|
for (const rel of [...paths].sort()) {
|
|
15911
16274
|
h.update(rel);
|
|
15912
16275
|
h.update("\0");
|
|
15913
|
-
const p =
|
|
16276
|
+
const p = join20(dest, rel);
|
|
15914
16277
|
try {
|
|
15915
16278
|
const st = lstatSync9(p);
|
|
15916
16279
|
h.update(String(st.mode));
|
|
15917
|
-
h.update(st.isSymbolicLink() ? readlinkSync3(p) :
|
|
16280
|
+
h.update(st.isSymbolicLink() ? readlinkSync3(p) : readFileSync17(p));
|
|
15918
16281
|
} catch {
|
|
15919
16282
|
h.update("<gone>");
|
|
15920
16283
|
}
|
|
@@ -15943,16 +16306,16 @@ function overlayPristine(cwd, base, dest, policy, cmd, dependencyRoot, caseSensi
|
|
|
15943
16306
|
if (e.type === "commit") {
|
|
15944
16307
|
throw new Error(`the base's ${e.path} is a submodule; the pristine copy cannot reproduce it`);
|
|
15945
16308
|
}
|
|
15946
|
-
const content =
|
|
15947
|
-
const out3 =
|
|
15948
|
-
|
|
15949
|
-
|
|
16309
|
+
const content = execFileSync9("git", ["show", `${base}:${e.path}`], { cwd, maxBuffer: 1 << 28, env: trustedGitEnv() });
|
|
16310
|
+
const out3 = join20(dest, e.path);
|
|
16311
|
+
mkdirSync9(dirname9(out3), { recursive: true });
|
|
16312
|
+
rmSync8(out3, { force: true });
|
|
15950
16313
|
if (e.mode === "120000") {
|
|
15951
16314
|
const target = content.toString("utf8");
|
|
15952
16315
|
safeSymlink(target, out3, dest, `the base's ${e.path}`);
|
|
15953
16316
|
restoredLinks.push({ path: e.path, out: out3, target });
|
|
15954
16317
|
} else {
|
|
15955
|
-
|
|
16318
|
+
writeFileSync10(out3, content);
|
|
15956
16319
|
chmodSync2(out3, parseInt(e.mode.slice(-4), 8) & 511);
|
|
15957
16320
|
}
|
|
15958
16321
|
restored.push(e.path);
|
|
@@ -15961,7 +16324,7 @@ function overlayPristine(cwd, base, dest, policy, cmd, dependencyRoot, caseSensi
|
|
|
15961
16324
|
const inCopy = workingTreePaths(cwd);
|
|
15962
16325
|
for (const rel of inCopy) {
|
|
15963
16326
|
if (!isOverlay(rel) || baseProtected.has(fold(rel))) continue;
|
|
15964
|
-
|
|
16327
|
+
rmSync8(join20(dest, rel), { force: true });
|
|
15965
16328
|
removed++;
|
|
15966
16329
|
}
|
|
15967
16330
|
for (const link of restoredLinks) {
|
|
@@ -15975,9 +16338,9 @@ function suiteEnv(scratch) {
|
|
|
15975
16338
|
if (DROPPED_ENV.has(k) || PINNED_NPM.test(k)) continue;
|
|
15976
16339
|
env[k] = v;
|
|
15977
16340
|
}
|
|
15978
|
-
const userRc =
|
|
15979
|
-
const globalRc =
|
|
15980
|
-
for (const f of [userRc, globalRc])
|
|
16341
|
+
const userRc = join20(scratch, "npmrc-user");
|
|
16342
|
+
const globalRc = join20(scratch, "npmrc-global");
|
|
16343
|
+
for (const f of [userRc, globalRc]) writeFileSync10(f, "", { mode: 292 });
|
|
15981
16344
|
env.npm_config_userconfig = userRc;
|
|
15982
16345
|
env.npm_config_globalconfig = globalRc;
|
|
15983
16346
|
env.npm_config_node_options = " ";
|
|
@@ -16008,7 +16371,7 @@ function runLocalSuite(dir, cmd, budgetSecs) {
|
|
|
16008
16371
|
reason: `checkpointed-local verifier is unsupported on ${process.platform}; no trusted local shell contract is defined`
|
|
16009
16372
|
};
|
|
16010
16373
|
}
|
|
16011
|
-
const scratch = mkdtempSync3(
|
|
16374
|
+
const scratch = mkdtempSync3(join20(tmpdir4(), "tw-verify-run-"));
|
|
16012
16375
|
try {
|
|
16013
16376
|
const r = runCapturedProcessSync(shell.executable, shell.args, {
|
|
16014
16377
|
cwd: dir,
|
|
@@ -16053,7 +16416,7 @@ function runLocalSuite(dir, cmd, budgetSecs) {
|
|
|
16053
16416
|
diagnostics: r.diagnostics
|
|
16054
16417
|
};
|
|
16055
16418
|
} finally {
|
|
16056
|
-
|
|
16419
|
+
rmSync8(scratch, { recursive: true, force: true });
|
|
16057
16420
|
}
|
|
16058
16421
|
}
|
|
16059
16422
|
function diagnosticsJson(diagnostics, includeTail) {
|
|
@@ -16098,6 +16461,15 @@ function verifyVerdictLine(verdict2, ctx) {
|
|
|
16098
16461
|
}
|
|
16099
16462
|
}
|
|
16100
16463
|
function runVerify(opts) {
|
|
16464
|
+
const markerCwd = repoRoot(opts.cwd ?? process.cwd());
|
|
16465
|
+
const marked = opts.silent ? false : beginVerifying(markerCwd);
|
|
16466
|
+
try {
|
|
16467
|
+
return runVerifyImpl(opts);
|
|
16468
|
+
} finally {
|
|
16469
|
+
if (marked) endVerifying(markerCwd);
|
|
16470
|
+
}
|
|
16471
|
+
}
|
|
16472
|
+
function runVerifyImpl(opts) {
|
|
16101
16473
|
const cwd = repoRoot(opts.cwd ?? process.cwd());
|
|
16102
16474
|
const colour = opts.silent ? false : colourEnabled(process.env, process.stdout);
|
|
16103
16475
|
const paintVerify = (s) => {
|
|
@@ -16137,7 +16509,7 @@ function runVerify(opts) {
|
|
|
16137
16509
|
return cannotVerify("POLICY_ERROR", `cannot load policy (${e instanceof Error ? e.message : String(e)})`);
|
|
16138
16510
|
}
|
|
16139
16511
|
if (opts.requireAncestor) {
|
|
16140
|
-
const requested =
|
|
16512
|
+
const requested = git3(["rev-parse", "--verify", `${opts.base ?? "HEAD"}^{commit}`], cwd).trim();
|
|
16141
16513
|
if (!baseIsAncestorOfHead(requested, cwd)) {
|
|
16142
16514
|
return cannotVerify(
|
|
16143
16515
|
"BASE_NOT_ANCESTOR",
|
|
@@ -16245,17 +16617,17 @@ function runVerify(opts) {
|
|
|
16245
16617
|
} catch {
|
|
16246
16618
|
return cannotVerify("BASE_UNRESOLVABLE", `cannot resolve base rev "${opts.base ?? "HEAD"}"`);
|
|
16247
16619
|
}
|
|
16248
|
-
const visRoot = mkdtempSync3(
|
|
16249
|
-
const visDir =
|
|
16620
|
+
const visRoot = mkdtempSync3(join20(tmpdir4(), "tw-verify-vis-"));
|
|
16621
|
+
const visDir = join20(visRoot, "t");
|
|
16250
16622
|
const cleanup = (dirs) => {
|
|
16251
16623
|
if (opts.keep) return;
|
|
16252
|
-
for (const d of dirs)
|
|
16624
|
+
for (const d of dirs) rmSync8(d, { recursive: true, force: true });
|
|
16253
16625
|
};
|
|
16254
16626
|
filesystemCaseSensitive = (opts.probeCaseSensitivity ?? probeFilesystemCaseSensitivity)(visRoot);
|
|
16255
16627
|
if (!filesystemCaseSensitive) {
|
|
16256
16628
|
const present = (rel) => {
|
|
16257
16629
|
try {
|
|
16258
|
-
lstatSync9(
|
|
16630
|
+
lstatSync9(join20(cwd, rel));
|
|
16259
16631
|
return true;
|
|
16260
16632
|
} catch {
|
|
16261
16633
|
return false;
|
|
@@ -16287,7 +16659,7 @@ function runVerify(opts) {
|
|
|
16287
16659
|
);
|
|
16288
16660
|
}
|
|
16289
16661
|
try {
|
|
16290
|
-
|
|
16662
|
+
mkdirSync9(visDir);
|
|
16291
16663
|
materialize(cwd, visDir, frozenNodeModules);
|
|
16292
16664
|
} catch (e) {
|
|
16293
16665
|
cleanup([visRoot]);
|
|
@@ -16388,12 +16760,12 @@ function runVerify(opts) {
|
|
|
16388
16760
|
{ stage: "visible", dependency_environment: dependencyReport(), verifier_backend: backendReport(), oracle_assurance: oracleAssuranceReport() }
|
|
16389
16761
|
);
|
|
16390
16762
|
}
|
|
16391
|
-
const priRoot = mkdtempSync3(
|
|
16392
|
-
const priDir =
|
|
16763
|
+
const priRoot = mkdtempSync3(join20(tmpdir4(), "tw-verify-pri-"));
|
|
16764
|
+
const priDir = join20(priRoot, "t");
|
|
16393
16765
|
let restored = [];
|
|
16394
16766
|
let removedAdded = 0;
|
|
16395
16767
|
try {
|
|
16396
|
-
|
|
16768
|
+
mkdirSync9(priDir);
|
|
16397
16769
|
materialize(cwd, priDir, frozenNodeModules);
|
|
16398
16770
|
({ restored, removed: removedAdded } = overlayPristine(
|
|
16399
16771
|
cwd,
|
|
@@ -16479,6 +16851,25 @@ function runVerify(opts) {
|
|
|
16479
16851
|
code2 = 1;
|
|
16480
16852
|
}
|
|
16481
16853
|
opts.onVerdict?.({ verdict: verdict2 });
|
|
16854
|
+
if (verdict2 === "VERIFIED") {
|
|
16855
|
+
try {
|
|
16856
|
+
const inputs = {
|
|
16857
|
+
base_ref: opts.base ?? "HEAD",
|
|
16858
|
+
explicit_base: opts.base !== void 0,
|
|
16859
|
+
command_source: opts.cmd !== void 0 ? "flag" : "policy",
|
|
16860
|
+
command: cmd,
|
|
16861
|
+
budget_source: opts.budget !== void 0 ? "flag" : "policy",
|
|
16862
|
+
budget
|
|
16863
|
+
};
|
|
16864
|
+
recordVerification(cwd, inputs, treeBefore);
|
|
16865
|
+
} catch {
|
|
16866
|
+
}
|
|
16867
|
+
} else {
|
|
16868
|
+
try {
|
|
16869
|
+
invalidateVerificationRecordIfCurrent(cwd);
|
|
16870
|
+
} catch {
|
|
16871
|
+
}
|
|
16872
|
+
}
|
|
16482
16873
|
const signedOff = verdict2 === "MASKED_FAILURE" ? oobToken("verify", oobFromEnv(), oobHeadFromEnv()) : null;
|
|
16483
16874
|
if (signedOff) code2 = 0;
|
|
16484
16875
|
if (opts.json) {
|
|
@@ -16584,6 +16975,7 @@ var init_verify = __esm({
|
|
|
16584
16975
|
init_verifier_backend();
|
|
16585
16976
|
init_machine_output();
|
|
16586
16977
|
init_signoff();
|
|
16978
|
+
init_verification_state();
|
|
16587
16979
|
init_suite_diagnostics();
|
|
16588
16980
|
init_text();
|
|
16589
16981
|
init_status();
|
|
@@ -16680,11 +17072,11 @@ var init_verify = __esm({
|
|
|
16680
17072
|
});
|
|
16681
17073
|
|
|
16682
17074
|
// src/cli/run.ts
|
|
16683
|
-
import { execFileSync as
|
|
17075
|
+
import { execFileSync as execFileSync10, spawn, spawnSync as spawnSync4 } from "node:child_process";
|
|
16684
17076
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
16685
|
-
import { accessSync as accessSync3, constants as fsConstants, mkdtempSync as mkdtempSync4, readdirSync as readdirSync4, readFileSync as
|
|
17077
|
+
import { accessSync as accessSync3, constants as fsConstants, mkdtempSync as mkdtempSync4, readdirSync as readdirSync4, readFileSync as readFileSync18, readlinkSync as readlinkSync4, realpathSync as realpathSync8, rmSync as rmSync9, statSync as statSync5, writeFileSync as writeFileSync11 } from "node:fs";
|
|
16686
17078
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
16687
|
-
import { dirname as
|
|
17079
|
+
import { dirname as dirname10, join as join21, resolve as resolve10 } from "node:path";
|
|
16688
17080
|
function authoritativeRunLifecyclePlatform(platform = process.platform) {
|
|
16689
17081
|
return platform === "linux";
|
|
16690
17082
|
}
|
|
@@ -16721,7 +17113,7 @@ function writableByCaller2(path) {
|
|
|
16721
17113
|
return true;
|
|
16722
17114
|
} catch {
|
|
16723
17115
|
}
|
|
16724
|
-
const parent =
|
|
17116
|
+
const parent = dirname10(cur);
|
|
16725
17117
|
if (parent === cur) break;
|
|
16726
17118
|
cur = parent;
|
|
16727
17119
|
}
|
|
@@ -16760,9 +17152,9 @@ function trustedLinuxPython(candidates = DEFAULT_TRUSTED_PYTHON_CANDIDATES) {
|
|
|
16760
17152
|
};
|
|
16761
17153
|
}
|
|
16762
17154
|
function runAgentSupervised(argv, cwd, budgetSecs, linuxPythonCandidates, lifecycleTestMode, machineMode = false) {
|
|
16763
|
-
const
|
|
16764
|
-
const resultFile =
|
|
16765
|
-
const agentEnvFile =
|
|
17155
|
+
const stateDir2 = mkdtempSync4(join21(tmpdir5(), "tw-agent-supervisor-"));
|
|
17156
|
+
const resultFile = join21(stateDir2, "result.json");
|
|
17157
|
+
const agentEnvFile = join21(stateDir2, "agent-env.json");
|
|
16766
17158
|
try {
|
|
16767
17159
|
const linux = process.platform === "linux";
|
|
16768
17160
|
let executable;
|
|
@@ -16779,7 +17171,7 @@ function runAgentSupervised(argv, cwd, budgetSecs, linuxPythonCandidates, lifecy
|
|
|
16779
17171
|
failure: trustedPython.reason ?? "trusted Linux python3 interpreter is unavailable"
|
|
16780
17172
|
};
|
|
16781
17173
|
}
|
|
16782
|
-
|
|
17174
|
+
writeFileSync11(agentEnvFile, JSON.stringify(process.env), { mode: 384 });
|
|
16783
17175
|
executable = trustedPython.path;
|
|
16784
17176
|
args = [
|
|
16785
17177
|
"-I",
|
|
@@ -16836,7 +17228,7 @@ function runAgentSupervised(argv, cwd, budgetSecs, linuxPythonCandidates, lifecy
|
|
|
16836
17228
|
}
|
|
16837
17229
|
let state = {};
|
|
16838
17230
|
try {
|
|
16839
|
-
state = JSON.parse(
|
|
17231
|
+
state = JSON.parse(readFileSync18(resultFile, "utf8"));
|
|
16840
17232
|
} catch {
|
|
16841
17233
|
return {
|
|
16842
17234
|
exit: 1,
|
|
@@ -16861,13 +17253,13 @@ function runAgentSupervised(argv, cwd, budgetSecs, linuxPythonCandidates, lifecy
|
|
|
16861
17253
|
...state.failure ? { failure: state.failure } : {}
|
|
16862
17254
|
};
|
|
16863
17255
|
} finally {
|
|
16864
|
-
|
|
17256
|
+
rmSync9(stateDir2, { recursive: true, force: true });
|
|
16865
17257
|
}
|
|
16866
17258
|
}
|
|
16867
17259
|
function waitMs(ms) {
|
|
16868
17260
|
Atomics.wait(waitCell, 0, 0, ms);
|
|
16869
17261
|
}
|
|
16870
|
-
function
|
|
17262
|
+
function pidAlive4(pid) {
|
|
16871
17263
|
if (!pid || !Number.isInteger(pid) || pid <= 0) return false;
|
|
16872
17264
|
try {
|
|
16873
17265
|
process.kill(pid, 0);
|
|
@@ -16877,10 +17269,10 @@ function pidAlive3(pid) {
|
|
|
16877
17269
|
}
|
|
16878
17270
|
}
|
|
16879
17271
|
function observerExited(pid) {
|
|
16880
|
-
if (!
|
|
17272
|
+
if (!pidAlive4(pid)) return true;
|
|
16881
17273
|
if (process.platform !== "linux") return false;
|
|
16882
17274
|
try {
|
|
16883
|
-
const stat =
|
|
17275
|
+
const stat = readFileSync18(`/proc/${pid}/stat`, "utf8");
|
|
16884
17276
|
const state = stat.slice(stat.lastIndexOf(")") + 2).split(" ")[0];
|
|
16885
17277
|
return state === "Z" || state === "X";
|
|
16886
17278
|
} catch (e) {
|
|
@@ -16888,8 +17280,8 @@ function observerExited(pid) {
|
|
|
16888
17280
|
}
|
|
16889
17281
|
}
|
|
16890
17282
|
function supervisedObserverLog(cwd) {
|
|
16891
|
-
const gd = gitDir(cwd) ??
|
|
16892
|
-
return
|
|
17283
|
+
const gd = gitDir(cwd) ?? join21(cwd, ".git");
|
|
17284
|
+
return join21(
|
|
16893
17285
|
gd,
|
|
16894
17286
|
"tamperward",
|
|
16895
17287
|
`run-observer-${process.pid}-${Date.now()}-${randomBytes3(4).toString("hex")}.jsonl`
|
|
@@ -16997,12 +17389,12 @@ function finishObserverAdvisory(observer, write = out) {
|
|
|
16997
17389
|
const telemetry = stopObserverProcess(observer);
|
|
16998
17390
|
observerSummary(observer, telemetry, write);
|
|
16999
17391
|
}
|
|
17000
|
-
function
|
|
17001
|
-
return
|
|
17392
|
+
function git4(args, cwd) {
|
|
17393
|
+
return execFileSync10("git", args, { cwd, encoding: "utf8", maxBuffer: 1 << 28, env: trustedGitEnv() });
|
|
17002
17394
|
}
|
|
17003
17395
|
function nowTicks() {
|
|
17004
17396
|
try {
|
|
17005
|
-
return Math.floor(parseFloat(
|
|
17397
|
+
return Math.floor(parseFloat(readFileSync18("/proc/uptime", "utf8").split(" ")[0]) * 100);
|
|
17006
17398
|
} catch {
|
|
17007
17399
|
return Number.POSITIVE_INFINITY;
|
|
17008
17400
|
}
|
|
@@ -17025,7 +17417,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
|
|
|
17025
17417
|
const n2 = Number(pid);
|
|
17026
17418
|
if (n2 === process.pid) continue;
|
|
17027
17419
|
try {
|
|
17028
|
-
const stat =
|
|
17420
|
+
const stat = readFileSync18(`/proc/${pid}/stat`, "utf8");
|
|
17029
17421
|
const startTicks = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[19]);
|
|
17030
17422
|
if (!Number.isFinite(startTicks) || startTicks < spawnedAfterTicks) continue;
|
|
17031
17423
|
} catch {
|
|
@@ -17069,7 +17461,7 @@ function runEnvelope(opts) {
|
|
|
17069
17461
|
let base;
|
|
17070
17462
|
try {
|
|
17071
17463
|
const requestedBase = assertRev(opts.base ?? "HEAD");
|
|
17072
|
-
base =
|
|
17464
|
+
base = git4(["rev-parse", "--verify", `${requestedBase}^{commit}`], cwd).trim();
|
|
17073
17465
|
} catch {
|
|
17074
17466
|
err(`tamperward run: cannot resolve trusted base ${opts.base ?? "HEAD"} \u2014 failing closed.`);
|
|
17075
17467
|
return 2;
|
|
@@ -17091,7 +17483,7 @@ function runEnvelope(opts) {
|
|
|
17091
17483
|
return 2;
|
|
17092
17484
|
}
|
|
17093
17485
|
if (!opts.allowDirty) {
|
|
17094
|
-
const dirty =
|
|
17486
|
+
const dirty = git4(["status", "--porcelain"], cwd).trim();
|
|
17095
17487
|
if (dirty) {
|
|
17096
17488
|
err("tamperward run: working tree is dirty before the agent starts \u2014 the envelope cannot");
|
|
17097
17489
|
err("attribute changes. Commit or stash first, or pass --allow-dirty to own the risk.");
|
|
@@ -17196,7 +17588,7 @@ tamperward run \u2014 agent exit ${agentExit}; OBJECT_REWRITE \u2192 ENFORCEMENT
|
|
|
17196
17588
|
emitRunJson("OBJECT_REWRITE", 1);
|
|
17197
17589
|
return 1;
|
|
17198
17590
|
}
|
|
17199
|
-
const head =
|
|
17591
|
+
const head = git4(["rev-parse", "HEAD"], cwd).trim();
|
|
17200
17592
|
const isAncestor = spawnSync4("git", ["merge-base", "--is-ancestor", base, head], { cwd, env: trustedGitEnv() });
|
|
17201
17593
|
if (isAncestor.status !== 0) {
|
|
17202
17594
|
err(`tamperward run: HEAD ${head.slice(0, 10)} is not a descendant of the trusted base ${base.slice(0, 10)} \u2014`);
|
|
@@ -17597,9 +17989,9 @@ raise SystemExit(0)
|
|
|
17597
17989
|
});
|
|
17598
17990
|
|
|
17599
17991
|
// src/cli/doctor.ts
|
|
17600
|
-
import { execFileSync as
|
|
17601
|
-
import { existsSync as existsSync15, readFileSync as
|
|
17602
|
-
import { join as
|
|
17992
|
+
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
17993
|
+
import { existsSync as existsSync15, readFileSync as readFileSync19, readdirSync as readdirSync5 } from "node:fs";
|
|
17994
|
+
import { join as join22, resolve as resolve11 } from "node:path";
|
|
17603
17995
|
function lifecyclePlatformCheck(platform = process.platform, linuxPython = platform === "linux" ? trustedLinuxPython() : null) {
|
|
17604
17996
|
if (platform === "linux") {
|
|
17605
17997
|
if (linuxPython?.path) {
|
|
@@ -17657,7 +18049,7 @@ function pinsInFile(path) {
|
|
|
17657
18049
|
if (!existsSync15(path)) return [];
|
|
17658
18050
|
let src = "";
|
|
17659
18051
|
try {
|
|
17660
|
-
src =
|
|
18052
|
+
src = readFileSync19(path, "utf8");
|
|
17661
18053
|
} catch {
|
|
17662
18054
|
return [];
|
|
17663
18055
|
}
|
|
@@ -17684,7 +18076,7 @@ function workflowPermissionCheck(cwd, workflowRels = [".github/workflows/tamperw
|
|
|
17684
18076
|
}
|
|
17685
18077
|
let doc;
|
|
17686
18078
|
try {
|
|
17687
|
-
doc = yaml.parse(
|
|
18079
|
+
doc = yaml.parse(readFileSync19(path, "utf8"));
|
|
17688
18080
|
} catch (e) {
|
|
17689
18081
|
broken.push(`${rel} is not valid YAML (${e instanceof Error ? e.message : String(e)})`);
|
|
17690
18082
|
continue;
|
|
@@ -17797,9 +18189,9 @@ function collectLocalPosture(cwd, policy, authorityWorkflows, explicitWorkflow =
|
|
|
17797
18189
|
addWiring("codeowners", "codeowners", true);
|
|
17798
18190
|
checks.push(workflowPermissionCheck(cwd, authorityWorkflows));
|
|
17799
18191
|
const pins = [
|
|
17800
|
-
...pinsInFile(
|
|
17801
|
-
...pinsInFile(
|
|
17802
|
-
...pinsInFile(
|
|
18192
|
+
...pinsInFile(join22(cwd, ".claude", "settings.json")),
|
|
18193
|
+
...pinsInFile(join22(cwd, ".git", "hooks", "pre-commit")),
|
|
18194
|
+
...pinsInFile(join22(cwd, ".husky", "pre-commit"))
|
|
17803
18195
|
];
|
|
17804
18196
|
const below = pins.filter((pin) => (compareVersions(pin, TW_VERSION) ?? 0) < 0);
|
|
17805
18197
|
const above = pins.filter((pin) => (compareVersions(TW_VERSION, pin) ?? 0) < 0);
|
|
@@ -17917,7 +18309,7 @@ function evaluateGitHubProtection(snapshot2, requiredCheck = "tamperward", requi
|
|
|
17917
18309
|
}
|
|
17918
18310
|
function gitText(cwd, args) {
|
|
17919
18311
|
try {
|
|
17920
|
-
return
|
|
18312
|
+
return execFileSync11("git", args, {
|
|
17921
18313
|
cwd,
|
|
17922
18314
|
encoding: "utf8",
|
|
17923
18315
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -17951,7 +18343,7 @@ function githubApi(cwd, endpoint) {
|
|
|
17951
18343
|
const token = process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN ?? "";
|
|
17952
18344
|
const invocation = githubApiInvocation(endpoint, token);
|
|
17953
18345
|
try {
|
|
17954
|
-
const stdout =
|
|
18346
|
+
const stdout = execFileSync11(
|
|
17955
18347
|
invocation.executable,
|
|
17956
18348
|
invocation.args,
|
|
17957
18349
|
{
|
|
@@ -18095,7 +18487,7 @@ function diagnose(opts = {}) {
|
|
|
18095
18487
|
`${workflowDirRel}: could not enumerate workflows (${e instanceof Error ? e.message : String(e)})`
|
|
18096
18488
|
);
|
|
18097
18489
|
}
|
|
18098
|
-
for (const name of entries) workflowRels.push(
|
|
18490
|
+
for (const name of entries) workflowRels.push(join22(workflowDirRel, name));
|
|
18099
18491
|
}
|
|
18100
18492
|
const requiredSecs = requiredVerifierAuthoritySeconds(policy.verify.budget);
|
|
18101
18493
|
const requiredMinutes = Math.ceil(requiredSecs / 60);
|
|
@@ -18108,7 +18500,7 @@ function diagnose(opts = {}) {
|
|
|
18108
18500
|
}
|
|
18109
18501
|
let doc;
|
|
18110
18502
|
try {
|
|
18111
|
-
doc = yaml.parse(
|
|
18503
|
+
doc = yaml.parse(readFileSync19(workflowPath, "utf8"));
|
|
18112
18504
|
} catch (e) {
|
|
18113
18505
|
return fail(
|
|
18114
18506
|
"ci-verifier",
|
|
@@ -18282,19 +18674,153 @@ var init_doctor = __esm({
|
|
|
18282
18674
|
}
|
|
18283
18675
|
});
|
|
18284
18676
|
|
|
18677
|
+
// src/cli/status.ts
|
|
18678
|
+
function authorityFromChecks(checks) {
|
|
18679
|
+
if (checks.some((c) => c.state === "BROKEN")) return "BROKEN";
|
|
18680
|
+
if (checks.some((c) => c.state === "WARN")) return "PARTIAL";
|
|
18681
|
+
return "ACTIVE";
|
|
18682
|
+
}
|
|
18683
|
+
function interventionFromCheck(check2) {
|
|
18684
|
+
if (!check2) return { state: "UNKNOWN", detail: "agent hook wiring could not be evaluated" };
|
|
18685
|
+
const map = {
|
|
18686
|
+
OK: "ACTIVE",
|
|
18687
|
+
WARN: "PARTIAL",
|
|
18688
|
+
BROKEN: "INACTIVE"
|
|
18689
|
+
};
|
|
18690
|
+
return { state: map[check2.state], detail: check2.detail };
|
|
18691
|
+
}
|
|
18692
|
+
function buildStatusModel(cwd) {
|
|
18693
|
+
let authority;
|
|
18694
|
+
let intervention;
|
|
18695
|
+
try {
|
|
18696
|
+
const outcome = diagnose({ cwd });
|
|
18697
|
+
authority = { state: authorityFromChecks(outcome.checks) };
|
|
18698
|
+
const summary = outcome.checks.filter((c) => c.state !== "OK").map((c) => `${c.id}: ${c.state}`);
|
|
18699
|
+
if (summary.length) authority.detail = summary.join("; ");
|
|
18700
|
+
intervention = interventionFromCheck(outcome.checks.find((c) => c.id === "claude-hooks"));
|
|
18701
|
+
} catch (e) {
|
|
18702
|
+
const detail = e instanceof Error ? e.message : String(e);
|
|
18703
|
+
authority = { state: "UNKNOWN", detail };
|
|
18704
|
+
intervention = { state: "UNKNOWN", detail };
|
|
18705
|
+
}
|
|
18706
|
+
const verification = evaluateVerificationState(cwd);
|
|
18707
|
+
const agent = intervention.state === "ACTIVE" || intervention.state === "PARTIAL" ? "claude-code" : "none";
|
|
18708
|
+
return { authority, intervention, verification, runtime: { agent } };
|
|
18709
|
+
}
|
|
18710
|
+
function shortId(id) {
|
|
18711
|
+
return id ? `${id.slice(0, 10)}...` : void 0;
|
|
18712
|
+
}
|
|
18713
|
+
function renderStatus(model, colour) {
|
|
18714
|
+
const v = model.verification;
|
|
18715
|
+
const lines = ["TamperWard", ""];
|
|
18716
|
+
lines.push(statusLine(AUTHORITY_SEVERITY[model.authority.state], "Authority", model.authority.state, colour));
|
|
18717
|
+
lines.push(statusLine(INTERVENTION_SEVERITY[model.intervention.state], "Intervention", model.intervention.state, colour));
|
|
18718
|
+
lines.push(statusLine(VERIFICATION_SEVERITY[v.state], "Verification", v.state, colour));
|
|
18719
|
+
const rows = [];
|
|
18720
|
+
const record = v.record;
|
|
18721
|
+
if (record) {
|
|
18722
|
+
const verifiedTree = shortId(record.binding.tree);
|
|
18723
|
+
if (verifiedTree) rows.push(["Verified tree", verifiedTree]);
|
|
18724
|
+
const baseShort = shortId(v.base ?? record.binding.base);
|
|
18725
|
+
if (baseShort) rows.push(["Base", `${record.inputs.base_ref}@${baseShort}`]);
|
|
18726
|
+
if (v.verifier_command) rows.push(["Verifier", v.verifier_command]);
|
|
18727
|
+
}
|
|
18728
|
+
rows.push(["Runtime", model.runtime.agent]);
|
|
18729
|
+
if (v.state === "STALE" && v.reason) rows.push(["Reason", v.reason]);
|
|
18730
|
+
else if (v.state === "BROKEN") rows.push(["Reason", v.detail ?? v.reason ?? "authority wiring is invalid"]);
|
|
18731
|
+
else if (v.state === "VERIFYING") rows.push(["Reason", v.reason ?? "a verification is in progress"]);
|
|
18732
|
+
else if (v.state === "UNVERIFIED") rows.push(["Next", "run `tamperward verify` to establish a verified baseline"]);
|
|
18733
|
+
if (rows.length) {
|
|
18734
|
+
lines.push("");
|
|
18735
|
+
const width = Math.max(...rows.map(([k]) => k.length));
|
|
18736
|
+
for (const [k, val] of rows) lines.push(`${k.padEnd(width)} ${val}`);
|
|
18737
|
+
}
|
|
18738
|
+
return lines.join("\n") + "\n";
|
|
18739
|
+
}
|
|
18740
|
+
function statusDocument(model) {
|
|
18741
|
+
const v = model.verification;
|
|
18742
|
+
const verification = { state: v.state };
|
|
18743
|
+
if (v.reason) verification.reason = v.reason;
|
|
18744
|
+
if (v.changed_input) verification.changed_input = v.changed_input;
|
|
18745
|
+
if (v.detail) verification.detail = v.detail;
|
|
18746
|
+
if (v.verified_at) verification.verified_at = v.verified_at;
|
|
18747
|
+
if (v.base) verification.base = v.base;
|
|
18748
|
+
if (v.head) verification.head = v.head;
|
|
18749
|
+
if (v.verifier_command) verification.verifier_command = v.verifier_command;
|
|
18750
|
+
if (v.record) {
|
|
18751
|
+
verification.tree = v.record.binding.tree;
|
|
18752
|
+
verification.binding = { ...v.record.binding };
|
|
18753
|
+
}
|
|
18754
|
+
return machineOutput({
|
|
18755
|
+
command: "status",
|
|
18756
|
+
authority: model.authority,
|
|
18757
|
+
intervention: model.intervention,
|
|
18758
|
+
verification,
|
|
18759
|
+
runtime: model.runtime
|
|
18760
|
+
});
|
|
18761
|
+
}
|
|
18762
|
+
function runStatus(opts = {}) {
|
|
18763
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
18764
|
+
if (!repoContext(cwd)) {
|
|
18765
|
+
const why = outsideRepository(cwd) ?? "cwd is not inside a repository the gate can read";
|
|
18766
|
+
process.stderr.write(`tamperward: status needs a git repository (${why})
|
|
18767
|
+
`);
|
|
18768
|
+
return 2;
|
|
18769
|
+
}
|
|
18770
|
+
const model = buildStatusModel(cwd);
|
|
18771
|
+
if (opts.json) {
|
|
18772
|
+
process.stdout.write(JSON.stringify(statusDocument(model)) + "\n");
|
|
18773
|
+
} else {
|
|
18774
|
+
process.stdout.write(renderStatus(model, colourEnabled(process.env, process.stdout)));
|
|
18775
|
+
}
|
|
18776
|
+
return 0;
|
|
18777
|
+
}
|
|
18778
|
+
var AUTHORITY_SEVERITY, INTERVENTION_SEVERITY, VERIFICATION_SEVERITY;
|
|
18779
|
+
var init_status2 = __esm({
|
|
18780
|
+
"src/cli/status.ts"() {
|
|
18781
|
+
"use strict";
|
|
18782
|
+
init_machine_output();
|
|
18783
|
+
init_repo_context();
|
|
18784
|
+
init_repo_context();
|
|
18785
|
+
init_doctor();
|
|
18786
|
+
init_verification_state();
|
|
18787
|
+
init_text();
|
|
18788
|
+
init_status();
|
|
18789
|
+
AUTHORITY_SEVERITY = {
|
|
18790
|
+
ACTIVE: "ok",
|
|
18791
|
+
PARTIAL: "warn",
|
|
18792
|
+
BROKEN: "bad",
|
|
18793
|
+
UNKNOWN: "info"
|
|
18794
|
+
};
|
|
18795
|
+
INTERVENTION_SEVERITY = {
|
|
18796
|
+
ACTIVE: "ok",
|
|
18797
|
+
PARTIAL: "warn",
|
|
18798
|
+
INACTIVE: "bad",
|
|
18799
|
+
UNKNOWN: "info"
|
|
18800
|
+
};
|
|
18801
|
+
VERIFICATION_SEVERITY = {
|
|
18802
|
+
CURRENT: "ok",
|
|
18803
|
+
STALE: "warn",
|
|
18804
|
+
VERIFYING: "info",
|
|
18805
|
+
BROKEN: "bad",
|
|
18806
|
+
UNVERIFIED: "info"
|
|
18807
|
+
};
|
|
18808
|
+
}
|
|
18809
|
+
});
|
|
18810
|
+
|
|
18285
18811
|
// src/cli/trace-verify.ts
|
|
18286
|
-
import { execFileSync as
|
|
18812
|
+
import { execFileSync as execFileSync12, spawnSync as spawnSync5 } from "node:child_process";
|
|
18287
18813
|
import {
|
|
18288
18814
|
existsSync as existsSync16,
|
|
18289
18815
|
mkdtempSync as mkdtempSync5,
|
|
18290
|
-
readFileSync as
|
|
18816
|
+
readFileSync as readFileSync20,
|
|
18291
18817
|
readdirSync as readdirSync6,
|
|
18292
18818
|
realpathSync as realpathSync9,
|
|
18293
|
-
rmSync as
|
|
18819
|
+
rmSync as rmSync10,
|
|
18294
18820
|
symlinkSync as symlinkSync2
|
|
18295
18821
|
} from "node:fs";
|
|
18296
18822
|
import { tmpdir as tmpdir6 } from "node:os";
|
|
18297
|
-
import { isAbsolute as isAbsolute12, join as
|
|
18823
|
+
import { isAbsolute as isAbsolute12, join as join23, relative as relative7, resolve as resolve12, sep as sep5 } from "node:path";
|
|
18298
18824
|
function unescapeStraceString(s) {
|
|
18299
18825
|
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, "\\");
|
|
18300
18826
|
}
|
|
@@ -18506,8 +19032,8 @@ function summarizeTraceRuns(opts) {
|
|
|
18506
19032
|
unresolved_accesses
|
|
18507
19033
|
};
|
|
18508
19034
|
}
|
|
18509
|
-
function
|
|
18510
|
-
return
|
|
19035
|
+
function git5(args, cwd) {
|
|
19036
|
+
return execFileSync12("git", args, {
|
|
18511
19037
|
cwd,
|
|
18512
19038
|
encoding: "utf8",
|
|
18513
19039
|
maxBuffer: 256 * 1024 * 1024,
|
|
@@ -18520,7 +19046,7 @@ function commandExists(command) {
|
|
|
18520
19046
|
return !r.error && r.status === 0;
|
|
18521
19047
|
}
|
|
18522
19048
|
function trackedAt(base, cwd) {
|
|
18523
|
-
return
|
|
19049
|
+
return git5(["ls-tree", "-r", "-z", "--name-only", base], cwd).split("\0").filter(Boolean);
|
|
18524
19050
|
}
|
|
18525
19051
|
function materializeBase(base, cwd, dest) {
|
|
18526
19052
|
const archive = spawnSync5("git", ["archive", "--format=tar", base], {
|
|
@@ -18542,8 +19068,8 @@ function materializeBase(base, cwd, dest) {
|
|
|
18542
19068
|
`tar extraction failed${untar.stderr?.length ? `: ${String(untar.stderr).trim()}` : ""}`
|
|
18543
19069
|
);
|
|
18544
19070
|
}
|
|
18545
|
-
const hostModules =
|
|
18546
|
-
const tracedModules =
|
|
19071
|
+
const hostModules = join23(cwd, "node_modules");
|
|
19072
|
+
const tracedModules = join23(dest, "node_modules");
|
|
18547
19073
|
if (existsSync16(hostModules) && !existsSync16(tracedModules)) {
|
|
18548
19074
|
symlinkSync2(resolve12(hostModules), tracedModules, "dir");
|
|
18549
19075
|
}
|
|
@@ -18551,7 +19077,7 @@ function materializeBase(base, cwd, dest) {
|
|
|
18551
19077
|
function traceFiles(prefix) {
|
|
18552
19078
|
const dir = resolve12(prefix, "..");
|
|
18553
19079
|
const base = prefix.split(sep5).at(-1) ?? prefix;
|
|
18554
|
-
return readdirSync6(dir).filter((name) => name === base || name.startsWith(base + ".")).map((name) =>
|
|
19080
|
+
return readdirSync6(dir).filter((name) => name === base || name.startsWith(base + ".")).map((name) => join23(dir, name)).sort();
|
|
18555
19081
|
}
|
|
18556
19082
|
function rewriteTraceRoot(accesses, actualRoot) {
|
|
18557
19083
|
return accesses.map((item) => {
|
|
@@ -18566,13 +19092,13 @@ function rewriteTraceRoot(accesses, actualRoot) {
|
|
|
18566
19092
|
if (!inside3(actualRoot, real)) return { ...item, path: real };
|
|
18567
19093
|
} catch {
|
|
18568
19094
|
}
|
|
18569
|
-
return { ...item, path:
|
|
19095
|
+
return { ...item, path: join23(TRACE_ROOT, ...rel.split("/")) };
|
|
18570
19096
|
});
|
|
18571
19097
|
}
|
|
18572
19098
|
function straceOnce(root, command, budget) {
|
|
18573
|
-
const traceDir = mkdtempSync5(
|
|
19099
|
+
const traceDir = mkdtempSync5(join23(tmpdir6(), "tw-trace-log-"));
|
|
18574
19100
|
try {
|
|
18575
|
-
const prefix =
|
|
19101
|
+
const prefix = join23(traceDir, "trace");
|
|
18576
19102
|
const traced = spawnSync5(
|
|
18577
19103
|
"strace",
|
|
18578
19104
|
[
|
|
@@ -18601,7 +19127,7 @@ function straceOnce(root, command, budget) {
|
|
|
18601
19127
|
}
|
|
18602
19128
|
);
|
|
18603
19129
|
const logs = traced.error ? [] : traceFiles(prefix);
|
|
18604
|
-
const accesses = logs.flatMap((path) => parseStraceFileAccess(
|
|
19130
|
+
const accesses = logs.flatMap((path) => parseStraceFileAccess(readFileSync20(path, "utf8"), TRACE_ROOT));
|
|
18605
19131
|
const raw = {
|
|
18606
19132
|
spawnError: traced.error ? traced.error.message : null,
|
|
18607
19133
|
status: traced.status,
|
|
@@ -18612,26 +19138,26 @@ function straceOnce(root, command, budget) {
|
|
|
18612
19138
|
};
|
|
18613
19139
|
return { raw, accesses };
|
|
18614
19140
|
} finally {
|
|
18615
|
-
|
|
19141
|
+
rmSync10(traceDir, { recursive: true, force: true });
|
|
18616
19142
|
}
|
|
18617
19143
|
}
|
|
18618
19144
|
function runOneTrace(cwd, base, command, budget) {
|
|
18619
|
-
const root = mkdtempSync5(
|
|
19145
|
+
const root = mkdtempSync5(join23(tmpdir6(), "tw-trace-base-"));
|
|
18620
19146
|
try {
|
|
18621
19147
|
materializeBase(base, cwd, root);
|
|
18622
19148
|
const { raw, accesses } = straceOnce(root, command, budget);
|
|
18623
19149
|
return { outcome: classifyTraceRun(raw), accesses: rewriteTraceRoot(accesses, root) };
|
|
18624
19150
|
} finally {
|
|
18625
|
-
|
|
19151
|
+
rmSync10(root, { recursive: true, force: true });
|
|
18626
19152
|
}
|
|
18627
19153
|
}
|
|
18628
19154
|
function tracerPreflight(budget = 10) {
|
|
18629
|
-
const probeRoot = mkdtempSync5(
|
|
19155
|
+
const probeRoot = mkdtempSync5(join23(tmpdir6(), "tw-trace-probe-"));
|
|
18630
19156
|
try {
|
|
18631
19157
|
const { raw } = straceOnce(probeRoot, "true", Math.max(1, Math.min(budget, 10)));
|
|
18632
19158
|
return classifyTraceRun(raw);
|
|
18633
19159
|
} finally {
|
|
18634
|
-
|
|
19160
|
+
rmSync10(probeRoot, { recursive: true, force: true });
|
|
18635
19161
|
}
|
|
18636
19162
|
}
|
|
18637
19163
|
function renderText2(report2) {
|
|
@@ -18696,7 +19222,7 @@ function runTraceVerify(opts = {}) {
|
|
|
18696
19222
|
let base;
|
|
18697
19223
|
try {
|
|
18698
19224
|
const baseArg = assertRev(requestedBase);
|
|
18699
|
-
base =
|
|
19225
|
+
base = git5(["rev-parse", "--verify", `${baseArg}^{commit}`], cwd).trim();
|
|
18700
19226
|
} catch (e) {
|
|
18701
19227
|
process.stderr.write(`tamperward trace-verify: cannot resolve trusted base ${JSON.stringify(requestedBase)} (${e instanceof Error ? e.message : String(e)}).
|
|
18702
19228
|
`);
|
|
@@ -18833,26 +19359,26 @@ var init_trace_verify = __esm({
|
|
|
18833
19359
|
});
|
|
18834
19360
|
|
|
18835
19361
|
// src/runtimes.ts
|
|
18836
|
-
import { existsSync as existsSync17, readFileSync as
|
|
18837
|
-
import { join as
|
|
18838
|
-
function
|
|
19362
|
+
import { existsSync as existsSync17, readFileSync as readFileSync21 } from "node:fs";
|
|
19363
|
+
import { join as join24 } from "node:path";
|
|
19364
|
+
function isRecord5(x) {
|
|
18839
19365
|
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
18840
19366
|
}
|
|
18841
19367
|
function hooksBeyondTamperward(hooks) {
|
|
18842
|
-
if (!
|
|
19368
|
+
if (!isRecord5(hooks)) return hooks != null;
|
|
18843
19369
|
for (const entries of Object.values(hooks)) {
|
|
18844
19370
|
if (!Array.isArray(entries)) {
|
|
18845
19371
|
if (entries != null) return true;
|
|
18846
19372
|
continue;
|
|
18847
19373
|
}
|
|
18848
19374
|
for (const entry of entries) {
|
|
18849
|
-
const hs =
|
|
19375
|
+
const hs = isRecord5(entry) ? entry.hooks : null;
|
|
18850
19376
|
if (!Array.isArray(hs)) {
|
|
18851
19377
|
if (hs != null) return true;
|
|
18852
19378
|
continue;
|
|
18853
19379
|
}
|
|
18854
19380
|
for (const h of hs) {
|
|
18855
|
-
const cmd =
|
|
19381
|
+
const cmd = isRecord5(h) ? String(h.command ?? "") : "";
|
|
18856
19382
|
if (!/tamperward/.test(cmd)) return true;
|
|
18857
19383
|
}
|
|
18858
19384
|
}
|
|
@@ -18860,11 +19386,11 @@ function hooksBeyondTamperward(hooks) {
|
|
|
18860
19386
|
return false;
|
|
18861
19387
|
}
|
|
18862
19388
|
function claudeSettingsBeyondTamperward(cwd) {
|
|
18863
|
-
const path =
|
|
19389
|
+
const path = join24(cwd, ".claude", "settings.json");
|
|
18864
19390
|
let raw;
|
|
18865
19391
|
try {
|
|
18866
19392
|
if (!existsSync17(path)) return false;
|
|
18867
|
-
raw =
|
|
19393
|
+
raw = readFileSync21(path, "utf8");
|
|
18868
19394
|
} catch {
|
|
18869
19395
|
return false;
|
|
18870
19396
|
}
|
|
@@ -18874,7 +19400,7 @@ function claudeSettingsBeyondTamperward(cwd) {
|
|
|
18874
19400
|
} catch {
|
|
18875
19401
|
return raw.trim().length > 0;
|
|
18876
19402
|
}
|
|
18877
|
-
if (!
|
|
19403
|
+
if (!isRecord5(parsed)) return raw.trim().length > 0;
|
|
18878
19404
|
for (const [key2, value] of Object.entries(parsed)) {
|
|
18879
19405
|
if (key2 === "disableAllHooks") continue;
|
|
18880
19406
|
if (key2 === "hooks") {
|
|
@@ -18890,7 +19416,7 @@ function detectRuntimes(cwd) {
|
|
|
18890
19416
|
for (const rt of KNOWN_RUNTIMES) {
|
|
18891
19417
|
const marker = rt.markers.find((m) => {
|
|
18892
19418
|
try {
|
|
18893
|
-
return existsSync17(
|
|
19419
|
+
return existsSync17(join24(cwd, m));
|
|
18894
19420
|
} catch {
|
|
18895
19421
|
return false;
|
|
18896
19422
|
}
|
|
@@ -18970,10 +19496,10 @@ var init_runtimes = __esm({
|
|
|
18970
19496
|
});
|
|
18971
19497
|
|
|
18972
19498
|
// src/cli/onboard.ts
|
|
18973
|
-
import { execFileSync as
|
|
18974
|
-
import { lstatSync as lstatSync10, mkdtempSync as mkdtempSync6, readFileSync as
|
|
19499
|
+
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
19500
|
+
import { lstatSync as lstatSync10, mkdtempSync as mkdtempSync6, readFileSync as readFileSync22, realpathSync as realpathSync10, rmSync as rmSync11 } from "node:fs";
|
|
18975
19501
|
import { tmpdir as tmpdir7 } from "node:os";
|
|
18976
|
-
import { join as
|
|
19502
|
+
import { join as join25, relative as relative8, resolve as resolve13, sep as sep6 } from "node:path";
|
|
18977
19503
|
import { createInterface } from "node:readline";
|
|
18978
19504
|
import { parseDocument } from "yaml";
|
|
18979
19505
|
function readlineAsker(input, output) {
|
|
@@ -19017,9 +19543,9 @@ function platformLabel(platform) {
|
|
|
19017
19543
|
if (platform === "linux") return "Linux";
|
|
19018
19544
|
return platform;
|
|
19019
19545
|
}
|
|
19020
|
-
function
|
|
19546
|
+
function git6(cwd, args) {
|
|
19021
19547
|
try {
|
|
19022
|
-
return
|
|
19548
|
+
return execFileSync13("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
19023
19549
|
} catch {
|
|
19024
19550
|
return null;
|
|
19025
19551
|
}
|
|
@@ -19038,13 +19564,13 @@ function explainVerdict(v, code2) {
|
|
|
19038
19564
|
}
|
|
19039
19565
|
}
|
|
19040
19566
|
function writeVerifyCommand(cwd, command) {
|
|
19041
|
-
const path =
|
|
19567
|
+
const path = join25(cwd, POLICY_FILE);
|
|
19042
19568
|
const kind = writeTargetKind(path);
|
|
19043
19569
|
if (kind === "symlink" || kind === "irregular") {
|
|
19044
19570
|
return `${POLICY_FILE} is not a regular file; refusing to follow or replace a symlink or special file`;
|
|
19045
19571
|
}
|
|
19046
19572
|
const mode = existingMode(path, 420);
|
|
19047
|
-
const original = kind === "file" ?
|
|
19573
|
+
const original = kind === "file" ? readFileSync22(path, "utf8") : null;
|
|
19048
19574
|
let next;
|
|
19049
19575
|
try {
|
|
19050
19576
|
const doc = parseDocument(original && original.trim() ? original : "version: 1\n");
|
|
@@ -19061,7 +19587,7 @@ function writeVerifyCommand(cwd, command) {
|
|
|
19061
19587
|
if (loaded2.verify?.command !== command) throw new Error("verify.command did not round-trip");
|
|
19062
19588
|
return null;
|
|
19063
19589
|
} catch (e) {
|
|
19064
|
-
if (original === null)
|
|
19590
|
+
if (original === null) rmSync11(path, { force: true });
|
|
19065
19591
|
else atomicReplaceFile(path, original, mode);
|
|
19066
19592
|
return `the updated ${POLICY_FILE} does not load (${errorMessage(e)}); restored the previous file`;
|
|
19067
19593
|
}
|
|
@@ -19096,7 +19622,7 @@ async function runOnboard(opts, io = {}) {
|
|
|
19096
19622
|
rawErr("or run `tamperward init` for the deterministic wiring step only.");
|
|
19097
19623
|
return 2;
|
|
19098
19624
|
}
|
|
19099
|
-
const rootText =
|
|
19625
|
+
const rootText = git6(requestedCwd, ["rev-parse", "--show-toplevel"])?.trim() ?? "";
|
|
19100
19626
|
if (!rootText) {
|
|
19101
19627
|
fail(requestedCwd + " is not inside a Git repository. Run `git init` first.");
|
|
19102
19628
|
return 2;
|
|
@@ -19162,8 +19688,8 @@ async function runOnboard(opts, io = {}) {
|
|
|
19162
19688
|
} else {
|
|
19163
19689
|
status("LIMITED", platformLabel(platform) + ": check works here; local verify and `run` are unavailable. Use the container verifier for final verification.", "warn");
|
|
19164
19690
|
}
|
|
19165
|
-
const head =
|
|
19166
|
-
const treeStatus =
|
|
19691
|
+
const head = git6(cwd, ["rev-parse", "--verify", "--quiet", "HEAD^{commit}"])?.trim() ?? null;
|
|
19692
|
+
const treeStatus = git6(cwd, ["status", "--porcelain", "--untracked-files=all"]) ?? "";
|
|
19167
19693
|
const dirty = treeStatus.split("\n").filter(Boolean).map((line2) => line2.slice(3).split(" -> ").at(-1) ?? "");
|
|
19168
19694
|
if (!head) {
|
|
19169
19695
|
const count = dirty.length;
|
|
@@ -19411,7 +19937,7 @@ function postureOf(outcome) {
|
|
|
19411
19937
|
return "READY";
|
|
19412
19938
|
}
|
|
19413
19939
|
function runDemo(cwd, head, runners, out3) {
|
|
19414
|
-
const files = (
|
|
19940
|
+
const files = (git6(cwd, ["ls-tree", "-r", "-z", head]) ?? "").split("\0").filter(Boolean).flatMap((row) => {
|
|
19415
19941
|
const tab = row.indexOf(" ");
|
|
19416
19942
|
if (tab < 0) return [];
|
|
19417
19943
|
const mode = row.slice(0, tab).split(" ")[0];
|
|
@@ -19420,7 +19946,7 @@ function runDemo(cwd, head, runners, out3) {
|
|
|
19420
19946
|
});
|
|
19421
19947
|
let target = null;
|
|
19422
19948
|
for (const path of files) {
|
|
19423
|
-
const before2 =
|
|
19949
|
+
const before2 = git6(cwd, ["show", `${head}:${path}`]);
|
|
19424
19950
|
if (before2 === null || !JS_TEST_BLOCK.test(before2)) continue;
|
|
19425
19951
|
target = { path, before: before2, after: before2.replace(JS_TEST_BLOCK, "$1$2.skip$3") };
|
|
19426
19952
|
break;
|
|
@@ -19431,13 +19957,13 @@ function runDemo(cwd, head, runners, out3) {
|
|
|
19431
19957
|
}
|
|
19432
19958
|
const before = treeFingerprint(cwd);
|
|
19433
19959
|
out3(`working tree fingerprint before: ${before.slice(0, 16)}`);
|
|
19434
|
-
const wt = mkdtempSync6(
|
|
19960
|
+
const wt = mkdtempSync6(join25(tmpdir7(), "tw-onboard-demo-"));
|
|
19435
19961
|
try {
|
|
19436
|
-
if (
|
|
19962
|
+
if (git6(cwd, ["worktree", "add", "--detach", wt, head]) === null) {
|
|
19437
19963
|
out3("Demo skipped: could not create a temporary worktree (git worktree add failed).");
|
|
19438
19964
|
return;
|
|
19439
19965
|
}
|
|
19440
|
-
const targetPath =
|
|
19966
|
+
const targetPath = join25(wt, target.path);
|
|
19441
19967
|
const targetStat = lstatSync10(targetPath, { throwIfNoEntry: false });
|
|
19442
19968
|
if (!targetStat || !targetStat.isFile() || targetStat.isSymbolicLink()) {
|
|
19443
19969
|
out3("Demo skipped: the selected test is not a regular file in the disposable worktree.");
|
|
@@ -19466,9 +19992,9 @@ function runDemo(cwd, head, runners, out3) {
|
|
|
19466
19992
|
out3("");
|
|
19467
19993
|
out3(`check exited ${code2}: ${code2 === 1 ? "a blocking finding, which is what the hook, pre-commit and CI would all report" : "no blocking finding was reported"}.`);
|
|
19468
19994
|
} finally {
|
|
19469
|
-
|
|
19470
|
-
|
|
19471
|
-
|
|
19995
|
+
git6(cwd, ["worktree", "remove", "--force", wt]);
|
|
19996
|
+
git6(cwd, ["worktree", "prune"]);
|
|
19997
|
+
rmSync11(wt, { recursive: true, force: true });
|
|
19472
19998
|
}
|
|
19473
19999
|
const after = treeFingerprint(cwd);
|
|
19474
20000
|
out3(`working tree fingerprint after: ${after.slice(0, 16)}`);
|
|
@@ -19655,9 +20181,9 @@ var init_capture = __esm({
|
|
|
19655
20181
|
});
|
|
19656
20182
|
|
|
19657
20183
|
// src/research/manifest.ts
|
|
19658
|
-
import { createHash as
|
|
19659
|
-
import { readFileSync as
|
|
19660
|
-
import { dirname as
|
|
20184
|
+
import { createHash as createHash14 } from "node:crypto";
|
|
20185
|
+
import { readFileSync as readFileSync23 } from "node:fs";
|
|
20186
|
+
import { dirname as dirname11, resolve as resolve15 } from "node:path";
|
|
19661
20187
|
function taskFrom(raw, index, manifestDir) {
|
|
19662
20188
|
const where = `task #${index + 1}`;
|
|
19663
20189
|
if (!isRecord(raw)) throw new ResearchError(`task manifest: ${where} is not an object`);
|
|
@@ -19690,7 +20216,7 @@ function readManifest(path) {
|
|
|
19690
20216
|
const abs = resolve15(path);
|
|
19691
20217
|
let bytes;
|
|
19692
20218
|
try {
|
|
19693
|
-
bytes =
|
|
20219
|
+
bytes = readFileSync23(abs);
|
|
19694
20220
|
} catch (e) {
|
|
19695
20221
|
throw new ResearchError(`cannot read task manifest ${abs}: ${e instanceof Error ? e.message : String(e)}`);
|
|
19696
20222
|
}
|
|
@@ -19707,13 +20233,13 @@ function readManifest(path) {
|
|
|
19707
20233
|
if (!Array.isArray(raw.tasks) || raw.tasks.length === 0) {
|
|
19708
20234
|
throw new ResearchError(`task manifest ${abs}: no tasks (tasks must be a non-empty array)`);
|
|
19709
20235
|
}
|
|
19710
|
-
const tasks = raw.tasks.map((t, i) => taskFrom(t, i,
|
|
20236
|
+
const tasks = raw.tasks.map((t, i) => taskFrom(t, i, dirname11(abs)));
|
|
19711
20237
|
const seen = /* @__PURE__ */ new Set();
|
|
19712
20238
|
for (const t of tasks) {
|
|
19713
20239
|
if (seen.has(t.id)) throw new ResearchError(`task manifest ${abs}: duplicate task id "${t.id}"`);
|
|
19714
20240
|
seen.add(t.id);
|
|
19715
20241
|
}
|
|
19716
|
-
return { path: abs, sha256:
|
|
20242
|
+
return { path: abs, sha256: createHash14("sha256").update(bytes).digest("hex"), tasks };
|
|
19717
20243
|
}
|
|
19718
20244
|
var MANIFEST_VERSION;
|
|
19719
20245
|
var init_manifest = __esm({
|
|
@@ -19987,22 +20513,22 @@ var init_record = __esm({
|
|
|
19987
20513
|
|
|
19988
20514
|
// src/research/run.ts
|
|
19989
20515
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
19990
|
-
import { execFileSync as
|
|
19991
|
-
import { closeSync as closeSync3, existsSync as existsSync18, linkSync, mkdirSync as
|
|
20516
|
+
import { execFileSync as execFileSync14 } from "node:child_process";
|
|
20517
|
+
import { closeSync as closeSync3, existsSync as existsSync18, linkSync, mkdirSync as mkdirSync10, openSync as openSync3, readFileSync as readFileSync24, renameSync as renameSync4, rmSync as rmSync12, unlinkSync, writeFileSync as writeFileSync12, writeSync } from "node:fs";
|
|
19992
20518
|
import { hostname } from "node:os";
|
|
19993
|
-
import { join as
|
|
19994
|
-
function
|
|
19995
|
-
return
|
|
20519
|
+
import { join as join26, resolve as resolve16 } from "node:path";
|
|
20520
|
+
function git7(args, cwd) {
|
|
20521
|
+
return execFileSync14("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
19996
20522
|
}
|
|
19997
20523
|
function pairRecordPath(ledger, task, pair) {
|
|
19998
|
-
return
|
|
20524
|
+
return join26(ledger, "pairs", `${task}--${pair}.json`);
|
|
19999
20525
|
}
|
|
20000
20526
|
function researchLockPath(ledger) {
|
|
20001
|
-
return
|
|
20527
|
+
return join26(ledger, "run.lock");
|
|
20002
20528
|
}
|
|
20003
20529
|
function readLockOwner(path) {
|
|
20004
20530
|
try {
|
|
20005
|
-
const parsed = JSON.parse(
|
|
20531
|
+
const parsed = JSON.parse(readFileSync24(path, "utf8"));
|
|
20006
20532
|
if (!isRecord(parsed) || typeof parsed.pid !== "number" || !Number.isInteger(parsed.pid) || parsed.pid <= 0) return null;
|
|
20007
20533
|
if (typeof parsed.host !== "string" || typeof parsed.started_at !== "string" || typeof parsed.token !== "string") return null;
|
|
20008
20534
|
return { pid: parsed.pid, host: parsed.host, started_at: parsed.started_at, token: parsed.token };
|
|
@@ -20023,7 +20549,7 @@ function lockHolder(path) {
|
|
|
20023
20549
|
return owner ? `pid ${owner.pid} on ${owner.host}, started ${owner.started_at}` : "an unreadable lock file";
|
|
20024
20550
|
}
|
|
20025
20551
|
function acquireResearchLock(ledger, breakStaleLock = false) {
|
|
20026
|
-
|
|
20552
|
+
mkdirSync10(ledger, { recursive: true });
|
|
20027
20553
|
const path = researchLockPath(ledger);
|
|
20028
20554
|
if (breakStaleLock && existsSync18(path)) {
|
|
20029
20555
|
const owner2 = readLockOwner(path);
|
|
@@ -20087,7 +20613,7 @@ function acquireResearchLock(ledger, breakStaleLock = false) {
|
|
|
20087
20613
|
if (released) return;
|
|
20088
20614
|
released = true;
|
|
20089
20615
|
try {
|
|
20090
|
-
if (
|
|
20616
|
+
if (readFileSync24(path, "utf8") === text) unlinkSync(path);
|
|
20091
20617
|
} catch {
|
|
20092
20618
|
}
|
|
20093
20619
|
}
|
|
@@ -20096,7 +20622,7 @@ function acquireResearchLock(ledger, breakStaleLock = false) {
|
|
|
20096
20622
|
function resumableRecord(path, expected) {
|
|
20097
20623
|
let raw;
|
|
20098
20624
|
try {
|
|
20099
|
-
raw = JSON.parse(
|
|
20625
|
+
raw = JSON.parse(readFileSync24(path, "utf8"));
|
|
20100
20626
|
} catch (e) {
|
|
20101
20627
|
throw new ResearchError(`ledger record ${path} is malformed (${errorMessage(e).split("\n")[0]}); remove it to regenerate the pair, or use a new --out`);
|
|
20102
20628
|
}
|
|
@@ -20133,28 +20659,28 @@ function resumableRecord(path, expected) {
|
|
|
20133
20659
|
}
|
|
20134
20660
|
function writeRecordAtomically(path, text) {
|
|
20135
20661
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
20136
|
-
|
|
20662
|
+
writeFileSync12(tmp, text);
|
|
20137
20663
|
renameSync4(tmp, path);
|
|
20138
20664
|
}
|
|
20139
20665
|
function cloneArgs(repo, ws3) {
|
|
20140
20666
|
return ["-c", "protocol.ext.allow=never", "clone", "-q", "--no-hardlinks", "--", repo, ws3];
|
|
20141
20667
|
}
|
|
20142
20668
|
function freshWorkspace(ledger, task, pair, arm, sourceBase) {
|
|
20143
|
-
const ws3 =
|
|
20144
|
-
|
|
20145
|
-
|
|
20669
|
+
const ws3 = join26(ledger, "workspaces", `${task.id}--${pair}--${arm}`);
|
|
20670
|
+
rmSync12(ws3, { recursive: true, force: true });
|
|
20671
|
+
mkdirSync10(join26(ledger, "workspaces"), { recursive: true });
|
|
20146
20672
|
try {
|
|
20147
|
-
|
|
20673
|
+
execFileSync14("git", cloneArgs(task.repo, ws3), { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
|
|
20148
20674
|
} catch (e) {
|
|
20149
20675
|
throw new ResearchError(`task "${task.id}": cannot clone ${task.repo}: ${errorMessage(e).split("\n")[0]}`);
|
|
20150
20676
|
}
|
|
20151
|
-
|
|
20152
|
-
|
|
20677
|
+
git7(["config", "user.name", "tamperward-research"], ws3);
|
|
20678
|
+
git7(["config", "user.email", "research@tamperward.invalid"], ws3);
|
|
20153
20679
|
const requestedBase = sourceBase ?? task.base;
|
|
20154
20680
|
const candidates = sourceBase !== void 0 ? [sourceBase] : task.base === "HEAD" ? ["HEAD"] : [task.base, `origin/${task.base}`];
|
|
20155
20681
|
for (const rev2 of candidates) {
|
|
20156
20682
|
try {
|
|
20157
|
-
|
|
20683
|
+
git7(["checkout", "-q", "--detach", rev2], ws3);
|
|
20158
20684
|
return ws3;
|
|
20159
20685
|
} catch {
|
|
20160
20686
|
}
|
|
@@ -20308,15 +20834,15 @@ function observeOutcome(ws3, base, head, task, trusted) {
|
|
|
20308
20834
|
}
|
|
20309
20835
|
function runTrajectory(ledger, task, pair, arm, adapter, opts, sourceBase) {
|
|
20310
20836
|
const ws3 = freshWorkspace(ledger, task, pair, arm, sourceBase);
|
|
20311
|
-
const prep = { id: task.id, prompt: task.prompt, cwd: ws3, base:
|
|
20837
|
+
const prep = { id: task.id, prompt: task.prompt, cwd: ws3, base: git7(["rev-parse", "HEAD"], ws3), arm, model: opts.model };
|
|
20312
20838
|
if (arm === "gated" && adapter.prepareGated) {
|
|
20313
20839
|
captureStdout(() => adapter.prepareGated?.(prep));
|
|
20314
|
-
if (
|
|
20315
|
-
|
|
20316
|
-
|
|
20840
|
+
if (git7(["status", "--porcelain"], ws3)) {
|
|
20841
|
+
git7(["add", "-A"], ws3);
|
|
20842
|
+
git7(["commit", "-qm", "tamperward research: wire enforcement (gated arm)"], ws3);
|
|
20317
20843
|
}
|
|
20318
20844
|
}
|
|
20319
|
-
const base =
|
|
20845
|
+
const base = git7(["rev-parse", "HEAD"], ws3);
|
|
20320
20846
|
const launch2 = adapter.launch({ ...prep, base });
|
|
20321
20847
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
20322
20848
|
let agent;
|
|
@@ -20329,7 +20855,7 @@ function runTrajectory(ledger, task, pair, arm, adapter, opts, sourceBase) {
|
|
|
20329
20855
|
agent = runUngated(ws3, launch2.argv, launch2.env, opts.agentBudget);
|
|
20330
20856
|
}
|
|
20331
20857
|
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
20332
|
-
const head =
|
|
20858
|
+
const head = git7(["rev-parse", "HEAD"], ws3);
|
|
20333
20859
|
const trustedSurface = trustedProtectedOnly(base, ws3);
|
|
20334
20860
|
const fingerprintBefore = treeFingerprint(ws3, trustedSurface.protectedOnly);
|
|
20335
20861
|
const observed = observeOutcome(ws3, base, head, task, trustedSurface.trusted);
|
|
@@ -20391,7 +20917,7 @@ function runResearch(opts) {
|
|
|
20391
20917
|
}
|
|
20392
20918
|
try {
|
|
20393
20919
|
const pairs = opts.pairs ?? 1;
|
|
20394
|
-
|
|
20920
|
+
mkdirSync10(join26(ledger, "pairs"), { recursive: true });
|
|
20395
20921
|
for (const task of tasks) {
|
|
20396
20922
|
const existingRecords = /* @__PURE__ */ new Map();
|
|
20397
20923
|
let sourceBase = null;
|
|
@@ -20518,10 +21044,10 @@ var init_run2 = __esm({
|
|
|
20518
21044
|
});
|
|
20519
21045
|
|
|
20520
21046
|
// src/research/summarize.ts
|
|
20521
|
-
import { readdirSync as readdirSync7, readFileSync as
|
|
20522
|
-
import { join as
|
|
21047
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync25 } from "node:fs";
|
|
21048
|
+
import { join as join27, resolve as resolve17 } from "node:path";
|
|
20523
21049
|
function readLedger2(dir) {
|
|
20524
|
-
const pairsDir =
|
|
21050
|
+
const pairsDir = join27(resolve17(dir), "pairs");
|
|
20525
21051
|
let names;
|
|
20526
21052
|
try {
|
|
20527
21053
|
names = readdirSync7(pairsDir).filter((n2) => n2.endsWith(".json")).sort();
|
|
@@ -20530,10 +21056,10 @@ function readLedger2(dir) {
|
|
|
20530
21056
|
}
|
|
20531
21057
|
if (names.length === 0) throw new ResearchError(`ledger ${pairsDir} holds no pair records`);
|
|
20532
21058
|
return names.map((n2) => {
|
|
20533
|
-
const path =
|
|
21059
|
+
const path = join27(pairsDir, n2);
|
|
20534
21060
|
let raw;
|
|
20535
21061
|
try {
|
|
20536
|
-
raw = JSON.parse(
|
|
21062
|
+
raw = JSON.parse(readFileSync25(path, "utf8"));
|
|
20537
21063
|
} catch (e) {
|
|
20538
21064
|
throw new ResearchError(`ledger record ${path} is not valid JSON: ${errorMessage(e)}`);
|
|
20539
21065
|
}
|
|
@@ -20664,9 +21190,9 @@ var init_summarize = __esm({
|
|
|
20664
21190
|
});
|
|
20665
21191
|
|
|
20666
21192
|
// src/research/init.ts
|
|
20667
|
-
import { createHash as
|
|
20668
|
-
import { existsSync as existsSync19, mkdirSync as
|
|
20669
|
-
import { basename as basename5, dirname as
|
|
21193
|
+
import { createHash as createHash15 } from "node:crypto";
|
|
21194
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync11, readFileSync as readFileSync26, writeFileSync as writeFileSync13 } from "node:fs";
|
|
21195
|
+
import { basename as basename5, dirname as dirname12, resolve as resolve18 } from "node:path";
|
|
20670
21196
|
function idFrom(repo) {
|
|
20671
21197
|
const clean = repo.replace(/[\\/]$/, "").split(/[\\/]/).pop() || "task";
|
|
20672
21198
|
return (basename5(clean, ".git").replace(/[^A-Za-z0-9._-]+/g, "-") || "task").slice(0, 80);
|
|
@@ -20694,8 +21220,8 @@ function createResearchManifest(opts) {
|
|
|
20694
21220
|
}]
|
|
20695
21221
|
};
|
|
20696
21222
|
const text = JSON.stringify(doc, null, 2) + "\n";
|
|
20697
|
-
|
|
20698
|
-
|
|
21223
|
+
mkdirSync11(dirname12(out3), { recursive: true });
|
|
21224
|
+
writeFileSync13(out3, text, { flag: "wx", mode: 384 });
|
|
20699
21225
|
const parsed = readManifest(out3);
|
|
20700
21226
|
if (parsed.tasks.length !== 1 || parsed.tasks[0].id !== id) throw new ResearchError(`research init wrote an invalid manifest ${out3}`);
|
|
20701
21227
|
return out3;
|
|
@@ -20717,7 +21243,7 @@ function runResearchInit(opts) {
|
|
|
20717
21243
|
}
|
|
20718
21244
|
}
|
|
20719
21245
|
function requireHash(path) {
|
|
20720
|
-
return
|
|
21246
|
+
return createHash15("sha256").update(readFileSync26(path)).digest("hex");
|
|
20721
21247
|
}
|
|
20722
21248
|
var init_init2 = __esm({
|
|
20723
21249
|
"src/research/init.ts"() {
|
|
@@ -20782,10 +21308,10 @@ var init_report2 = __esm({
|
|
|
20782
21308
|
});
|
|
20783
21309
|
|
|
20784
21310
|
// src/research/bundle.ts
|
|
20785
|
-
import { createHash as
|
|
21311
|
+
import { createHash as createHash16 } from "node:crypto";
|
|
20786
21312
|
import { gzipSync, gunzipSync } from "node:zlib";
|
|
20787
|
-
import { existsSync as existsSync20, mkdirSync as
|
|
20788
|
-
import { dirname as
|
|
21313
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync12, readdirSync as readdirSync8, readFileSync as readFileSync27, statSync as statSync6, writeFileSync as writeFileSync14 } from "node:fs";
|
|
21314
|
+
import { dirname as dirname13, join as join28, resolve as resolve19 } from "node:path";
|
|
20789
21315
|
function octal(value, width) {
|
|
20790
21316
|
return value.toString(8).padStart(width - 1, "0") + "\0";
|
|
20791
21317
|
}
|
|
@@ -20831,14 +21357,14 @@ function parseTar(bytes) {
|
|
|
20831
21357
|
}
|
|
20832
21358
|
function walkLedger(ledger) {
|
|
20833
21359
|
const entries = [];
|
|
20834
|
-
const pairs =
|
|
21360
|
+
const pairs = join28(ledger, "pairs");
|
|
20835
21361
|
if (!existsSync20(pairs) || !statSync6(pairs).isDirectory()) throw new ResearchError(`ledger ${ledger} has no pairs directory`);
|
|
20836
21362
|
const names = readdirSync8(pairs).filter((n2) => n2.endsWith(".json")).sort();
|
|
20837
21363
|
if (names.length === 0) throw new ResearchError(`ledger ${ledger} holds no pair records`);
|
|
20838
21364
|
for (const name of names) {
|
|
20839
|
-
const path =
|
|
21365
|
+
const path = join28(pairs, name);
|
|
20840
21366
|
if (!statSync6(path).isFile()) continue;
|
|
20841
|
-
entries.push({ name: `ledger/pairs/${name}`, bytes:
|
|
21367
|
+
entries.push({ name: `ledger/pairs/${name}`, bytes: readFileSync27(path) });
|
|
20842
21368
|
}
|
|
20843
21369
|
return entries;
|
|
20844
21370
|
}
|
|
@@ -20848,7 +21374,7 @@ function jsonBytes(value) {
|
|
|
20848
21374
|
function validateResearchBundle(path) {
|
|
20849
21375
|
let files;
|
|
20850
21376
|
try {
|
|
20851
|
-
files = parseTar(gunzipSync(
|
|
21377
|
+
files = parseTar(gunzipSync(readFileSync27(resolve19(path))));
|
|
20852
21378
|
} catch (e) {
|
|
20853
21379
|
throw new ResearchError(`cannot read research bundle ${path}: ${e instanceof Error ? e.message : String(e)}`);
|
|
20854
21380
|
}
|
|
@@ -20871,7 +21397,7 @@ function validateResearchBundle(path) {
|
|
|
20871
21397
|
if (p.manifest_included !== Boolean(manifest) || p.prompts_included !== Boolean(manifest)) {
|
|
20872
21398
|
throw new ResearchError("research bundle manifest inclusion metadata is inconsistent");
|
|
20873
21399
|
}
|
|
20874
|
-
if (manifest &&
|
|
21400
|
+
if (manifest && createHash16("sha256").update(manifest).digest("hex") !== p.manifest_sha256) {
|
|
20875
21401
|
throw new ResearchError("research bundle manifest does not match its recorded sha256");
|
|
20876
21402
|
}
|
|
20877
21403
|
const parsedSummary = summary;
|
|
@@ -20902,8 +21428,8 @@ function createResearchBundle(opts) {
|
|
|
20902
21428
|
const summary = summarizeLedger(ledger);
|
|
20903
21429
|
const entries = walkLedger(ledger);
|
|
20904
21430
|
if (opts.manifest) {
|
|
20905
|
-
const manifest =
|
|
20906
|
-
const sha3 =
|
|
21431
|
+
const manifest = readFileSync27(resolve19(opts.manifest));
|
|
21432
|
+
const sha3 = createHash16("sha256").update(manifest).digest("hex");
|
|
20907
21433
|
if (sha3 !== summary.manifest_sha256) throw new ResearchError(`manifest sha256 ${sha3} does not match ledger ${summary.manifest_sha256}`);
|
|
20908
21434
|
entries.push({ name: "manifest.json", bytes: manifest });
|
|
20909
21435
|
}
|
|
@@ -20925,8 +21451,8 @@ function createResearchBundle(opts) {
|
|
|
20925
21451
|
entries.push({ name: "report.txt", bytes: Buffer.from(renderResearchReport(summary), "utf8") });
|
|
20926
21452
|
entries.push({ name: "provenance.json", bytes: jsonBytes(provenance) });
|
|
20927
21453
|
const out3 = resolve19(opts.out);
|
|
20928
|
-
|
|
20929
|
-
|
|
21454
|
+
mkdirSync12(dirname13(out3), { recursive: true });
|
|
21455
|
+
writeFileSync14(out3, gzipSync(makeTar(entries)), { flag: "wx", mode: 384 });
|
|
20930
21456
|
return out3;
|
|
20931
21457
|
}
|
|
20932
21458
|
function runResearchBundle(opts) {
|
|
@@ -21145,6 +21671,15 @@ function parseDoctor(args) {
|
|
|
21145
21671
|
}
|
|
21146
21672
|
return o;
|
|
21147
21673
|
}
|
|
21674
|
+
function parseStatus(args) {
|
|
21675
|
+
const o = {};
|
|
21676
|
+
for (let i = 0; i < args.length; i++) {
|
|
21677
|
+
const a = args[i];
|
|
21678
|
+
if (a === "--json") o.json = true;
|
|
21679
|
+
else if (a === "--cwd") o.cwd = args[++i];
|
|
21680
|
+
}
|
|
21681
|
+
return o;
|
|
21682
|
+
}
|
|
21148
21683
|
function parseStats(args) {
|
|
21149
21684
|
const o = {};
|
|
21150
21685
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -21353,6 +21888,12 @@ function validateCliArgs(cmd, args) {
|
|
|
21353
21888
|
}
|
|
21354
21889
|
}).error;
|
|
21355
21890
|
}
|
|
21891
|
+
if (cmd === "status") {
|
|
21892
|
+
return validateFlatArgs(args, {
|
|
21893
|
+
flags: ["--json"],
|
|
21894
|
+
values: { "--cwd": "string" }
|
|
21895
|
+
}).error;
|
|
21896
|
+
}
|
|
21356
21897
|
if (cmd === "onboard") {
|
|
21357
21898
|
const parsed = validateFlatArgs(args, {
|
|
21358
21899
|
flags: ["--yes", "--skip-demo", "--demo", "--no-github"],
|
|
@@ -21598,6 +22139,16 @@ Formats:
|
|
|
21598
22139
|
every tool the gate must see.
|
|
21599
22140
|
--force-workflow replaces a workflow it
|
|
21600
22141
|
did not write, or one you have edited.
|
|
22142
|
+
tamperward status [--json] [--cwd D] verification posture in three distinct
|
|
22143
|
+
lanes \u2014 Authority (repository/final
|
|
22144
|
+
adjudication), Intervention (runtime
|
|
22145
|
+
steering), and Verification: whether the
|
|
22146
|
+
last successful verify still applies to
|
|
22147
|
+
the EXACT current state (CURRENT / STALE /
|
|
22148
|
+
VERIFYING / BROKEN / UNVERIFIED). --json
|
|
22149
|
+
emits the versioned status document for
|
|
22150
|
+
editors, CI and dashboards. Local CURRENT
|
|
22151
|
+
is posture, never repository/CI authority.
|
|
21601
22152
|
tamperward doctor [--base R] report installation + authority posture
|
|
21602
22153
|
[--workflow F] [--cwd D] and validate the CI verifier's outer-time
|
|
21603
22154
|
[--json] envelope against the trusted policy.
|
|
@@ -21650,6 +22201,8 @@ function main(argv) {
|
|
|
21650
22201
|
return runDoctor(parseDoctor(rest));
|
|
21651
22202
|
case "stats":
|
|
21652
22203
|
return runStats(parseStats(rest));
|
|
22204
|
+
case "status":
|
|
22205
|
+
return runStatus(parseStatus(rest));
|
|
21653
22206
|
case "onboard":
|
|
21654
22207
|
return runOnboard(parseOnboard(rest));
|
|
21655
22208
|
case "watch":
|
|
@@ -21705,6 +22258,7 @@ var init_main = __esm({
|
|
|
21705
22258
|
init_allow();
|
|
21706
22259
|
init_init();
|
|
21707
22260
|
init_doctor();
|
|
22261
|
+
init_status2();
|
|
21708
22262
|
init_verify();
|
|
21709
22263
|
init_trace_verify();
|
|
21710
22264
|
init_run();
|
|
@@ -21719,7 +22273,7 @@ var init_main = __esm({
|
|
|
21719
22273
|
// src/cli/index.ts
|
|
21720
22274
|
init_hook_client();
|
|
21721
22275
|
init_exit();
|
|
21722
|
-
import { readFileSync as
|
|
22276
|
+
import { readFileSync as readFileSync28 } from "node:fs";
|
|
21723
22277
|
function loadMain() {
|
|
21724
22278
|
return Promise.resolve().then(() => (init_main(), main_exports));
|
|
21725
22279
|
}
|
|
@@ -21732,7 +22286,7 @@ async function launch(argv) {
|
|
|
21732
22286
|
if (kind && hookServiceEnabled()) {
|
|
21733
22287
|
let raw = null;
|
|
21734
22288
|
try {
|
|
21735
|
-
raw =
|
|
22289
|
+
raw = readFileSync28(0, "utf8");
|
|
21736
22290
|
} catch {
|
|
21737
22291
|
raw = null;
|
|
21738
22292
|
}
|