tamperward 1.11.0 → 1.11.1

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 +53 -7
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -2791,7 +2791,7 @@ tamperward init: ${changed} change(s) applied. Commit them so the gate travels w
2791
2791
  import { execFileSync as execFileSync4, spawnSync } from "node:child_process";
2792
2792
  import { chmodSync as chmodSync2, cpSync, mkdirSync as mkdirSync6, mkdtempSync as mkdtempSync2, rmSync as rmSync2, statSync as statSync5, symlinkSync, writeFileSync as writeFileSync6, existsSync as existsSync8 } from "node:fs";
2793
2793
  import { tmpdir as tmpdir2 } from "node:os";
2794
- import { dirname as dirname4, join as join9 } from "node:path";
2794
+ import { dirname as dirname4, join as join9, resolve } from "node:path";
2795
2795
  var OVERLAY_CLASSES = ["tests", "snapshots", "config"];
2796
2796
  function git2(args, cwd) {
2797
2797
  return execFileSync4("git", args, { cwd, encoding: "utf8", maxBuffer: 1 << 28 });
@@ -2820,7 +2820,7 @@ function materialize(cwd, dest) {
2820
2820
  cpSync(src, out2);
2821
2821
  chmodSync2(out2, st.mode);
2822
2822
  }
2823
- const nm = join9(cwd, "node_modules");
2823
+ const nm = resolve(cwd, "node_modules");
2824
2824
  if (existsSync8(nm)) symlinkSync(nm, join9(dest, "node_modules"), "dir");
2825
2825
  }
