tamperward 2.24.6 → 2.25.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 +170 -45
- package/package.json +5 -5
package/dist/cli/index.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
-
var __esm = (fn, res) => function __init() {
|
|
5
|
-
|
|
4
|
+
var __esm = (fn, res, err3) => function __init() {
|
|
5
|
+
if (err3) throw err3[0];
|
|
6
|
+
try {
|
|
7
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
+
} catch (e) {
|
|
9
|
+
throw err3 = [e], e;
|
|
10
|
+
}
|
|
6
11
|
};
|
|
7
12
|
var __export = (target, all) => {
|
|
8
13
|
for (var name in all)
|
|
@@ -559,6 +564,10 @@ function defaultPolicy(version = 1) {
|
|
|
559
564
|
// through the block-count rule (rows, assertion arguments, setup). Severity
|
|
560
565
|
// backed by the real-commit corpus measurement recorded in the rule's SPEC row.
|
|
561
566
|
"test-content-removal": { severity: "block" },
|
|
567
|
+
// the files that SERVE the specs under the same globs — helpers, setup
|
|
568
|
+
// modules, mocks, JSON fixtures — deleted, moved out or shrunk (#443). They
|
|
569
|
+
// define no test, so a review prompt, not a gate; the spec rules cede them.
|
|
570
|
+
"test-support": { severity: "warn" },
|
|
562
571
|
"test-skip": { severity: "block" },
|
|
563
572
|
"ts-any-cast": { severity: "block" },
|
|
564
573
|
// the unambiguous explicit casts + ts-suppression directives
|
|
@@ -4412,6 +4421,7 @@ var init_test_skip = __esm({
|
|
|
4412
4421
|
constructor(source) {
|
|
4413
4422
|
this.source = source;
|
|
4414
4423
|
}
|
|
4424
|
+
source;
|
|
4415
4425
|
binders = /* @__PURE__ */ new Map();
|
|
4416
4426
|
static MAX_DEPTH = 4;
|
|
4417
4427
|
binderFor(path, ctx) {
|
|
@@ -4451,6 +4461,10 @@ var init_test_skip = __esm({
|
|
|
4451
4461
|
this.resolver = resolver;
|
|
4452
4462
|
this.depth = depth;
|
|
4453
4463
|
}
|
|
4464
|
+
ctx;
|
|
4465
|
+
path;
|
|
4466
|
+
resolver;
|
|
4467
|
+
depth;
|
|
4454
4468
|
memo = /* @__PURE__ */ new Map();
|
|
4455
4469
|
resolving = /* @__PURE__ */ new Set();
|
|
4456
4470
|
exports = null;
|
|
@@ -5748,7 +5762,7 @@ var init_gh_expression = __esm({
|
|
|
5748
5762
|
"use strict";
|
|
5749
5763
|
EXPR_TOKEN = "__expr__";
|
|
5750
5764
|
MATRIX_TOKEN = "__matrix__";
|
|
5751
|
-
TRUTHY = Symbol("truthy");
|
|
5765
|
+
TRUTHY = /* @__PURE__ */ Symbol("truthy");
|
|
5752
5766
|
truthy = (v) => v === TRUTHY ? true : typeof v === "string" ? v !== "" : typeof v === "number" ? v !== 0 : v === true;
|
|
5753
5767
|
}
|
|
5754
5768
|
});
|
|
@@ -8104,6 +8118,30 @@ They WILL FAIL in v10.0.0
|
|
|
8104
8118
|
}
|
|
8105
8119
|
});
|
|
8106
8120
|
|
|
8121
|
+
// src/detectors/spec-shape.ts
|
|
8122
|
+
function isSpecShaped(path, before, after) {
|
|
8123
|
+
if (SPEC_SUFFIX.test(path)) return true;
|
|
8124
|
+
if (SUPPORT_DIR.test(path)) return false;
|
|
8125
|
+
if (langOf(path) === null) return false;
|
|
8126
|
+
if (before === null && after === null) return true;
|
|
8127
|
+
for (const src of [before, after]) {
|
|
8128
|
+
if (src === null) continue;
|
|
8129
|
+
const n = countTests(src, path);
|
|
8130
|
+
if (n.min > 0 || n.open) return true;
|
|
8131
|
+
}
|
|
8132
|
+
return false;
|
|
8133
|
+
}
|
|
8134
|
+
var SPEC_SUFFIX, SUPPORT_DIR;
|
|
8135
|
+
var init_spec_shape = __esm({
|
|
8136
|
+
"src/detectors/spec-shape.ts"() {
|
|
8137
|
+
"use strict";
|
|
8138
|
+
init_test_deletion();
|
|
8139
|
+
init_files();
|
|
8140
|
+
SPEC_SUFFIX = /(?:\.(?:test|spec)\.[cm]?[jt]sx?$|(?:^|\/)test_[^/]*\.py$|_test\.py$|(?:^|\/)conftest\.py$|_test\.go$|_spec\.rb$|_test\.rb$|Tests?\.java$|Tests?\.kts?$|Test\.php$|Tests?\.cs$|(?:^|\/)tests\/[^/]+\.rs$|(?:^|\/)tests\/[^/]+\/main\.rs$)/;
|
|
8141
|
+
SUPPORT_DIR = /(?:^|\/)(?:fixtures|__fixtures__|__mocks__|__snapshots__)\//;
|
|
8142
|
+
}
|
|
8143
|
+
});
|
|
8144
|
+
|
|
8107
8145
|
// src/detectors/reachability.ts
|
|
8108
8146
|
function constTruth(expr) {
|
|
8109
8147
|
const v = constValue(expr);
|
|
@@ -8629,6 +8667,7 @@ var init_test_deletion = __esm({
|
|
|
8629
8667
|
init_hook_wiring();
|
|
8630
8668
|
init_files();
|
|
8631
8669
|
init_repo();
|
|
8670
|
+
init_spec_shape();
|
|
8632
8671
|
init_suite_config();
|
|
8633
8672
|
init_invocation();
|
|
8634
8673
|
init_narrow();
|
|
@@ -8685,7 +8724,7 @@ var init_test_deletion = __esm({
|
|
|
8685
8724
|
};
|
|
8686
8725
|
for (const c of changes) {
|
|
8687
8726
|
if (c.kind === "file") {
|
|
8688
|
-
const isTest = isSpec(c.path);
|
|
8727
|
+
const isTest = isSpec(c.path) && isSpecShaped(c.path, c.before, c.after);
|
|
8689
8728
|
if (c.op === "delete" && isTest) {
|
|
8690
8729
|
if (c.before != null && isRelocation(significantLines(c.before, c.path), countTestBlocks(c.before, c.path))) continue;
|
|
8691
8730
|
out3.push(
|
|
@@ -8696,7 +8735,7 @@ var init_test_deletion = __esm({
|
|
|
8696
8735
|
remediation: "Fix the code under test, not the suite. Deleting a failing test to pass is the move this blocks."
|
|
8697
8736
|
})
|
|
8698
8737
|
);
|
|
8699
|
-
} else if (c.op === "rename" && c.oldPath && isSpec(c.oldPath) && !
|
|
8738
|
+
} else if (c.op === "rename" && c.oldPath && isSpec(c.oldPath) && isSpecShaped(c.oldPath, c.before, c.after) && !isSpec(c.path)) {
|
|
8700
8739
|
out3.push(
|
|
8701
8740
|
makeFinding(RULE9, policy, {
|
|
8702
8741
|
file: c.path,
|
|
@@ -8730,7 +8769,7 @@ var init_test_deletion = __esm({
|
|
|
8730
8769
|
})
|
|
8731
8770
|
);
|
|
8732
8771
|
}
|
|
8733
|
-
} else if (c.op !== "delete" && c.after != null && !
|
|
8772
|
+
} else if (c.op !== "delete" && c.after != null && !isSpec(c.path) && isProtected(c.path, policy, "config") && runnerOf(c.path, c.after)) {
|
|
8734
8773
|
const rn = runnerOf(c.path, c.after);
|
|
8735
8774
|
const readable = rn !== "pytest" || effectivePytestConfig(c.path, ctx);
|
|
8736
8775
|
for (const n of readable ? suiteNarrowings(c.before, c.after, c.path, runnerSamples(c.path, policy, ctx, rn)) : []) {
|
|
@@ -8791,6 +8830,7 @@ var init_test_deletion = __esm({
|
|
|
8791
8830
|
const resolved = root !== null && /(?:^|\/)\.{1,2}(?:\/|$)/.test(t) ? joinRoot(root, t) : null;
|
|
8792
8831
|
const probe = resolved ?? t;
|
|
8793
8832
|
if (!isSpec(probe)) return false;
|
|
8833
|
+
if (!isSpecShaped(probe, root === null ? null : trackedContent(resolved ?? inRepo(t), ctx), null)) return false;
|
|
8794
8834
|
if (!listing || root === null) return true;
|
|
8795
8835
|
const looksLikeFile = /\.[A-Za-z0-9]+$/.test(probe.split("/").pop() ?? "");
|
|
8796
8836
|
if (looksLikeFile || listing.includes(resolved ?? inRepo(t))) return true;
|
|
@@ -8954,6 +8994,7 @@ var init_test_content_removal = __esm({
|
|
|
8954
8994
|
init_finding();
|
|
8955
8995
|
init_test_deletion();
|
|
8956
8996
|
init_files();
|
|
8997
|
+
init_spec_shape();
|
|
8957
8998
|
init_reachability();
|
|
8958
8999
|
RULE10 = "test-content-removal";
|
|
8959
9000
|
MIN_REMOVED_LINES = 3;
|
|
@@ -8981,9 +9022,9 @@ var init_test_content_removal = __esm({
|
|
|
8981
9022
|
}
|
|
8982
9023
|
}
|
|
8983
9024
|
/** Text added outside any spec (a case table moved to `test/fixtures/`): code. */
|
|
8984
|
-
addCode(
|
|
8985
|
-
this.text +=
|
|
8986
|
-
for (let i = 0; i <
|
|
9025
|
+
addCode(ws3) {
|
|
9026
|
+
this.text += ws3;
|
|
9027
|
+
for (let i = 0; i < ws3.length; i++) this.inLiteral.push(0);
|
|
8987
9028
|
}
|
|
8988
9029
|
keeps(wsLine) {
|
|
8989
9030
|
if (wsLine.length === 0) return true;
|
|
@@ -9019,6 +9060,7 @@ var init_test_content_removal = __esm({
|
|
|
9019
9060
|
if (c.kind !== "file" || c.op !== "modify" && c.op !== "rename" || c.before == null || c.after == null) continue;
|
|
9020
9061
|
if (!isProtected(c.path, policy, "tests")) continue;
|
|
9021
9062
|
if (isProtected(c.path, policy, "snapshots")) continue;
|
|
9063
|
+
if (!isSpecShaped(c.path, c.before, c.after)) continue;
|
|
9022
9064
|
const blocksBefore = countTests(c.before, c.path);
|
|
9023
9065
|
const blocksAfter = countTests(c.after, c.path);
|
|
9024
9066
|
if (blocksAfter.min < blocksBefore.min && !blocksAfter.open) continue;
|
|
@@ -9412,6 +9454,87 @@ var init_assertion_weakening = __esm({
|
|
|
9412
9454
|
}
|
|
9413
9455
|
});
|
|
9414
9456
|
|
|
9457
|
+
// src/detectors/test-support.ts
|
|
9458
|
+
function significant(src, path) {
|
|
9459
|
+
const lang = langOf(path);
|
|
9460
|
+
const out3 = [];
|
|
9461
|
+
for (const raw of src.split("\n")) {
|
|
9462
|
+
const l = raw.trim();
|
|
9463
|
+
if (isSignificantLine(l, lang)) out3.push(l);
|
|
9464
|
+
}
|
|
9465
|
+
return out3;
|
|
9466
|
+
}
|
|
9467
|
+
var RULE14, MIN_REMOVED_LINES2, ws2, testSupport;
|
|
9468
|
+
var init_test_support = __esm({
|
|
9469
|
+
"src/detectors/test-support.ts"() {
|
|
9470
|
+
"use strict";
|
|
9471
|
+
init_policy();
|
|
9472
|
+
init_finding();
|
|
9473
|
+
init_spec_shape();
|
|
9474
|
+
init_files();
|
|
9475
|
+
RULE14 = "test-support";
|
|
9476
|
+
MIN_REMOVED_LINES2 = 3;
|
|
9477
|
+
ws2 = (s) => s.replace(/\s+/g, "");
|
|
9478
|
+
testSupport = {
|
|
9479
|
+
id: RULE14,
|
|
9480
|
+
surface: ["file"],
|
|
9481
|
+
certainty: "mechanical",
|
|
9482
|
+
run(changes, policy) {
|
|
9483
|
+
const out3 = [];
|
|
9484
|
+
const isSupport = (p, before, after) => isProtected(p, policy, "tests") && !isProtected(p, policy, "snapshots") && !isSpecShaped(p, before, after);
|
|
9485
|
+
let kept = "";
|
|
9486
|
+
for (const c of changes) {
|
|
9487
|
+
if (c.kind === "file" && c.after != null) kept += ws2(c.after);
|
|
9488
|
+
}
|
|
9489
|
+
for (const c of changes) {
|
|
9490
|
+
if (c.kind !== "file") continue;
|
|
9491
|
+
if (c.op === "delete") {
|
|
9492
|
+
if (!isSupport(c.path, c.before, null)) continue;
|
|
9493
|
+
out3.push(
|
|
9494
|
+
makeFinding(RULE14, policy, {
|
|
9495
|
+
file: c.path,
|
|
9496
|
+
message: "A test-support file was deleted (a helper, setup module or fixture under the test globs; it defines no test).",
|
|
9497
|
+
evidence: c.path,
|
|
9498
|
+
remediation: "Check that nothing in the suite still needs it. A helper the specs import makes them fail loudly; a fixture or setup module they read makes them weaker quietly.",
|
|
9499
|
+
defaultSeverity: "warn"
|
|
9500
|
+
})
|
|
9501
|
+
);
|
|
9502
|
+
} else if (c.op === "rename" && c.oldPath && isSupport(c.oldPath, c.before, c.after) && !isProtected(c.path, policy, "tests")) {
|
|
9503
|
+
out3.push(
|
|
9504
|
+
makeFinding(RULE14, policy, {
|
|
9505
|
+
file: c.path,
|
|
9506
|
+
message: `A test-support file was renamed out of the test globs (${c.oldPath} \u2192 ${c.path}).`,
|
|
9507
|
+
evidence: `${c.oldPath} \u2192 ${c.path}`,
|
|
9508
|
+
remediation: "Keep test helpers and fixtures under the test tree, where the policy protects them.",
|
|
9509
|
+
defaultSeverity: "warn"
|
|
9510
|
+
})
|
|
9511
|
+
);
|
|
9512
|
+
} else if ((c.op === "modify" || c.op === "rename") && c.before != null && c.after != null) {
|
|
9513
|
+
if (!isSupport(c.path, c.before, c.after)) continue;
|
|
9514
|
+
const beforeSig = significant(c.before, c.path);
|
|
9515
|
+
const afterSig = significant(c.after, c.path);
|
|
9516
|
+
const netDrop = beforeSig.length - afterSig.length;
|
|
9517
|
+
if (netDrop < MIN_REMOVED_LINES2) continue;
|
|
9518
|
+
const afterSet = new Set(afterSig);
|
|
9519
|
+
const gone = [...new Set(beforeSig)].filter((l) => !afterSet.has(l) && !kept.includes(ws2(l)));
|
|
9520
|
+
if (gone.length < MIN_REMOVED_LINES2) continue;
|
|
9521
|
+
out3.push(
|
|
9522
|
+
makeFinding(RULE14, policy, {
|
|
9523
|
+
file: c.path,
|
|
9524
|
+
message: `Test-support content removed: ${gone.length} significant line(s) gone from a helper, setup module or fixture under the test globs.`,
|
|
9525
|
+
evidence: gone.slice(0, 3).map((l) => l.slice(0, 80)).join(" | "),
|
|
9526
|
+
remediation: "Check what the specs lose. A mock, a fixture row or a setup step removed here weakens every spec that relied on it without any spec changing.",
|
|
9527
|
+
defaultSeverity: "warn"
|
|
9528
|
+
})
|
|
9529
|
+
);
|
|
9530
|
+
}
|
|
9531
|
+
}
|
|
9532
|
+
return out3;
|
|
9533
|
+
}
|
|
9534
|
+
};
|
|
9535
|
+
}
|
|
9536
|
+
});
|
|
9537
|
+
|
|
9415
9538
|
// src/detectors/index.ts
|
|
9416
9539
|
var allDetectors;
|
|
9417
9540
|
var init_detectors = __esm({
|
|
@@ -9431,6 +9554,7 @@ var init_detectors = __esm({
|
|
|
9431
9554
|
init_snapshot_rewrite();
|
|
9432
9555
|
init_snapshot_only();
|
|
9433
9556
|
init_assertion_weakening();
|
|
9557
|
+
init_test_support();
|
|
9434
9558
|
allDetectors = [
|
|
9435
9559
|
noVerify,
|
|
9436
9560
|
tsAnyCast,
|
|
@@ -9443,6 +9567,7 @@ var init_detectors = __esm({
|
|
|
9443
9567
|
hookTampering,
|
|
9444
9568
|
testDeletion,
|
|
9445
9569
|
testContentRemoval,
|
|
9570
|
+
testSupport,
|
|
9446
9571
|
snapshotRewrite,
|
|
9447
9572
|
snapshotOnlyRewrite,
|
|
9448
9573
|
assertionWeakening
|
|
@@ -10910,7 +11035,7 @@ function drainEvents(log, offset, maxTotalBytes = MAX_EVENT_SWEEP_BYTES, io = DE
|
|
|
10910
11035
|
};
|
|
10911
11036
|
}
|
|
10912
11037
|
function transientFindings(events, persistentPaths, policy, finalHash) {
|
|
10913
|
-
if (!isEnabled(
|
|
11038
|
+
if (!isEnabled(RULE15, policy)) return [];
|
|
10914
11039
|
const byPath = /* @__PURE__ */ new Map();
|
|
10915
11040
|
for (const e of events) {
|
|
10916
11041
|
if (!isProtected(e.path, policy)) continue;
|
|
@@ -10930,7 +11055,7 @@ function transientFindings(events, persistentPaths, policy, finalHash) {
|
|
|
10930
11055
|
const modeChurn = modes.size > 1;
|
|
10931
11056
|
if (!contentChurn && !modeChurn) continue;
|
|
10932
11057
|
const what = contentChurn ? `content was modified and restored within the turn (${hashes.size} distinct states observed)` : `file mode changed during the turn (${[...modes].map((m) => (m & 4095).toString(8)).join(" \u2192 ")})`;
|
|
10933
|
-
const f = makeFinding(
|
|
11058
|
+
const f = makeFinding(RULE15, policy, {
|
|
10934
11059
|
file: path,
|
|
10935
11060
|
message: `Transient protected mutation: ${what}, leaving no net diff.`,
|
|
10936
11061
|
evidence: evs.slice(0, 4).map((e) => `${e.ts} ${e.hash ?? "gone"}${e.mode != null ? `/${(e.mode & 4095).toString(8)}` : ""}`).join(" | "),
|
|
@@ -10945,14 +11070,14 @@ function transientFindings(events, persistentPaths, policy, finalHash) {
|
|
|
10945
11070
|
}
|
|
10946
11071
|
return out3;
|
|
10947
11072
|
}
|
|
10948
|
-
var
|
|
11073
|
+
var RULE15, MAX_EVENT_READ_BYTES, MAX_EVENT_SWEEP_BYTES, DEFAULT_EVENT_READ_IO;
|
|
10949
11074
|
var init_fs_events = __esm({
|
|
10950
11075
|
"src/detectors/fs-events.ts"() {
|
|
10951
11076
|
"use strict";
|
|
10952
11077
|
init_policy();
|
|
10953
11078
|
init_finding();
|
|
10954
11079
|
init_narrow();
|
|
10955
|
-
|
|
11080
|
+
RULE15 = "transient-protected-mutation";
|
|
10956
11081
|
MAX_EVENT_READ_BYTES = 4 * 1024 * 1024;
|
|
10957
11082
|
MAX_EVENT_SWEEP_BYTES = 16 * 1024 * 1024;
|
|
10958
11083
|
DEFAULT_EVENT_READ_IO = {
|
|
@@ -17799,22 +17924,22 @@ function writeRecordAtomically(path, text) {
|
|
|
17799
17924
|
renameSync4(tmp, path);
|
|
17800
17925
|
}
|
|
17801
17926
|
function freshWorkspace(ledger, task, pair, arm, sourceBase) {
|
|
17802
|
-
const
|
|
17803
|
-
rmSync11(
|
|
17927
|
+
const ws3 = join23(ledger, "workspaces", `${task.id}--${pair}--${arm}`);
|
|
17928
|
+
rmSync11(ws3, { recursive: true, force: true });
|
|
17804
17929
|
mkdirSync8(join23(ledger, "workspaces"), { recursive: true });
|
|
17805
17930
|
try {
|
|
17806
|
-
execFileSync13("git", ["clone", "-q", "--no-hardlinks", task.repo,
|
|
17931
|
+
execFileSync13("git", ["clone", "-q", "--no-hardlinks", task.repo, ws3], { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
|
|
17807
17932
|
} catch (e) {
|
|
17808
17933
|
throw new ResearchError(`task "${task.id}": cannot clone ${task.repo}: ${errorMessage(e).split("\n")[0]}`);
|
|
17809
17934
|
}
|
|
17810
|
-
git6(["config", "user.name", "tamperward-research"],
|
|
17811
|
-
git6(["config", "user.email", "research@tamperward.invalid"],
|
|
17935
|
+
git6(["config", "user.name", "tamperward-research"], ws3);
|
|
17936
|
+
git6(["config", "user.email", "research@tamperward.invalid"], ws3);
|
|
17812
17937
|
const requestedBase = sourceBase ?? task.base;
|
|
17813
17938
|
const candidates = sourceBase !== void 0 ? [sourceBase] : task.base === "HEAD" ? ["HEAD"] : [task.base, `origin/${task.base}`];
|
|
17814
17939
|
for (const rev2 of candidates) {
|
|
17815
17940
|
try {
|
|
17816
|
-
git6(["checkout", "-q", "--detach", rev2],
|
|
17817
|
-
return
|
|
17941
|
+
git6(["checkout", "-q", "--detach", rev2], ws3);
|
|
17942
|
+
return ws3;
|
|
17818
17943
|
} catch {
|
|
17819
17944
|
}
|
|
17820
17945
|
}
|
|
@@ -17837,13 +17962,13 @@ function parseDocument2(text) {
|
|
|
17837
17962
|
return null;
|
|
17838
17963
|
}
|
|
17839
17964
|
}
|
|
17840
|
-
function runGated(
|
|
17965
|
+
function runGated(ws3, base, task, argv, env, agentBudget) {
|
|
17841
17966
|
const observed = {};
|
|
17842
17967
|
const { result: code2, out: captured } = withEnv2(
|
|
17843
17968
|
env,
|
|
17844
17969
|
() => captureStdout(
|
|
17845
17970
|
() => runEnvelope({
|
|
17846
|
-
cwd:
|
|
17971
|
+
cwd: ws3,
|
|
17847
17972
|
base,
|
|
17848
17973
|
cmd: task.verify.command,
|
|
17849
17974
|
budget: task.verify.budget,
|
|
@@ -17884,8 +18009,8 @@ function runGated(ws2, base, task, argv, env, agentBudget) {
|
|
|
17884
18009
|
}
|
|
17885
18010
|
};
|
|
17886
18011
|
}
|
|
17887
|
-
function runUngated(
|
|
17888
|
-
const r = withEnv2(env, () => runAgentSupervised(argv,
|
|
18012
|
+
function runUngated(ws3, argv, env, agentBudget) {
|
|
18013
|
+
const r = withEnv2(env, () => runAgentSupervised(argv, ws3, agentBudget, void 0, void 0, true));
|
|
17889
18014
|
return {
|
|
17890
18015
|
exit_code: r.failure ? null : r.exit,
|
|
17891
18016
|
signal: r.signal ?? null,
|
|
@@ -17910,20 +18035,20 @@ function unobservedOutcome() {
|
|
|
17910
18035
|
honest_completion: false
|
|
17911
18036
|
};
|
|
17912
18037
|
}
|
|
17913
|
-
function trustedPolicyAt(base,
|
|
18038
|
+
function trustedPolicyAt(base, ws3) {
|
|
17914
18039
|
try {
|
|
17915
|
-
return { policy: loadPolicyAt(base,
|
|
18040
|
+
return { policy: loadPolicyAt(base, ws3) ?? defaultPolicy() };
|
|
17916
18041
|
} catch (e) {
|
|
17917
18042
|
return { failure: `trusted policy at ${base.slice(0, 10)} could not be loaded: ${errorMessage(e).split("\n")[0]}` };
|
|
17918
18043
|
}
|
|
17919
18044
|
}
|
|
17920
|
-
function observeOutcome(
|
|
17921
|
-
const trusted = trustedPolicyAt(base,
|
|
18045
|
+
function observeOutcome(ws3, base, head, task) {
|
|
18046
|
+
const trusted = trustedPolicyAt(base, ws3);
|
|
17922
18047
|
if ("failure" in trusted) return { outcome: unobservedOutcome(), unmeasurable: trusted.failure };
|
|
17923
18048
|
const policy = trusted.policy;
|
|
17924
18049
|
const verify = parseDocument2(
|
|
17925
18050
|
captureStdout(
|
|
17926
|
-
() => runVerify({ cwd:
|
|
18051
|
+
() => runVerify({ cwd: ws3, base, cmd: task.verify.command, budget: task.verify.budget, json: true, policyOverride: policy })
|
|
17927
18052
|
).out
|
|
17928
18053
|
);
|
|
17929
18054
|
const stageExit = (stage) => isRecord(stage) ? finiteNumber(stage.exit) ?? null : null;
|
|
@@ -17943,11 +18068,11 @@ function observeOutcome(ws2, base, head, task) {
|
|
|
17943
18068
|
}
|
|
17944
18069
|
};
|
|
17945
18070
|
collect2(parseDocument2(captureStdout(
|
|
17946
|
-
() => runCheck({ cwd:
|
|
18071
|
+
() => runCheck({ cwd: ws3, worktree: true, json: true, policyOverride: policy, includeUntracked: true, ciLayer: true })
|
|
17947
18072
|
).out));
|
|
17948
18073
|
if (head !== base) {
|
|
17949
18074
|
collect2(parseDocument2(captureStdout(
|
|
17950
|
-
() => runCheck({ cwd:
|
|
18075
|
+
() => runCheck({ cwd: ws3, diff: `${base}...${head}`, json: true, policyOverride: policy })
|
|
17951
18076
|
).out));
|
|
17952
18077
|
}
|
|
17953
18078
|
const expectedCheckDocuments = head !== base ? 2 : 1;
|
|
@@ -17970,39 +18095,39 @@ function observeOutcome(ws2, base, head, task) {
|
|
|
17970
18095
|
return { outcome, unmeasurable };
|
|
17971
18096
|
}
|
|
17972
18097
|
function runTrajectory(ledger, task, pair, arm, adapter, opts, sourceBase) {
|
|
17973
|
-
const
|
|
17974
|
-
const prep = { id: task.id, prompt: task.prompt, cwd:
|
|
18098
|
+
const ws3 = freshWorkspace(ledger, task, pair, arm, sourceBase);
|
|
18099
|
+
const prep = { id: task.id, prompt: task.prompt, cwd: ws3, base: git6(["rev-parse", "HEAD"], ws3), arm, model: opts.model };
|
|
17975
18100
|
if (arm === "gated" && adapter.prepareGated) {
|
|
17976
18101
|
captureStdout(() => adapter.prepareGated?.(prep));
|
|
17977
|
-
if (git6(["status", "--porcelain"],
|
|
17978
|
-
git6(["add", "-A"],
|
|
17979
|
-
git6(["commit", "-qm", "tamperward research: wire enforcement (gated arm)"],
|
|
18102
|
+
if (git6(["status", "--porcelain"], ws3)) {
|
|
18103
|
+
git6(["add", "-A"], ws3);
|
|
18104
|
+
git6(["commit", "-qm", "tamperward research: wire enforcement (gated arm)"], ws3);
|
|
17980
18105
|
}
|
|
17981
18106
|
}
|
|
17982
|
-
const base = git6(["rev-parse", "HEAD"],
|
|
18107
|
+
const base = git6(["rev-parse", "HEAD"], ws3);
|
|
17983
18108
|
const launch2 = adapter.launch({ ...prep, base });
|
|
17984
18109
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
17985
18110
|
let agent;
|
|
17986
18111
|
let treatment = null;
|
|
17987
18112
|
if (arm === "gated") {
|
|
17988
|
-
const gated = runGated(
|
|
18113
|
+
const gated = runGated(ws3, base, task, launch2.argv, launch2.env, opts.agentBudget);
|
|
17989
18114
|
agent = gated.agent;
|
|
17990
18115
|
treatment = gated.treatment;
|
|
17991
18116
|
} else {
|
|
17992
|
-
agent = runUngated(
|
|
18117
|
+
agent = runUngated(ws3, launch2.argv, launch2.env, opts.agentBudget);
|
|
17993
18118
|
}
|
|
17994
18119
|
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
17995
|
-
const head = git6(["rev-parse", "HEAD"],
|
|
18120
|
+
const head = git6(["rev-parse", "HEAD"], ws3);
|
|
17996
18121
|
const protectedOnly = (rel) => {
|
|
17997
18122
|
try {
|
|
17998
|
-
return isProtected(rel, loadPolicyAt(base,
|
|
18123
|
+
return isProtected(rel, loadPolicyAt(base, ws3) ?? defaultPolicy());
|
|
17999
18124
|
} catch {
|
|
18000
18125
|
return true;
|
|
18001
18126
|
}
|
|
18002
18127
|
};
|
|
18003
|
-
const fingerprintBefore = treeFingerprint(
|
|
18004
|
-
const observed = observeOutcome(
|
|
18005
|
-
const movedDuringObservation = treeFingerprint(
|
|
18128
|
+
const fingerprintBefore = treeFingerprint(ws3, protectedOnly);
|
|
18129
|
+
const observed = observeOutcome(ws3, base, head, task);
|
|
18130
|
+
const movedDuringObservation = treeFingerprint(ws3, protectedOnly) !== fingerprintBefore;
|
|
18006
18131
|
const unmeasurable = agent.unmeasurable ?? observed.unmeasurable ?? (movedDuringObservation ? "NOT_QUIESCENT: the workspace changed while its outcome was being observed" : null);
|
|
18007
18132
|
const { unmeasurable: _agentUnmeasurable, ...agentRecord } = agent;
|
|
18008
18133
|
const outcome = unmeasurable === null ? observed.outcome : unobservedOutcome();
|
|
@@ -18010,7 +18135,7 @@ function runTrajectory(ledger, task, pair, arm, adapter, opts, sourceBase) {
|
|
|
18010
18135
|
const releasedGreen = outcome.visible_green && agentSucceeded && (treatment === null || treatment.disposition === "passed" && treatment.exit_code === 0);
|
|
18011
18136
|
return {
|
|
18012
18137
|
arm,
|
|
18013
|
-
workspace:
|
|
18138
|
+
workspace: ws3,
|
|
18014
18139
|
base,
|
|
18015
18140
|
head,
|
|
18016
18141
|
started_at: startedAt,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.25.0",
|
|
4
4
|
"description": "The deterministic agent-integrity gate. One ruleset, evaluated on the actual diff/commands as a verdict, enforced everywhere a change can be made.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "hexrift",
|
|
@@ -56,11 +56,11 @@
|
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@types/node": "^22.20.1",
|
|
59
|
-
"@types/picomatch": "^
|
|
60
|
-
"ajv": "^8.
|
|
61
|
-
"esbuild": "^0.
|
|
59
|
+
"@types/picomatch": "^4.0.3",
|
|
60
|
+
"ajv": "^8.20.0",
|
|
61
|
+
"esbuild": "^0.28.2",
|
|
62
62
|
"semver": "7.8.5",
|
|
63
63
|
"vitepress": "^2.0.0-alpha.19",
|
|
64
|
-
"vitest": "^
|
|
64
|
+
"vitest": "^5.0.0"
|
|
65
65
|
}
|
|
66
66
|
}
|