tamperward 1.9.0 → 1.10.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.
Files changed (2) hide show
  1. package/dist/cli/index.js +215 -135
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -153,7 +153,7 @@ function parseHunk(lines, start) {
153
153
  const oldLines = m[2] !== void 0 ? Number(m[2]) : 1;
154
154
  const newStart = Number(m[3]);
155
155
  const newLines = m[4] !== void 0 ? Number(m[4]) : 1;
156
- const out = [];
156
+ const out2 = [];
157
157
  let oldLine = oldStart;
158
158
  let newLine = newStart;
159
159
  let i = start + 1;
@@ -164,20 +164,20 @@ function parseHunk(lines, start) {
164
164
  const tag = l[0];
165
165
  const content = l.slice(1);
166
166
  if (tag === "+") {
167
- out.push({ type: "add", content, oldLine: null, newLine });
167
+ out2.push({ type: "add", content, oldLine: null, newLine });
168
168
  newLine++;
169
169
  } else if (tag === "-") {
170
- out.push({ type: "del", content, oldLine, newLine: null });
170
+ out2.push({ type: "del", content, oldLine, newLine: null });
171
171
  oldLine++;
172
172
  } else if (tag === " ") {
173
- out.push({ type: "context", content, oldLine, newLine });
173
+ out2.push({ type: "context", content, oldLine, newLine });
174
174
  oldLine++;
175
175
  newLine++;
176
176
  } else {
177
177
  break;
178
178
  }
179
179
  }
180
- return { hunk: { oldStart, oldLines, newStart, newLines, lines: out }, next: i };
180
+ return { hunk: { oldStart, oldLines, newStart, newLines, lines: out2 }, next: i };
181
181
  }
182
182
 
183
183
  // src/git/build.ts
@@ -190,9 +190,9 @@ function git(args, cwd) {
190
190
  stdio: ["ignore", "pipe", "pipe"]
191
191
  });
192
192
  } catch (e) {
193
- const err = e;
194
- const detail = String(err.stderr ?? "").trim();
195
- throw new Error(detail ? `git ${args[0]}: ${detail}` : err.message ?? `git ${args[0]} failed`);
193
+ const err2 = e;
194
+ const detail = String(err2.stderr ?? "").trim();
195
+ throw new Error(detail ? `git ${args[0]}: ${detail}` : err2.message ?? `git ${args[0]} failed`);
196
196
  }
197
197
  }