2826
2826
  function overlayPristine(cwd, base, dest, policy) {
@@ -2955,8 +2955,8 @@ function parseVerify(args) {
2955
2955
  // src/cli/run.ts
2956
2956
  import { execFileSync as execFileSync5, spawnSync as spawnSync2 } from "node:child_process";
2957
2957
  import { createHash as createHash5 } from "node:crypto";
2958
- import { readdirSync as readdirSync3, readFileSync as readFileSync11, realpathSync } from "node:fs";
2959
- import { join as join10 } from "node:path";
2958
+ import { readdirSync as readdirSync3, readFileSync as readFileSync11, readlinkSync, realpathSync, statSync as statSync6 } from "node:fs";
2959
+ import { join as join10, resolve as resolve2 } from "node:path";
2960
2960
  var out = (s) => process.stdout.write(s + "\n");
2961
2961
  var err = (s) => process.stderr.write(s + "\n");
2962
2962
  function git3(args, cwd) {
@@ -2977,6 +2977,45 @@ function treeFingerprint(cwd) {
2977
2977
  }
2978
2978
  return h.digest("hex");
2979
2979
  }
2980
+ function depsFingerprint(cwd) {
2981
+ const root = join10(cwd, "node_modules");
2982
+ const h = createHash5("sha256");
2983
+ const walk = (dir) => {
2984
+ let entries;
2985
+ try {
2986
+ entries = readdirSync3(dir, { withFileTypes: true });
2987
+ } catch {
2988
+ return;
2989
+ }
2990
+ for (const e of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
2991
+ const p = join10(dir, e.name);
2992
+ if (e.isDirectory()) walk(p);
2993
+ else if (e.isFile()) {
2994
+ h.update(p);
2995
+ try {
2996
+ h.update(readFileSync11(p));
2997
+ h.update(String(statSync6(p).mode));
2998
+ } catch {
2999
+ h.update("<unreadable>");
3000
+ }
3001
+ } else if (e.isSymbolicLink()) {
3002
+ h.update(p);
3003
+ try {
3004
+ h.update(readlinkSync(p));
3005
+ } catch {
3006
+ h.update("<unreadable-link>");
3007
+ }
3008
+ }
3009
+ }
3010
+ };
3011
+ try {
3012
+ if (!statSync6(root).isDirectory()) return "none";
3013
+ } catch {
3014
+ return "none";
3015
+ }
3016
+ walk(root);
3017
+ return h.digest("hex");
3018
+ }
2980
3019
  function nowTicks() {
2981
3020
  try {
2982
3021
  return Math.floor(parseFloat(readFileSync11("/proc/uptime", "utf8").split(" ")[0]) * 100);
@@ -3018,7 +3057,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
3018
3057
  return out2;
3019
3058
  }
3020
3059
  function runEnvelope(opts) {
3021
- const cwd = opts.cwd ?? process.cwd();
3060
+ const cwd = resolve2(opts.cwd ?? process.cwd());
3022
3061
  if (!opts.argv.length) {
3023
3062
  err("tamperward run: no agent command given (usage: tamperward run [options] -- <command...>)");
3024
3063
  return 2;
@@ -3055,6 +3094,7 @@ function runEnvelope(opts) {
3055
3094
  }
3056
3095
  }
3057
3096
  out(`tamperward run \u2014 trusted base ${base.slice(0, 10)}; agent exit is untrusted.`);
3097
+ const depsBefore = depsFingerprint(cwd);
3058
3098
  const spawnTicks = nowTicks();
3059
3099
  const agent = spawnSync2(opts.argv[0], opts.argv.slice(1), { cwd, stdio: "inherit" });
3060
3100
  const agentExit = agent.status ?? 1;
@@ -3089,16 +3129,21 @@ tamperward run \u2014 agent exit ${agentExit}; HISTORY_REWRITE \u2192 ENFORCEMEN
3089
3129
  err("tamperward run: the working tree changed while it was being adjudicated \u2014");
3090
3130
  err("the verdict below would describe a tree that no longer exists.");
3091
3131
  }
3132
+ const depsDrifted = !opts.allowDepDrift && depsFingerprint(cwd) !== depsBefore;
3133
+ if (depsDrifted) {
3134
+ err("tamperward run: the installed dependency tree changed during this run \u2014 the program that");
3135
+ err("runs the suite is not the one we started with. Both verification runs execute it.");
3136
+ }
3092
3137
  const survivors = survivorsHoldingTree(cwd, spawnTicks);
3093
3138
  if (survivors.length) {
3094
3139
  err(`tamperward run: ${survivors.length} process(es) started during this run still hold the`);
3095
3140
  err(`working tree (pid ${survivors.join(", ")}). A verdict cannot outlive the tree it describes.`);
3096
3141
  }
3097
- const notQuiescent = mutatedDuringAdjudication || survivors.length > 0;
3142
+ const notQuiescent = mutatedDuringAdjudication || survivors.length > 0 || depsDrifted;
3098
3143
  const cannot = diffCode === 2 || workCode === 2 || verifyCode === 2;
3099
3144
  const blocked = diffCode === 1 || workCode === 1 || verifyCode === 1 || notQuiescent;
3100
3145
  const enforcement = cannot ? 2 : blocked ? 1 : 0;
3101
- const verdict2 = enforcement === 2 ? "CANNOT_ADJUDICATE (fail closed)" : notQuiescent ? "NOT_QUIESCENT \u2014 the tree moved, or something still holds it" : enforcement === 1 ? "ENFORCEMENT_FAILED \u2014 the tree the runtime released does not stand" : agentExit !== 0 ? `agent exited ${agentExit} (enforcement clean)` : "GREEN MEANS GREEN";
3146
+ const verdict2 = enforcement === 2 ? "CANNOT_ADJUDICATE (fail closed)" : depsDrifted ? "DEPENDENCY_DRIFT \u2014 the suite runner changed under the envelope" : notQuiescent ? "NOT_QUIESCENT \u2014 the tree moved, or something still holds it" : enforcement === 1 ? "ENFORCEMENT_FAILED \u2014 the tree the runtime released does not stand" : agentExit !== 0 ? `agent exited ${agentExit} (enforcement clean)` : "GREEN MEANS GREEN";
3102
3147
  out(`
3103
3148
  tamperward run \u2014 agent exit ${agentExit}; checks diff=${diffCode} worktree=${workCode} verify=${verifyCode} \u2192 ${verdict2}`);
3104
3149
  return enforcement !== 0 ? enforcement : agentExit;
@@ -3115,6 +3160,7 @@ function parseRun(args) {
3115
3160
  else if (a === "--budget") o.budget = Number(args[++i]);
3116
3161
  else if (a === "--allow-dirty") o.allowDirty = true;
3117
3162
  else if (a === "--settle") o.settle = Number(args[++i]);
3163
+ else if (a === "--allow-dep-drift") o.allowDepDrift = true;
3118
3164
  else if (a === "--cwd") o.cwd = args[++i];
3119
3165
  else {
3120
3166
  o.argv = args.slice(i);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tamperward",
3
- "version": "1.11.0",
3
+ "version": "1.11.1",
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",