198
198
  function blobAt(rev, path, cwd) {
@@ -324,7 +324,7 @@ var noVerify = {
324
324
  surface: ["command"],
325
325
  certainty: "mechanical",
326
326
  run(changes, policy) {
327
- const out = [];
327
+ const out2 = [];
328
328
  for (const c of changes) {
329
329
  if (c.kind !== "command") continue;
330
330
  for (const seg of segments(c.raw)) {
@@ -341,7 +341,7 @@ var noVerify = {
341
341
  }
342
342
  }
343
343
  if (why) {
344
- out.push(
344
+ out2.push(
345
345
  makeFinding(RULE, policy, {
346
346
  message: `Command bypasses git hooks: ${why}.`,
347
347
  evidence: seg,
@@ -351,7 +351,7 @@ var noVerify = {
351
351
  }
352
352
  }
353
353
  }
354
- return out;
354
+ return out2;
355
355
  }
356
356
  };
357
357
 
@@ -373,13 +373,13 @@ import picomatch from "picomatch";
373
373
  var POLICY_FILE = ".tamperward.yml";
374
374
  var BLOCK_SINCE = {};
375
375
  function applyVersionGates(rules, version, gates = BLOCK_SINCE) {
376
- const out = { ...rules };
376
+ const out2 = { ...rules };
377
377
  for (const [rule, since] of Object.entries(gates)) {
378
- if (since > version && out[rule]?.severity === "block") {
379
- out[rule] = { ...out[rule], severity: "warn" };
378
+ if (since > version && out2[rule]?.severity === "block") {
379
+ out2[rule] = { ...out2[rule], severity: "warn" };
380
380
  }
381
381
  }
382
- return out;
382
+ return out2;
383
383
  }
384
384
  var cache = /* @__PURE__ */ new Map();
385
385
  function matcher(glob) {
@@ -410,12 +410,12 @@ function isIgnored(path, policy) {
410
410
  return matchesAny(path, policy.ignore);
411
411
  }
412
412
  function mergeProtected(base, user) {
413
- const out = { ...base };
413
+ const out2 = { ...base };
414
414
  for (const [cat, globs] of Object.entries(user ?? {})) {
415
- const have = new Set(out[cat] ?? []);
416
- out[cat] = [...out[cat] ?? [], ...(globs ?? []).filter((g) => !have.has(g))];
415
+ const have = new Set(out2[cat] ?? []);
416
+ out2[cat] = [...out2[cat] ?? [], ...(globs ?? []).filter((g) => !have.has(g))];
417
417
  }
418
- return out;
418
+ return out2;
419
419
  }
420
420
  function defaultPolicy(version = 1) {
421
421
  const p = {
@@ -522,7 +522,7 @@ var tsAnyCast = {
522
522
  surface: ["file"],
523
523
  certainty: "mechanical",
524
524
  run(changes, policy) {
525
- const out = [];
525
+ const out2 = [];
526
526
  for (const c of changes) {
527
527
  if (c.kind !== "file" || !isCodeFile(c.path)) continue;
528
528
  const inTest = protectedCategory(c.path, policy) === "tests";
@@ -539,7 +539,7 @@ var tsAnyCast = {
539
539
  if (dDouble > 0) blockReasons.push("`as unknown as` double cast");
540
540
  if (dSuppr > 0) blockReasons.push("@ts-ignore/@ts-expect-error/@ts-nocheck suppression");
541
541
  if (blockReasons.length) {
542
- out.push(
542
+ out2.push(
543
543
  makeFinding(inTest ? WARN_RULE : BLOCK_RULE, policy, {
544
544
  file: c.path,
545
545
  message: inTest ? `Type-checker escape in a test file: ${blockReasons.join("; ")} (test infrastructure \u2014 flagged, not blocked).` : `Type safety discarded: ${blockReasons.join("; ")}.`,
@@ -550,7 +550,7 @@ var tsAnyCast = {
550
550
  );
551
551
  }
552
552
  if (dBroad > 0) {
553
- out.push(
553
+ out2.push(
554
554
  makeFinding(WARN_RULE, policy, {
555
555
  file: c.path,
556
556
  message: `Type laundered to \`any\`: introduces ${dBroad} new \`any\`-typed value(s) in a type/generic position (e.g. : any, Record<string, any>).`,
@@ -564,7 +564,7 @@ var tsAnyCast = {
564
564
  }
565
565
  for (const l of addedLines(c)) {
566
566
  if (NARROW_LINE.test(l.content)) {
567
- out.push(
567
+ out2.push(
568
568
  makeFinding(inTest ? WARN_RULE : BLOCK_RULE, policy, {
569
569
  file: c.path,
570
570
  line: l.newLine ?? void 0,
@@ -575,7 +575,7 @@ var tsAnyCast = {
575
575
  })
576
576
  );
577
577
  } else if (BROAD_LINE.test(l.content)) {
578
- out.push(
578
+ out2.push(
579
579
  makeFinding(WARN_RULE, policy, {
580
580
  file: c.path,
581
581
  line: l.newLine ?? void 0,
@@ -588,7 +588,7 @@ var tsAnyCast = {
588
588
  }
589
589
  }
590
590
  }
591
- return out;
591
+ return out2;
592
592
  }
593
593
  };
594
594
 
@@ -604,13 +604,13 @@ var lintSuppression = {
604
604
  surface: ["file"],
605
605
  certainty: "mechanical",
606
606
  run(changes, policy) {
607
- const out = [];
607
+ const out2 = [];
608
608
  for (const c of changes) {
609
609
  if (c.kind !== "file" || !isCodeFile(c.path)) continue;
610
610
  for (const l of addedLines(c)) {
611
611
  for (const p of PATTERNS) {
612
612
  if (p.re.test(l.content)) {
613
- out.push(
613
+ out2.push(
614
614
  makeFinding(RULE2, policy, {
615
615
  file: c.path,
616
616
  line: l.newLine ?? void 0,
@@ -624,7 +624,7 @@ var lintSuppression = {
624
624
  }
625
625
  }
626
626
  }
627
- return out;
627
+ return out2;
628
628
  }
629
629
  };
630
630
 
@@ -647,14 +647,14 @@ var testSkip = {
647
647
  surface: ["file"],
648
648
  certainty: "mechanical",
649
649
  run(changes, policy) {
650
- const out = [];
650
+ const out2 = [];
651
651
  for (const c of changes) {
652
652
  if (c.kind !== "file") continue;
653
653
  if (!isProtected(c.path, policy, "tests")) continue;
654
654
  for (const l of addedLines(c)) {
655
655
  for (const p of PATTERNS2) {
656
656
  if (p.re.test(l.content)) {
657
- out.push(
657
+ out2.push(
658
658
  makeFinding(RULE3, policy, {
659
659
  file: c.path,
660
660
  line: l.newLine ?? void 0,
@@ -668,7 +668,7 @@ var testSkip = {
668
668
  }
669
669
  }
670
670
  }
671
- return out;
671
+ return out2;
672
672
  }
673
673
  };
674
674
 
@@ -691,7 +691,7 @@ function numericOf(e) {
691
691
  }
692
692
  function metricsOf(obj) {
693
693
  if (!ts2.isObjectLiteralExpression(obj)) return void 0;
694
- const out = {};
694
+ const out2 = {};
695
695
  let any = false;
696
696
  for (const p of obj.properties) {
697
697
  if (!ts2.isPropertyAssignment(p)) continue;
@@ -699,10 +699,10 @@ function metricsOf(obj) {
699
699
  if (k === null || !isMetric(k)) continue;
700
700
  const n = numericOf(p.initializer);
701
701
  if (n === void 0) continue;
702
- out[k] = n;
702
+ out2[k] = n;
703
703
  any = true;
704
704
  }
705
- return any ? out : void 0;
705
+ return any ? out2 : void 0;
706
706
  }
707
707
  function underCoverageKey(node) {
708
708
  for (let p = node.parent; p; p = p.parent) {
@@ -744,41 +744,41 @@ function parseThresholds(src) {
744
744
  return res;
745
745
  }
746
746
  var eff = (t, path) => t.paths.get(norm(path)) ?? t.global;
747
- function compareMetrics(scope, before, after, out) {
747
+ function compareMetrics(scope, before, after, out2) {
748
748
  if (!before) return;
749
749
  for (const m of METRICS) {
750
750
  const bv = before[m];
751
751
  if (bv === void 0) continue;
752
752
  const av = after?.[m];
753
- if (av === void 0) out.push(`${scope} ${m} threshold removed (was ${bv})`);
754
- else if (av < bv) out.push(`${scope} ${m} threshold lowered ${bv} \u2192 ${av}`);
753
+ if (av === void 0) out2.push(`${scope} ${m} threshold removed (was ${bv})`);
754
+ else if (av < bv) out2.push(`${scope} ${m} threshold lowered ${bv} \u2192 ${av}`);
755
755
  }
756
756
  }
757
757
  function weakenings(before, after) {
758
- const out = [];
758
+ const out2 = [];
759
759
  if (before.present && !after.present) {
760
- out.push("the coverage threshold gate was removed");
761
- return out;
760
+ out2.push("the coverage threshold gate was removed");
761
+ return out2;
762
762
  }
763
- compareMetrics("global", before.global, after.global, out);
763
+ compareMetrics("global", before.global, after.global, out2);
764
764
  const paths = /* @__PURE__ */ new Set([...before.paths.keys(), ...after.paths.keys()]);
765
765
  for (const p of paths) {
766
- compareMetrics(`required coverage for ${p}:`, eff(before, p), eff(after, p), out);
766
+ compareMetrics(`required coverage for ${p}:`, eff(before, p), eff(after, p), out2);
767
767
  }
768
- return out;
768
+ return out2;
769
769
  }
770
770
  var coverageLowering = {
771
771
  id: RULE4,
772
772
  surface: ["file"],
773
773
  certainty: "mechanical",
774
774
  run(changes, policy) {
775
- const out = [];
775
+ const out2 = [];
776
776
  for (const c of changes) {
777
777
  if (c.kind !== "file") continue;
778
778
  if (!isProtected(c.path, policy, "config")) continue;
779
779
  if (c.after != null && c.op !== "delete") {
780
780
  for (const reason of weakenings(parseThresholds(c.before ?? ""), parseThresholds(c.after))) {
781
- out.push(
781
+ out2.push(
782
782
  makeFinding(RULE4, policy, {
783
783
  file: c.path,
784
784
  message: `Coverage gate weakened: ${reason}.`,
@@ -789,7 +789,7 @@ var coverageLowering = {
789
789
  }
790
790
  } else if (c.op === "delete" || c.after === "" && (c.before ?? "").length > 0) {
791
791
  if (parseThresholds(c.before ?? "").present) {
792
- out.push(
792
+ out2.push(
793
793
  makeFinding(RULE4, policy, {
794
794
  file: c.path,
795
795
  message: "Coverage gate weakened: the coverage config was deleted.",
@@ -803,7 +803,7 @@ var coverageLowering = {
803
803
  const removed = removedLines(c);
804
804
  const removedCoverage = removed.find((l) => /--coverage\b/.test(l.content));
805
805
  if (removedCoverage && !added.some((l) => /--coverage\b/.test(l.content))) {
806
- out.push(
806
+ out2.push(
807
807
  makeFinding(RULE4, policy, {
808
808
  file: c.path,
809
809
  message: "The --coverage flag was removed from a test script.",
@@ -814,7 +814,7 @@ var coverageLowering = {
814
814
  }
815
815
  for (const l of added) {
816
816
  if (/--passWithNoTests\b/.test(l.content)) {
817
- out.push(
817
+ out2.push(
818
818
  makeFinding(RULE4, policy, {
819
819
  file: c.path,
820
820
  line: l.newLine ?? void 0,
@@ -837,7 +837,7 @@ var coverageLowering = {
837
837
  if (!m) continue;
838
838
  const ov = oldNums.get(m[1]);
839
839
  if (ov !== void 0 && Number(m[2]) < ov) {
840
- out.push(
840
+ out2.push(
841
841
  makeFinding(RULE4, policy, {
842
842
  file: c.path,
843
843
  line: l.newLine ?? void 0,
@@ -851,7 +851,7 @@ var coverageLowering = {
851
851
  }
852
852
  }
853
853
  const seen = /* @__PURE__ */ new Set();
854
- return out.filter((f) => {
854
+ return out2.filter((f) => {
855
855
  const k = `${f.rule}|${f.message}`;
856
856
  if (seen.has(k)) return false;
857
857
  seen.add(k);
@@ -894,7 +894,7 @@ var ciTampering = {
894
894
  surface: ["file"],
895
895
  certainty: "mechanical",
896
896
  run(changes, policy) {
897
- const out = [];
897
+ const out2 = [];
898
898
  for (const c of changes) {
899
899
  if (c.kind !== "file") continue;
900
900
  if (!isProtected(c.path, policy, "ci")) continue;
@@ -902,7 +902,7 @@ var ciTampering = {
902
902
  const coe = l.content.match(/^\s*-?\s*continue-on-error:\s*(.+?)\s*$/);
903
903
  const cond = l.content.match(/^\s*-?\s*if:\s*(.+?)\s*$/);
904
904
  if (coe && isTruthy(coe[1])) {
905
- out.push(
905
+ out2.push(
906
906
  makeFinding(RULE5, policy, {
907
907
  file: c.path,
908
908
  line: l.newLine ?? void 0,
@@ -912,7 +912,7 @@ var ciTampering = {
912
912
  })
913
913
  );
914
914
  } else if (cond && isAlwaysFalse(cond[1])) {
915
- out.push(
915
+ out2.push(
916
916
  makeFinding(RULE5, policy, {
917
917
  file: c.path,
918
918
  line: l.newLine ?? void 0,
@@ -931,7 +931,7 @@ var ciTampering = {
931
931
  const isCheckCommand = !isUsesLine && !/^\s*-?\s*uses:/.test(l.content) && (YAML_KEY.test(l.content) ? /^\s*-?\s*run:/.test(l.content) : true) && invokesCheck(l.content);
932
932
  if (!isUsesLine && !isCheckCommand) continue;
933
933
  if (afterCores.has(commandCore(l.content))) continue;
934
- out.push(
934
+ out2.push(
935
935
  makeFinding(RULE5, policy, {
936
936
  file: c.path,
937
937
  message: isUsesLine ? "A CI check step (test/lint/typecheck) was removed." : "A CI check command was removed from a run block.",
@@ -941,7 +941,7 @@ var ciTampering = {
941
941
  );
942
942
  }
943
943
  }
944
- return out;
944
+ return out2;
945
945
  }
946
946
  };
947
947
 
@@ -1044,13 +1044,13 @@ var hookTampering = {
1044
1044
  surface: ["file", "command"],
1045
1045
  certainty: "mechanical",
1046
1046
  run(changes, policy) {
1047
- const out = [];
1047
+ const out2 = [];
1048
1048
  for (const c of changes) {
1049
1049
  if (c.kind === "file") {
1050
1050
  const targetsHook = isProtected(c.path, policy, "hooks") || (c.oldPath ? isProtected(c.oldPath, policy, "hooks") : false);
1051
1051
  if (!targetsHook) continue;
1052
1052
  if (c.op === "delete") {
1053
- out.push(
1053
+ out2.push(
1054
1054
  makeFinding(RULE6, policy, {
1055
1055
  file: c.path,
1056
1056
  message: "A protected hook/policy file was deleted.",
@@ -1059,7 +1059,7 @@ var hookTampering = {
1059
1059
  })
1060
1060
  );
1061
1061
  } else if (c.op === "rename") {
1062
- out.push(
1062
+ out2.push(
1063
1063
  makeFinding(RULE6, policy, {
1064
1064
  file: c.path,
1065
1065
  message: `A protected hook/policy file was renamed (${c.oldPath} \u2192 ${c.path}).`,
@@ -1069,7 +1069,7 @@ var hookTampering = {
1069
1069
  );
1070
1070
  } else if (c.path.endsWith(POLICY_FILE) && c.before != null && c.after != null && policyWeakening(c.before, c.after) !== null) {
1071
1071
  for (const reason of policyWeakening(c.before, c.after) ?? []) {
1072
- out.push(
1072
+ out2.push(
1073
1073
  makeFinding(RULE6, policy, {
1074
1074
  file: c.path,
1075
1075
  message: `The policy was weakened: ${reason}.`,
@@ -1084,7 +1084,7 @@ var hookTampering = {
1084
1084
  const live = added.filter((l) => !isShellComment(l.content));
1085
1085
  const earlyExit = added.find((l) => /^\s*exit\s+0\s*$/.test(l.content));
1086
1086
  if (earlyExit) {
1087
- out.push(
1087
+ out2.push(
1088
1088
  makeFinding(RULE6, policy, {
1089
1089
  file: c.path,
1090
1090
  line: earlyExit.newLine ?? void 0,
@@ -1099,7 +1099,7 @@ var hookTampering = {
1099
1099
  const m = l.content.match(HOOK_INVOCATION);
1100
1100
  if (!m) continue;
1101
1101
  if (live.some((a) => a.content.includes(m[0]))) continue;
1102
- out.push(
1102
+ out2.push(
1103
1103
  makeFinding(RULE6, policy, {
1104
1104
  file: c.path,
1105
1105
  message: `A check invocation (\`${m[0]}\`) was removed from a protected hook.`,
@@ -1119,7 +1119,7 @@ var hookTampering = {
1119
1119
  for (const l of addedLines(c)) {
1120
1120
  const m = l.content.match(INLINE);
1121
1121
  if (m && m[2] === "warn" && removedSeverity.get(m[1]) === "block") {
1122
- out.push(
1122
+ out2.push(
1123
1123
  makeFinding(RULE6, policy, {
1124
1124
  file: c.path,
1125
1125
  line: l.newLine ?? void 0,
@@ -1129,7 +1129,7 @@ var hookTampering = {
1129
1129
  })
1130
1130
  );
1131
1131
  } else if (/enabled:\s*false\b/.test(l.content)) {
1132
- out.push(
1132
+ out2.push(
1133
1133
  makeFinding(RULE6, policy, {
1134
1134
  file: c.path,
1135
1135
  line: l.newLine ?? void 0,
@@ -1158,7 +1158,7 @@ var hookTampering = {
1158
1158
  why = "a shell rewrite (tee/sed/cp/dd/install/mv) replaces a protected hook in place";
1159
1159
  }
1160
1160
  if (why) {
1161
- out.push(
1161
+ out2.push(
1162
1162
  makeFinding(RULE6, policy, {
1163
1163
  message: `Hook tampering via shell: ${why}.`,
1164
1164
  evidence: seg,
@@ -1169,7 +1169,7 @@ var hookTampering = {
1169
1169
  }
1170
1170
  }
1171
1171
  }
1172
- return out;
1172
+ return out2;
1173
1173
  }
1174
1174
  };
1175
1175
 
@@ -1197,19 +1197,19 @@ function countTestBlocks(src) {
1197
1197
  return n;
1198
1198
  }
1199
1199
  function significantLines(src) {
1200
- const out = /* @__PURE__ */ new Set();
1200
+ const out2 = /* @__PURE__ */ new Set();
1201
1201
  for (const raw of src.split("\n")) {
1202
1202
  const l = raw.trim();
1203
- if (l.length >= 10 && !/^(import\b|export\s|\/\/|\*|\/\*|}\)?;?$)/.test(l)) out.add(l);
1203
+ if (l.length >= 10 && !/^(import\b|export\s|\/\/|\*|\/\*|}\)?;?$)/.test(l)) out2.add(l);
1204
1204
  }
1205
- return out;
1205
+ return out2;
1206
1206
  }
1207
1207
  var testDeletion = {
1208
1208
  id: RULE7,
1209
1209
  surface: ["file", "command"],
1210
1210
  certainty: "mechanical",
1211
1211
  run(changes, policy) {
1212
- const out = [];
1212
+ const out2 = [];
1213
1213
  const addedTestLines = /* @__PURE__ */ new Set();
1214
1214
  let addedTestBlocks = 0;
1215
1215
  for (const c of changes) {
@@ -1230,7 +1230,7 @@ var testDeletion = {
1230
1230
  const isTest = isProtected(c.path, policy, "tests");
1231
1231
  if (c.op === "delete" && isTest) {
1232
1232
  if (c.before != null && isRelocation(c.before)) continue;
1233
- out.push(
1233
+ out2.push(
1234
1234
  makeFinding(RULE7, policy, {
1235
1235
  file: c.path,
1236
1236
  message: "A test file was deleted.",
@@ -1239,7 +1239,7 @@ var testDeletion = {
1239
1239
  })
1240
1240
  );
1241
1241
  } else if (c.op === "rename" && c.oldPath && isProtected(c.oldPath, policy, "tests") && !isTest) {
1242
- out.push(
1242
+ out2.push(
1243
1243
  makeFinding(RULE7, policy, {
1244
1244
  file: c.path,
1245
1245
  message: `A test file was renamed out of the test glob (${c.oldPath} \u2192 ${c.path}).`,
@@ -1251,7 +1251,7 @@ var testDeletion = {
1251
1251
  const before = countTestBlocks(c.before);
1252
1252
  const after = countTestBlocks(c.after);
1253
1253
  if (after < before) {
1254
- out.push(
1254
+ out2.push(
1255
1255
  makeFinding(RULE7, policy, {
1256
1256
  file: c.path,
1257
1257
  message: `Test blocks removed: ${before} \u2192 ${after} it()/test() in this spec.`,
@@ -1286,7 +1286,7 @@ var testDeletion = {
1286
1286
  }
1287
1287
  }
1288
1288
  if (why) {
1289
- out.push(
1289
+ out2.push(
1290
1290
  makeFinding(RULE7, policy, {
1291
1291
  message: `Test removed via shell: ${why}.`,
1292
1292
  evidence,
@@ -1297,7 +1297,7 @@ var testDeletion = {
1297
1297
  }
1298
1298
  }
1299
1299
  }
1300
- return out;
1300
+ return out2;
1301
1301
  }
1302
1302
  };
1303
1303
 
@@ -1306,19 +1306,19 @@ var RULE8 = "test-content-removal";
1306
1306
  var MIN_REMOVED_LINES = 3;
1307
1307
  var ws = (s) => s.replace(/\s+/g, "");
1308
1308
  function significantLinesOrdered(src) {
1309
- const out = [];
1309
+ const out2 = [];
1310
1310
  for (const raw of src.split("\n")) {
1311
1311
  const l = raw.trim();
1312
- if (l.length >= 10 && !/^(import\b|export\s|\/\/|\*|\/\*|}\)?;?$)/.test(l)) out.push(l);
1312
+ if (l.length >= 10 && !/^(import\b|export\s|\/\/|\*|\/\*|}\)?;?$)/.test(l)) out2.push(l);
1313
1313
  }
1314
- return out;
1314
+ return out2;
1315
1315
  }
1316
1316
  var testContentRemoval = {
1317
1317
  id: RULE8,
1318
1318
  surface: ["file"],
1319
1319
  certainty: "mechanical",
1320
1320
  run(changes, policy) {
1321
- const out = [];
1321
+ const out2 = [];
1322
1322
  let keptPool = "";
1323
1323
  for (const c of changes) {
1324
1324
  if (c.kind === "file" && c.after != null && isProtected(c.path, policy, "tests")) {
@@ -1341,7 +1341,7 @@ var testContentRemoval = {
1341
1341
  gone.push(line);
1342
1342
  }
1343
1343
  if (gone.length >= MIN_REMOVED_LINES && netDrop >= MIN_REMOVED_LINES) {
1344
- out.push(
1344
+ out2.push(
1345
1345
  makeFinding(RULE8, policy, {
1346
1346
  file: c.path,
1347
1347
  message: `Test content removed from a surviving spec: ${gone.length} significant line(s) gone with no test block deleted.`,
@@ -1351,7 +1351,7 @@ var testContentRemoval = {
1351
1351
  );
1352
1352
  }
1353
1353
  }
1354
- return out;
1354
+ return out2;
1355
1355
  }
1356
1356
  };
1357
1357
 
@@ -1374,8 +1374,8 @@ var snapshotRewrite = {
1374
1374
  surface: ["command", "file"],
1375
1375
  certainty: "mechanical",
1376
1376
  run(changes, policy) {
1377
- const out = [];
1378
- const warn = (input) => out.push(makeFinding(RULE9, policy, { ...input, defaultSeverity: "warn" }));
1377
+ const out2 = [];
1378
+ const warn = (input) => out2.push(makeFinding(RULE9, policy, { ...input, defaultSeverity: "warn" }));
1379
1379
  for (const c of changes) {
1380
1380
  if (c.kind === "command") {
1381
1381
  for (const seg of segments(c.raw)) {
@@ -1408,7 +1408,7 @@ var snapshotRewrite = {
1408
1408
  remediation: "If the intended output genuinely changed, a human should confirm the new expectation against the requirement; if the test was failing, fix the code instead of re-recording the expectation from it."
1409
1409
  });
1410
1410
  }
1411
- return out;
1411
+ return out2;
1412
1412
  }
1413
1413
  };
1414
1414
 
@@ -1474,18 +1474,18 @@ function activeChanges(changes, policy) {
1474
1474
  }
1475
1475
  function evaluate(changes, policy, detectors = allDetectors, view) {
1476
1476
  const active = activeChanges(changes, policy);
1477
- const out = [];
1477
+ const out2 = [];
1478
1478
  for (const d of detectors) {
1479
1479
  if (!isEnabled(d.id, policy)) continue;
1480
1480
  try {
1481
- out.push(...d.run(active, policy, view));
1481
+ out2.push(...d.run(active, policy, view));
1482
1482
  } catch (e) {
1483
1483
  process.stderr.write(`tamperward: detector "${d.id}" errored and was skipped: ${String(e)}
1484
1484
  `);
1485
1485
  }
1486
1486
  }
1487
1487
  const seen = /* @__PURE__ */ new Set();
1488
- const deduped = out.filter((f) => {
1488
+ const deduped = out2.filter((f) => {
1489
1489
  const k = key(f);
1490
1490
  if (seen.has(k)) return false;
1491
1491
  seen.add(k);
@@ -1571,16 +1571,16 @@ function ledgerPath(cwd, policy) {
1571
1571
  function readLedger(cwd, policy) {
1572
1572
  const p = ledgerPath(cwd, policy);
1573
1573
  if (!existsSync2(p)) return [];
1574
- const out = [];
1574
+ const out2 = [];
1575
1575
  for (const line of readFileSync3(p, "utf8").split("\n")) {
1576
1576
  if (!line.trim()) continue;
1577
1577
  try {
1578
1578
  const e = JSON.parse(line);
1579
- if (e && typeof e.fingerprint === "string") out.push(e);
1579
+ if (e && typeof e.fingerprint === "string") out2.push(e);
1580
1580
  } catch {
1581
1581
  }
1582
1582
  }
1583
- return out;
1583
+ return out2;
1584
1584
  }
1585
1585
  function appendEntry(cwd, policy, e) {
1586
1586
  const p = ledgerPath(cwd, policy);
@@ -1639,18 +1639,18 @@ function paint(s, code, on) {
1639
1639
  return on ? code + s + RESET : s;
1640
1640
  }
1641
1641
  function wrap(text, width) {
1642
- const out = [];
1642
+ const out2 = [];
1643
1643
  let cur = "";
1644
1644
  for (const w of text.split(/\s+/).filter(Boolean)) {
1645
1645
  if (cur === "") cur = w;
1646
1646
  else if (cur.length + 1 + w.length <= width) cur += " " + w;
1647
1647
  else {
1648
- out.push(cur);
1648
+ out2.push(cur);
1649
1649
  cur = w;
1650
1650
  }
1651
1651
  }
1652
- if (cur) out.push(cur);
1653
- return out.length ? out : [""];
1652
+ if (cur) out2.push(cur);
1653
+ return out2.length ? out2 : [""];
1654
1654
  }
1655
1655
  function clip(s, max) {
1656
1656
  const one = s.replace(/\s+/g, " ").trim();
@@ -1682,15 +1682,15 @@ function renderText(input, opts) {
1682
1682
  const blocks = findings.filter((f) => f.severity === "block");
1683
1683
  const warns = findings.filter((f) => f.severity === "warn");
1684
1684
  const c = opts.colour;
1685
- const out = [];
1685
+ const out2 = [];
1686
1686
  if (findings.length === 0) {
1687
1687
  return `${paint("tamperward: clean", GREEN, c)} \u2014 no integrity findings (${scopePhrase(scanned, ignoredFiles)}).
1688
1688
  `;
1689
1689
  }
1690
1690
  const headColour = blocks.length ? RED : YELLOW;
1691
- out.push(paint(`tamperward: ${countsPhrase(blocks.length, warns.length)}`, BOLD + headColour, c));
1692
- out.push(paint(`(${scopePhrase(scanned, ignoredFiles)})`, DIM, c));
1693
- out.push("");
1691
+ out2.push(paint(`tamperward: ${countsPhrase(blocks.length, warns.length)}`, BOLD + headColour, c));
1692
+ out2.push(paint(`(${scopePhrase(scanned, ignoredFiles)})`, DIM, c));
1693
+ out2.push("");
1694
1694
  const LABEL = "sign-off".length;
1695
1695
  const pad = (k) => (k + " ".repeat(LABEL)).slice(0, LABEL);
1696
1696
  const body = Math.max(20, opts.width - LABEL - 6);
@@ -1698,22 +1698,22 @@ function renderText(input, opts) {
1698
1698
  const isBlock = f.severity === "block";
1699
1699
  const mark = paint(isBlock ? "BLOCK" : "warn ", isBlock ? BOLD + RED : YELLOW, c);
1700
1700
  const loc = f.file ? `${f.file}${f.line ? `:${f.line}` : ""}` : "(command)";
1701
- out.push(` ${mark} ${paint(f.rule, BOLD, c)} ${paint(loc, DIM, c)}`);
1702
- for (const l of wrap(f.message, opts.width - 4)) out.push(` ${l}`);
1701
+ out2.push(` ${mark} ${paint(f.rule, BOLD, c)} ${paint(loc, DIM, c)}`);
1702
+ for (const l of wrap(f.message, opts.width - 4)) out2.push(` ${l}`);
1703
1703
  const row = (key2, lines) => {
1704
- lines.forEach((l, i) => out.push(` ${paint(pad(i === 0 ? key2 : ""), DIM, c)} ${l}`));
1704
+ lines.forEach((l, i) => out2.push(` ${paint(pad(i === 0 ? key2 : ""), DIM, c)} ${l}`));
1705
1705
  };
1706
1706
  row("evidence", [paint(clip(f.evidence, body), DIM, c)]);
1707
1707
  row("instead", wrap(f.remediation, body));
1708
1708
  if (f.signoff.required) row("sign-off", [f.signoff.command]);
1709
- out.push("");
1709
+ out2.push("");
1710
1710
  }
1711
1711
  if (blocks.length > 0) {
1712
- out.push(
1712
+ out2.push(
1713
1713
  wrap(SIGNOFF_NOTE, opts.width - 2).map((l) => paint(l, DIM, c)).join("\n")
1714
1714
  );
1715
1715
  }
1716
- return out.join("\n").replace(/\n+$/, "") + "\n";
1716
+ return out2.join("\n").replace(/\n+$/, "") + "\n";
1717
1717
  }
1718
1718
 
1719
1719
  // src/cli/render/github.ts
@@ -1761,7 +1761,7 @@ function renderSummary(input, env = process.env) {
1761
1761
  No integrity findings (${scope}).
1762
1762
  `;
1763
1763
  }
1764
- const out = [
1764
+ const out2 = [
1765
1765
  `## Tamperward: ${countsPhrase(blocks.length, warns.length)}`,
1766
1766
  "",
1767
1767
  `${scope}. Every finding below is also annotated on the diff in **Files changed**.`,
@@ -1771,21 +1771,21 @@ No integrity findings (${scope}).
1771
1771
  ];
1772
1772
  for (const f of findings) {
1773
1773
  const sev = f.severity === "block" ? "**BLOCK**" : "warn";
1774
- out.push(
1774
+ out2.push(
1775
1775
  `| ${sev} | \`${cell(f.rule)}\` | ${locationCell(f, env)} | ${cell(f.message)} | ${cell(f.remediation)} |`
1776
1776
  );
1777
1777
  }
1778
1778
  const needSignoff = findings.filter((f) => f.signoff.required);
1779
1779
  if (needSignoff.length) {
1780
- out.push("", "### Clearing a blocking finding", "", SIGNOFF_NOTE, "");
1780
+ out2.push("", "### Clearing a blocking finding", "", SIGNOFF_NOTE, "");
1781
1781
  const seen = /* @__PURE__ */ new Set();
1782
1782
  for (const f of needSignoff) {
1783
1783
  if (seen.has(f.signoff.command)) continue;
1784
1784
  seen.add(f.signoff.command);
1785
- out.push(`- \`${cell(f.signoff.command)}\``);
1785
+ out2.push(`- \`${cell(f.signoff.command)}\``);
1786
1786
  }
1787
1787
  }
1788
- return out.join("\n") + "\n";
1788
+ return out2.join("\n") + "\n";
1789
1789
  }
1790
1790
  function writeSummary(md, env = process.env) {
1791
1791
  const path = env.GITHUB_STEP_SUMMARY;
@@ -1939,8 +1939,8 @@ function synthFileChange(displayPath, before, after) {
1939
1939
  try {
1940
1940
  raw = execFileSync2("git", ["diff", "--no-index", "--no-color", a, b], { encoding: "utf8" });
1941
1941
  } catch (e) {
1942
- const err = e;
1943
- raw = err.stdout ? String(err.stdout) : "";
1942
+ const err2 = e;
1943
+ raw = err2.stdout ? String(err2.stdout) : "";
1944
1944
  }
1945
1945
  } finally {
1946
1946
  rmSync(dir, { recursive: true, force: true });
@@ -2074,7 +2074,7 @@ function ptreePath(cwd, sessionId) {
2074
2074
  }
2075
2075
  var sha = (buf) => createHash2("sha256").update(buf).digest("hex").slice(0, 16);
2076
2076
  function snapshotProtected(cwd, policy, prev) {
2077
- const out = {};
2077
+ const out2 = {};
2078
2078
  const walk = (rel) => {
2079
2079
  const abs2 = rel ? join6(cwd, rel) : cwd;
2080
2080
  let names;
@@ -2096,7 +2096,7 @@ function snapshotProtected(cwd, policy, prev) {
2096
2096
  else if (st.isFile() && isProtected(r, policy)) {
2097
2097
  const p = prev?.[r];
2098
2098
  if (p && p.mtimeMs === st.mtimeMs && p.size === st.size && p.mode === st.mode) {
2099
- out[r] = p;
2099
+ out2[r] = p;
2100
2100
  } else {
2101
2101
  let hash = "";
2102
2102
  try {
@@ -2104,13 +2104,13 @@ function snapshotProtected(cwd, policy, prev) {
2104
2104
  } catch {
2105
2105
  hash = "unreadable";
2106
2106
  }
2107
- out[r] = { hash, mode: st.mode, size: st.size, mtimeMs: st.mtimeMs };
2107
+ out2[r] = { hash, mode: st.mode, size: st.size, mtimeMs: st.mtimeMs };
2108
2108
  }
2109
2109
  }
2110
2110
  }
2111
2111
  };
2112
2112
  walk("");
2113
- return out;
2113
+ return out2;
2114
2114
  }
2115
2115
  function driftBetween(expected, current) {
2116
2116
  const changed = [];
@@ -2237,9 +2237,9 @@ function runWatch(args) {
2237
2237
  else if (args[i] === "--log" && args[i + 1]) log = args[++i];
2238
2238
  }
2239
2239
  const policy = loadPolicy(dir);
2240
- const out = log ?? defaultEventLog(dir);
2241
- startWatcher(dir, out, policy);
2242
- process.stdout.write(`tamperward watch: recording protected-file events under ${dir} -> ${out}
2240
+ const out2 = log ?? defaultEventLog(dir);
2241
+ startWatcher(dir, out2, policy);
2242
+ process.stdout.write(`tamperward watch: recording protected-file events under ${dir} -> ${out2}
2243
2243
  `);
2244
2244
  return -1;
2245
2245
  }
@@ -2274,7 +2274,7 @@ function transientFindings(events, persistentPaths, policy, finalHash) {
2274
2274
  arr.push(e);
2275
2275
  byPath.set(e.path, arr);
2276
2276
  }
2277
- const out = [];
2277
+ const out2 = [];
2278
2278
  const strict = process.env.TAMPERWARD_TRANSIENT === "block";
2279
2279
  for (const [path, evs] of byPath) {
2280
2280
  if (persistentPaths.has(path)) continue;
@@ -2297,9 +2297,9 @@ function transientFindings(events, persistentPaths, policy, finalHash) {
2297
2297
  f.severity = "block";
2298
2298
  f.signoff.required = true;
2299
2299
  }
2300
- out.push(f);
2300
+ out2.push(f);
2301
2301
  }
2302
- return out;
2302
+ return out2;
2303
2303
  }
2304
2304
 
2305
2305
  // src/cli/hook.ts
@@ -2775,10 +2775,10 @@ function materialize(cwd, dest) {
2775
2775
  continue;
2776
2776
  }
2777
2777
  if (!st.isFile()) continue;
2778
- const out = join9(dest, rel);
2779
- mkdirSync6(dirname4(out), { recursive: true });
2780
- cpSync(src, out);
2781
- chmodSync2(out, st.mode);
2778
+ const out2 = join9(dest, rel);
2779
+ mkdirSync6(dirname4(out2), { recursive: true });
2780
+ cpSync(src, out2);
2781
+ chmodSync2(out2, st.mode);
2782
2782
  }
2783
2783
  const nm = join9(cwd, "node_modules");
2784
2784
  if (existsSync8(nm)) symlinkSync(nm, join9(dest, "node_modules"), "dir");
@@ -2792,9 +2792,9 @@ function overlayPristine(cwd, base, dest, policy) {
2792
2792
  if (!isOverlay(rel)) continue;
2793
2793
  baseProtected.add(rel);
2794
2794
  const content = execFileSync4("git", ["show", `${base}:${rel}`], { cwd, maxBuffer: 1 << 28 });
2795
- const out = join9(dest, rel);
2796
- mkdirSync6(dirname4(out), { recursive: true });
2797
- writeFileSync6(out, content);
2795
+ const out2 = join9(dest, rel);
2796
+ mkdirSync6(dirname4(out2), { recursive: true });
2797
+ writeFileSync6(out2, content);
2798
2798
  restored++;
2799
2799
  }
2800
2800
  let kept = 0;
@@ -2817,17 +2817,17 @@ function runSuite(dir, cmd, budgetSecs) {
2817
2817
  }
2818
2818
  function runVerify(opts) {
2819
2819
  const cwd = opts.cwd ?? process.cwd();
2820
- const out = (s) => void process.stdout.write(s + "\n");
2820
+ const out2 = (s) => void process.stdout.write(s + "\n");
2821
2821
  let policy;
2822
2822
  try {
2823
2823
  policy = loadPolicy(cwd);
2824
2824
  } catch (e) {
2825
- out(`verify: cannot load policy (${e instanceof Error ? e.message : String(e)}) \u2014 failing closed`);
2825
+ out2(`verify: cannot load policy (${e instanceof Error ? e.message : String(e)}) \u2014 failing closed`);
2826
2826
  return 2;
2827
2827
  }
2828
2828
  const cmd = opts.cmd ?? policy.verify?.command;
2829
2829
  if (!cmd) {
2830
- out("verify: no suite command \u2014 set policy `verify: { command: ... }` or pass --cmd");
2830
+ out2("verify: no suite command \u2014 set policy `verify: { command: ... }` or pass --cmd");
2831
2831
  return 2;
2832
2832
  }
2833
2833
  const budget = opts.budget ?? policy.verify?.budget ?? 300;
@@ -2835,7 +2835,7 @@ function runVerify(opts) {
2835
2835
  try {
2836
2836
  base = resolveBase(opts.base ?? "HEAD", cwd);
2837
2837
  } catch {
2838
- out(`verify: cannot resolve base rev "${opts.base ?? "HEAD"}" \u2014 failing closed`);
2838
+ out2(`verify: cannot resolve base rev "${opts.base ?? "HEAD"}" \u2014 failing closed`);
2839
2839
  return 2;
2840
2840
  }
2841
2841
  const work = mkdtempSync2(join9(tmpdir2(), "tw-verify-"));
@@ -2851,7 +2851,7 @@ function runVerify(opts) {
2851
2851
  [restored, kept] = overlayPristine(cwd, base, priDir, policy);
2852
2852
  } catch (e) {
2853
2853
  if (!opts.keep) rmSync2(work, { recursive: true, force: true });
2854
- out(`verify: could not materialize (${e instanceof Error ? e.message : String(e)}) \u2014 failing closed`);
2854
+ out2(`verify: could not materialize (${e instanceof Error ? e.message : String(e)}) \u2014 failing closed`);
2855
2855
  return 2;
2856
2856
  }
2857
2857
  const visible = runSuite(visDir, cmd, budget);
@@ -2873,7 +2873,7 @@ function runVerify(opts) {
2873
2873
  code = 1;
2874
2874
  }
2875
2875
  if (opts.json) {
2876
- out(
2876
+ out2(
2877
2877
  JSON.stringify({
2878
2878
  verdict: verdict2,
2879
2879
  base,
@@ -2893,8 +2893,8 @@ function runVerify(opts) {
2893
2893
  SUITE_RED: `suite red: the visible suite fails (exit ${visible.exit}) \u2014 fix the code first (pristine exit ${pristine.exit}).`,
2894
2894
  BUDGET_EXCEEDED: `budget exceeded (${budget}s): could not verify \u2014 failing closed, not open.`
2895
2895
  };
2896
- out(`tamperward verify \u2014 ${lines[verdict2]}`);
2897
- if (kept > 0) out(`(${kept} protected file(s) added since ${base.slice(0, 10)} were kept in the pristine run.)`);
2896
+ out2(`tamperward verify \u2014 ${lines[verdict2]}`);
2897
+ if (kept > 0) out2(`(${kept} protected file(s) added since ${base.slice(0, 10)} were kept in the pristine run.)`);
2898
2898
  }
2899
2899
  return code;
2900
2900
  }
@@ -2912,6 +2912,76 @@ function parseVerify(args) {
2912
2912
  return o;
2913
2913
  }
2914
2914
 
2915
+ // src/cli/run.ts
2916
+ import { execFileSync as execFileSync5, spawnSync as spawnSync2 } from "node:child_process";
2917
+ var out = (s) => process.stdout.write(s + "\n");
2918
+ var err = (s) => process.stderr.write(s + "\n");
2919
+ function git3(args, cwd) {
2920
+ return execFileSync5("git", args, { cwd, encoding: "utf8", maxBuffer: 1 << 28 });
2921
+ }
2922
+ function runEnvelope(opts) {
2923
+ const cwd = opts.cwd ?? process.cwd();
2924
+ if (!opts.argv.length) {
2925
+ err("tamperward run: no agent command given (usage: tamperward run [options] -- <command...>)");
2926
+ return 2;
2927
+ }
2928
+ let base;
2929
+ try {
2930
+ base = git3(["rev-parse", "--verify", `${opts.base ?? "HEAD"}^{commit}`], cwd).trim();
2931
+ } catch {
2932
+ err(`tamperward run: cannot resolve trusted base ${opts.base ?? "HEAD"} \u2014 failing closed.`);
2933
+ return 2;
2934
+ }
2935
+ if (!opts.allowDirty) {
2936
+ const dirty = git3(["status", "--porcelain"], cwd).trim();
2937
+ if (dirty) {
2938
+ err("tamperward run: working tree is dirty before the agent starts \u2014 the envelope cannot");
2939
+ err("attribute changes. Commit or stash first, or pass --allow-dirty to own the risk.");
2940
+ return 2;
2941
+ }
2942
+ }
2943
+ out(`tamperward run \u2014 trusted base ${base.slice(0, 10)}; agent exit is untrusted.`);
2944
+ const agent = spawnSync2(opts.argv[0], opts.argv.slice(1), { cwd, stdio: "inherit" });
2945
+ const agentExit = agent.status ?? 1;
2946
+ const head = git3(["rev-parse", "HEAD"], cwd).trim();
2947
+ let diffCode = 0;
2948
+ if (head !== base) {
2949
+ out(`
2950
+ [envelope] policy check over committed changes (${base.slice(0, 10)}...${head.slice(0, 10)}):`);
2951
+ diffCode = runCheck({ diff: `${base}...${head}`, cwd });
2952
+ }
2953
+ out("\n[envelope] policy check over the working tree:");
2954
+ const workCode = runCheck({ worktree: true, cwd });
2955
+ out("\n[envelope] pristine verification against the trusted base:");
2956
+ const verifyCode = runVerify({ cwd, base, cmd: opts.cmd, budget: opts.budget });
2957
+ const cannot = diffCode === 2 || workCode === 2 || verifyCode === 2;
2958
+ const blocked = diffCode === 1 || workCode === 1 || verifyCode === 1;
2959
+ const enforcement = cannot ? 2 : blocked ? 1 : 0;
2960
+ const verdict2 = enforcement === 2 ? "CANNOT_ADJUDICATE (fail closed)" : enforcement === 1 ? "ENFORCEMENT_FAILED \u2014 the tree the runtime released does not stand" : agentExit !== 0 ? `agent exited ${agentExit} (enforcement clean)` : "GREEN MEANS GREEN";
2961
+ out(`
2962
+ tamperward run \u2014 agent exit ${agentExit}; checks diff=${diffCode} worktree=${workCode} verify=${verifyCode} \u2192 ${verdict2}`);
2963
+ return enforcement !== 0 ? enforcement : agentExit;
2964
+ }
2965
+ function parseRun(args) {
2966
+ const o = { argv: [] };
2967
+ for (let i = 0; i < args.length; i++) {
2968
+ const a = args[i];
2969
+ if (a === "--") {
2970
+ o.argv = args.slice(i + 1);
2971
+ break;
2972
+ } else if (a === "--base") o.base = args[++i];
2973
+ else if (a === "--cmd") o.cmd = args[++i];
2974
+ else if (a === "--budget") o.budget = Number(args[++i]);
2975
+ else if (a === "--allow-dirty") o.allowDirty = true;
2976
+ else if (a === "--cwd") o.cwd = args[++i];
2977
+ else {
2978
+ o.argv = args.slice(i);
2979
+ break;
2980
+ }
2981
+ }
2982
+ return o;
2983
+ }
2984
+
2915
2985
  // src/cli/index.ts
2916
2986
  function parseAllow(args) {
2917
2987
  const o = {};
@@ -2992,6 +3062,14 @@ Formats:
2992
3062
  from the trusted base; a visible-green /
2993
3063
  pristine-red result is a MASKED FAILURE
2994
3064
  (exit 1); cannot-verify fails closed (2)
3065
+ tamperward run [opts] -- <agent cmd...> enforcement envelope: record the trusted
3066
+ [--base R] [--cmd C] base, run the agent, treat its exit as
3067
+ [--budget S] [--allow-dirty] untrusted, then re-adjudicate the tree it
3068
+ left (policy over base...HEAD and the
3069
+ worktree, plus verify). Exit: agent's code
3070
+ when clean; 1 on any blocking finding or
3071
+ masked failure; 2 when it cannot
3072
+ adjudicate (fails closed)
2995
3073
  tamperward allow <rule> --reason "..." record a human sign-off (local audit ledger)
2996
3074
  tamperward init [--dry-run] wire the policy file plus every
2997
3075
  enforcement point: Claude Code hooks,
@@ -3017,6 +3095,8 @@ function main(argv2) {
3017
3095
  return runInit(parseInit(rest));
3018
3096
  case "verify":
3019
3097
  return runVerify(parseVerify(rest));
3098
+ case "run":
3099
+ return runEnvelope(parseRun(rest));
3020
3100
  case void 0:
3021
3101
  case "-h":
3022
3102
  case "--help":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tamperward",
3
- "version": "1.9.0",
3
+ "version": "1.10.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",