tamperward 2.11.2 → 2.11.3

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 (3) hide show
  1. package/README.md +21 -12
  2. package/dist/cli/index.js +257 -14
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -155,7 +155,15 @@ After the runtime exits its exit code is treated as untrusted, and the envelope
155
155
  checks that post-agent `HEAD` still descends from the entry commit; the committed
156
156
  changes over `entry...HEAD`; staged, unstaged and untracked non-ignored worktree
157
157
  changes; dependency drift and whether the tree stayed quiescent; and the
158
- verification outcome.
158
+ verification outcome. For agent runtimes that may hang, `--agent-budget <seconds>`
159
+ adds an operator-owned wall-clock boundary around the **agent itself** (separate from
160
+ the verifier's `--budget`). On expiry TamperWard terminates the owned process tree,
161
+ then still performs the same post-timeout adjudication. A clean timeout is
162
+ `AGENT_TIMEOUT` / exit 124; any enforcement failure or cannot-adjudicate result
163
+ still outranks that runtime status. On POSIX the agent starts in its own process
164
+ group; on Linux the timeout also enumerates the agent's `/proc` descendant tree so
165
+ a child that escaped the group with `setsid()` is terminated before adjudication.
166
+ Windows uses `taskkill /T /F` as the explicit process-tree fallback.
159
167
 
160
168
  `tamperward verify` materialises two temporary copies — they are **not
161
169
  sandboxes**, and both execute agent-controlled code. The visible copy runs the
@@ -175,6 +183,7 @@ semantic correctness nor a substitute for independent held-out tests.
175
183
  | Enforcement clean and agent exits 0 | exit 0 |
176
184
  | Blocking finding, masked failure, dependency drift, or non-quiescent tree | exit 1 |
177
185
  | Required adjudication cannot be completed | exit 2, failing closed |
186
+ | Enforcement clean but the agent exceeded `--agent-budget` | `AGENT_TIMEOUT`, exit 124 |
178
187
  | Enforcement clean but the agent failed | the agent's non-zero exit is preserved |
179
188
 
180
189
  ### CI authority
@@ -294,7 +303,7 @@ The four primitives:
294
303
  npx tamperward check --staged # pre-commit view
295
304
  npx tamperward check --diff "main...HEAD" # CI view over the PR's commit range
296
305
  npx tamperward verify --base main # pristine-suite re-execution
297
- npx tamperward run -- <agent command...> # same-host envelope (local verifier only)
306
+ npx tamperward run --agent-budget 1800 -- <agent command...> # optional agent-runtime bound
298
307
  ```
299
308
 
300
309
  The isolated backend is a **frozen-artifact final verifier**. `tamperward run`
@@ -312,7 +321,7 @@ commands ignore what they do not know.
312
321
  | --- | --- |
313
322
  | `check` | one view — `--staged` · `--worktree` · `--diff <base>...<head>` — plus `--format text\|json\|github\|auto` (default `auto`) · `--json` (alias for `--format json`) · `--cwd <dir>` |
314
323
  | `verify` | `--base <rev>` (default `HEAD`) · `--cmd <suite command>` · `--budget <seconds>` · `--json` · `--keep` (keep the two materialised copies and report their paths) · `--require-ancestor` (refuse a base that is not an ancestor of `HEAD`) · `--cwd <dir>` |
315
- | `run` | `--base <rev>` · `--cmd <suite command>` · `--budget <seconds>` · `--allow-dirty` · `--settle <seconds>` (wait before the final quiescence check) · `--allow-dep-drift` · `--cwd <dir>` · then `-- <agent command...>` |
324
+ | `run` | `--base <rev>` · `--cmd <suite command>` · `--budget <seconds>` (per verifier suite) · `--agent-budget <seconds>` (optional wrapped-agent wall clock) · `--allow-dirty` · `--settle <seconds>` (wait before the final quiescence check) · `--allow-dep-drift` · `--cwd <dir>` · then `-- <agent command...>` |
316
325
  | `allow` | `<rule>` · `--file <path>` · `--reason "<why>"` (required) · `--cwd <dir>` |
317
326
  | `init` | `--cwd <dir>` · `--dry-run` · `--force-workflow` |
318
327
  | `watch` | `--dir <dir>` · `--log <file>` — a daemon; it runs until signalled |
@@ -320,15 +329,15 @@ commands ignore what they do not know.
320
329
 
321
330
  **Exit codes** — part of the public surface:
322
331
 
323
- | command | 0 | 1 | 2 |
324
- | --- | --- | --- | --- |
325
- | `check` | no blocking finding | at least one blocking finding | cannot evaluate: policy parse error, malformed `--diff` range, no view given, not a git repository, or an unresolvable revision — any failure the gate cannot recover from is one clean `tamperward: …` line on stderr at exit 2, never a stack trace at exit 1 |
326
- | `verify` | `VERIFIED` — visible and pristine both green; or a `MASKED_FAILURE` cleared by an out-of-band `verify@<head-sha>` approval | `MASKED_FAILURE` (visible green, pristine red) or `SUITE_RED` | cannot verify, failing closed: no suite command, unresolvable base, `--require-ancestor` refused, budget exceeded, or the working or dependency tree moved during the run |
327
- | `run` | enforcement clean and the agent exited 0 — a non-zero agent exit is passed through unchanged | any blocking finding or masked failure, whatever the agent returned | cannot adjudicate: dirty start, policy error, verify cannot run |
328
- | `hook claude` / `sweep claude` | always — a deny is JSON on stdout at exit 0, never exit 2 | — | only for an unsupported agent name |
329
- | `allow` | sign-off recorded | — | no rule or `--reason`, not a git repo, or no current blocking finding to sign off |
330
- | `init` | wired, or already wired | — | an item needs attention |
331
- | no or unknown command | help printed (no command) | — | unknown command, help printed |
332
+ | command | 0 | 1 | 2 | 124 |
333
+ | --- | --- | --- | --- | --- |
334
+ | `check` | no blocking finding | at least one blocking finding | cannot evaluate: policy parse error, malformed `--diff` range, no view given, not a git repository, or an unresolvable revision — any failure the gate cannot recover from is one clean `tamperward: …` line on stderr at exit 2, never a stack trace at exit 1 | — |
335
+ | `verify` | `VERIFIED` — visible and pristine both green; or a `MASKED_FAILURE` cleared by an out-of-band `verify@<head-sha>` approval | `MASKED_FAILURE` (visible green, pristine red) or `SUITE_RED` | cannot verify, failing closed: no suite command, unresolvable base, `--require-ancestor` refused, budget exceeded, or the working or dependency tree moved during the run | — |
336
+ | `run` | enforcement clean and the agent exited 0 — another non-zero agent exit is passed through unchanged | any blocking finding or masked failure, including a non-quiescent process after timeout | cannot adjudicate: dirty start, policy error, verify cannot run | `AGENT_TIMEOUT`: `--agent-budget` expired and post-timeout enforcement was clean |
337
+ | `hook claude` / `sweep claude` | always — a deny is JSON on stdout at exit 0, never exit 2 | — | only for an unsupported agent name | — |
338
+ | `allow` | sign-off recorded | — | no rule or `--reason`, not a git repo, or no current blocking finding to sign off | — |
339
+ | `init` | wired, or already wired | — | an item needs attention | — |
340
+ | no or unknown command | help printed (no command) | — | unknown command, help printed | — |
332
341
 
333
342
  ### Environment variables
334
343
 
package/dist/cli/index.js CHANGED
@@ -9694,10 +9694,227 @@ function parseVerify(args) {
9694
9694
 
9695
9695
  // src/cli/run.ts
9696
9696
  import { execFileSync as execFileSync9, spawnSync as spawnSync3 } from "node:child_process";
9697
- import { readdirSync as readdirSync4, readFileSync as readFileSync16, readlinkSync as readlinkSync4, realpathSync as realpathSync6 } from "node:fs";
9698
- import { resolve as resolve7 } from "node:path";
9697
+ import { mkdtempSync as mkdtempSync3, readdirSync as readdirSync4, readFileSync as readFileSync16, readlinkSync as readlinkSync4, realpathSync as realpathSync6, rmSync as rmSync4 } from "node:fs";
9698
+ import { tmpdir as tmpdir3 } from "node:os";
9699
+ import { join as join16, resolve as resolve7 } from "node:path";
9699
9700
  var out = (s) => process.stdout.write(s + "\n");
9700
9701
  var err = (s) => process.stderr.write(s + "\n");
9702
+ var AGENT_SUPERVISOR = String.raw`
9703
+ const { spawn, spawnSync } = require('node:child_process');
9704
+ const fs = require('node:fs');
9705
+
9706
+ const [resultFile, budgetRaw, command, ...args] = process.argv.slice(1);
9707
+ const budgetMs = Number(budgetRaw);
9708
+ let child;
9709
+ let timedOut = false;
9710
+ let finished = false;
9711
+
9712
+ function writeResult(value) {
9713
+ try { fs.writeFileSync(resultFile, JSON.stringify(value)); } catch {}
9714
+ }
9715
+
9716
+ function linuxDescendants(rootPid) {
9717
+ if (process.platform !== 'linux') return [];
9718
+ const byParent = new Map();
9719
+ let names = [];
9720
+ try { names = fs.readdirSync('/proc').filter((x) => /^\d+$/.test(x)); } catch { return []; }
9721
+ for (const name of names) {
9722
+ const pid = Number(name);
9723
+ try {
9724
+ const stat = fs.readFileSync('/proc/' + name + '/stat', 'utf8');
9725
+ const fields = stat.slice(stat.lastIndexOf(')') + 2).split(' ');
9726
+ const ppid = Number(fields[1]);
9727
+ if (!Number.isFinite(ppid)) continue;
9728
+ const kids = byParent.get(ppid) || [];
9729
+ kids.push(pid);
9730
+ byParent.set(ppid, kids);
9731
+ } catch {}
9732
+ }
9733
+ const out = [];
9734
+ const seen = new Set([rootPid]);
9735
+ const stack = [rootPid];
9736
+ while (stack.length) {
9737
+ const parent = stack.pop();
9738
+ for (const childPid of (byParent.get(parent) || [])) {
9739
+ if (seen.has(childPid)) continue;
9740
+ seen.add(childPid);
9741
+ out.push(childPid);
9742
+ stack.push(childPid);
9743
+ }
9744
+ }
9745
+ return out;
9746
+ }
9747
+
9748
+ function killTree(pid) {
9749
+ if (!pid) return;
9750
+ if (process.platform === 'win32') {
9751
+ try { spawnSync('taskkill', ['/pid', String(pid), '/t', '/f'], { stdio: 'ignore' }); } catch {}
9752
+ return;
9753
+ }
9754
+
9755
+ // Snapshot Linux descendants BEFORE killing the process-group leader. A
9756
+ // descendant may have called setsid() and escaped the group while remaining
9757
+ // in the agent's parent/child tree; /proc still lets us identify it.
9758
+ const descendants = linuxDescendants(pid);
9759
+ try { process.kill(-pid, 'SIGKILL'); } catch {
9760
+ try { process.kill(pid, 'SIGKILL'); } catch {}
9761
+ }
9762
+ for (const childPid of descendants.reverse()) {
9763
+ try { process.kill(childPid, 'SIGKILL'); } catch {}
9764
+ }
9765
+ }
9766
+
9767
+ try {
9768
+ child = spawn(command, args, {
9769
+ stdio: 'inherit',
9770
+ detached: process.platform !== 'win32',
9771
+ });
9772
+ } catch (e) {
9773
+ writeResult({ exit: 1, timedOut: false, failure: String(e) });
9774
+ process.exit(0);
9775
+ }
9776
+
9777
+ writeResult({ pid: child.pid, started: true });
9778
+
9779
+ const timer = setTimeout(() => {
9780
+ timedOut = true;
9781
+ killTree(child.pid);
9782
+ }, budgetMs);
9783
+ timer.unref();
9784
+
9785
+ child.once('error', (e) => {
9786
+ if (finished) return;
9787
+ finished = true;
9788
+ clearTimeout(timer);
9789
+ writeResult({ exit: 1, timedOut, failure: String(e) });
9790
+ process.exit(0);
9791
+ });
9792
+
9793
+ child.once('exit', (code, signal) => {
9794
+ if (finished) return;
9795
+ finished = true;
9796
+ clearTimeout(timer);
9797
+ // A timeout owns the public status even when the killed process reports a
9798
+ // shell-specific signal/exit value. The envelope will still adjudicate.
9799
+ writeResult({
9800
+ exit: timedOut ? 124 : (code == null ? 1 : code),
9801
+ timedOut,
9802
+ signal: signal == null ? null : String(signal),
9803
+ });
9804
+ process.exit(0);
9805
+ });
9806
+ `;
9807
+ function linuxDescendantPids(rootPid) {
9808
+ if (process.platform !== "linux") return [];
9809
+ const byParent = /* @__PURE__ */ new Map();
9810
+ let names;
9811
+ try {
9812
+ names = readdirSync4("/proc").filter((x) => /^\d+$/.test(x));
9813
+ } catch {
9814
+ return [];
9815
+ }
9816
+ for (const name of names) {
9817
+ const pid = Number(name);
9818
+ try {
9819
+ const stat = readFileSync16(`/proc/${name}/stat`, "utf8");
9820
+ const fields = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
9821
+ const ppid = Number(fields[1]);
9822
+ if (!Number.isFinite(ppid)) continue;
9823
+ const kids = byParent.get(ppid) ?? [];
9824
+ kids.push(pid);
9825
+ byParent.set(ppid, kids);
9826
+ } catch {
9827
+ }
9828
+ }
9829
+ const out2 = [];
9830
+ const seen = /* @__PURE__ */ new Set([rootPid]);
9831
+ const stack = [rootPid];
9832
+ while (stack.length) {
9833
+ const parent = stack.pop();
9834
+ for (const childPid of byParent.get(parent) ?? []) {
9835
+ if (seen.has(childPid)) continue;
9836
+ seen.add(childPid);
9837
+ out2.push(childPid);
9838
+ stack.push(childPid);
9839
+ }
9840
+ }
9841
+ return out2;
9842
+ }
9843
+ function killAgentTree(pid) {
9844
+ if (!Number.isInteger(pid) || pid <= 1) return;
9845
+ if (process.platform === "win32") {
9846
+ try {
9847
+ spawnSync3("taskkill", ["/pid", String(pid), "/t", "/f"], { stdio: "ignore", timeout: 5e3 });
9848
+ } catch {
9849
+ }
9850
+ return;
9851
+ }
9852
+ const descendants = linuxDescendantPids(pid);
9853
+ try {
9854
+ process.kill(-pid, "SIGKILL");
9855
+ } catch {
9856
+ try {
9857
+ process.kill(pid, "SIGKILL");
9858
+ } catch {
9859
+ }
9860
+ }
9861
+ for (const childPid of descendants.reverse()) {
9862
+ try {
9863
+ process.kill(childPid, "SIGKILL");
9864
+ } catch {
9865
+ }
9866
+ }
9867
+ }
9868
+ function runAgentWithBudget(argv2, cwd, budgetSecs) {
9869
+ const stateDir = mkdtempSync3(join16(tmpdir3(), "tw-agent-supervisor-"));
9870
+ const resultFile = join16(stateDir, "result.json");
9871
+ let supervisorTimedOut = false;
9872
+ try {
9873
+ const supervisor = spawnSync3(
9874
+ process.execPath,
9875
+ ["-e", AGENT_SUPERVISOR, resultFile, String(budgetSecs * 1e3), ...argv2],
9876
+ {
9877
+ cwd,
9878
+ stdio: "inherit",
9879
+ // Backstop only. The inner supervisor owns the intended budget and
9880
+ // process-tree kill. This prevents a broken supervisor hanging run.
9881
+ timeout: Math.ceil(budgetSecs * 1e3) + 1e4,
9882
+ killSignal: "SIGKILL"
9883
+ }
9884
+ );
9885
+ supervisorTimedOut = Boolean(supervisor.error) && supervisor.error.code === "ETIMEDOUT";
9886
+ let state = {};
9887
+ try {
9888
+ state = JSON.parse(readFileSync16(resultFile, "utf8"));
9889
+ } catch {
9890
+ }
9891
+ if (supervisorTimedOut || state.timedOut) {
9892
+ if (state.pid) killAgentTree(state.pid);
9893
+ return {
9894
+ exit: 124,
9895
+ timedOut: true,
9896
+ signal: state.signal ?? null,
9897
+ ...state.failure ? { failure: state.failure } : {}
9898
+ };
9899
+ }
9900
+ if (typeof state.exit === "number") {
9901
+ return {
9902
+ exit: state.exit,
9903
+ timedOut: false,
9904
+ signal: state.signal ?? null,
9905
+ ...state.failure ? { failure: state.failure } : {}
9906
+ };
9907
+ }
9908
+ if (state.pid) killAgentTree(state.pid);
9909
+ return {
9910
+ exit: 1,
9911
+ timedOut: false,
9912
+ failure: state.failure ?? "agent supervisor did not produce a final status"
9913
+ };
9914
+ } finally {
9915
+ rmSync4(stateDir, { recursive: true, force: true });
9916
+ }
9917
+ }
9701
9918
  function git3(args, cwd) {
9702
9919
  return execFileSync9("git", args, { cwd, encoding: "utf8", maxBuffer: 1 << 28, env: trustedGitEnv() });
9703
9920
  }
@@ -9759,6 +9976,12 @@ function runEnvelope(opts) {
9759
9976
  err("tamperward run: no agent command given (usage: tamperward run [options] -- <command...>)");
9760
9977
  return 2;
9761
9978
  }
9979
+ if (opts.agentBudget !== void 0 && (!Number.isFinite(opts.agentBudget) || opts.agentBudget <= 0)) {
9980
+ err(
9981
+ `tamperward run: --agent-budget needs a positive number of seconds (got ${JSON.stringify(opts.agentBudget)}) \u2014 failing closed.`
9982
+ );
9983
+ return 2;
9984
+ }
9762
9985
  let base;
9763
9986
  try {
9764
9987
  base = git3(["rev-parse", "--verify", `${opts.base ?? "HEAD"}^{commit}`], cwd).trim();
@@ -9820,8 +10043,25 @@ function runEnvelope(opts) {
9820
10043
  }
9821
10044
  const rewritesBefore = objectRewriteState(cwd);
9822
10045
  const spawnTicks = nowTicks();
9823
- const agent = spawnSync3(opts.argv[0], opts.argv.slice(1), { cwd, stdio: "inherit" });
9824
- const agentExit = agent.status ?? 1;
10046
+ const agentRun = opts.agentBudget === void 0 ? (() => {
10047
+ const agent = spawnSync3(opts.argv[0], opts.argv.slice(1), { cwd, stdio: "inherit" });
10048
+ return {
10049
+ exit: agent.status ?? 1,
10050
+ timedOut: false,
10051
+ signal: agent.signal ? String(agent.signal) : null,
10052
+ ...agent.error ? { failure: agent.error.message } : {}
10053
+ };
10054
+ })() : runAgentWithBudget(opts.argv, cwd, opts.agentBudget);
10055
+ const agentExit = agentRun.exit;
10056
+ const agentTimedOut = agentRun.timedOut;
10057
+ if (agentRun.failure && !agentTimedOut) {
10058
+ err(`tamperward run: agent runtime failed to start/report cleanly (${agentRun.failure}) \u2014 continuing to adjudicate the released tree.`);
10059
+ }
10060
+ if (agentTimedOut) {
10061
+ err(
10062
+ `tamperward run: AGENT_TIMEOUT \u2014 wrapped agent exceeded ${opts.agentBudget}s; its owned process tree was terminated. Post-timeout adjudication continues.`
10063
+ );
10064
+ }
9825
10065
  if (objectRewriteState(cwd) !== rewritesBefore) {
9826
10066
  err("tamperward run: git object-replacement state changed during this run \u2014 a replace ref, graft");
9827
10067
  err("or shallow boundary now redirects what the trusted base resolves to. Nothing below could be");
@@ -9902,10 +10142,11 @@ tamperward run \u2014 agent exit ${agentExit}; HISTORY_REWRITE \u2192 ENFORCEMEN
9902
10142
  const cannot = !notQuiescent && (diffCode === 2 || workCode === 2 || verifyCode === 2);
9903
10143
  const blocked = diffCode === 1 || workCode === 1 || verifyCode === 1 || notQuiescent;
9904
10144
  const enforcement = cannot ? 2 : blocked ? 1 : 0;
9905
- const verdict2 = enforcement === 2 ? "CANNOT_ADJUDICATE (fail closed)" : rewrote ? "OBJECT_REWRITE \u2014 the base the checks read is not the base we anchored to" : 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";
10145
+ const verdict2 = enforcement === 2 ? "CANNOT_ADJUDICATE (fail closed)" : rewrote ? "OBJECT_REWRITE \u2014 the base the checks read is not the base we anchored to" : 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" : agentTimedOut ? "AGENT_TIMEOUT \u2014 runtime budget expired; post-timeout enforcement clean" : agentExit !== 0 ? `agent exited ${agentExit} (enforcement clean)` : "GREEN MEANS GREEN";
10146
+ const agentSummary = agentTimedOut ? `AGENT_TIMEOUT (${opts.agentBudget}s; exit 124)` : `agent exit ${agentExit}`;
9906
10147
  out(`
9907
- tamperward run \u2014 agent exit ${agentExit}; checks diff=${diffCode} worktree=${workCode} verify=${verifyCode} \u2192 ${verdict2}`);
9908
- return enforcement !== 0 ? enforcement : agentExit;
10148
+ tamperward run \u2014 ${agentSummary}; checks diff=${diffCode} worktree=${workCode} verify=${verifyCode} \u2192 ${verdict2}`);
10149
+ return enforcement !== 0 ? enforcement : agentTimedOut ? 124 : agentExit;
9909
10150
  }
9910
10151
  function parseRun(args) {
9911
10152
  const o = { argv: [] };
@@ -9917,6 +10158,7 @@ function parseRun(args) {
9917
10158
  } else if (a === "--base") o.base = args[++i];
9918
10159
  else if (a === "--cmd") o.cmd = args[++i];
9919
10160
  else if (a === "--budget") o.budget = Number(args[++i]);
10161
+ else if (a === "--agent-budget") o.agentBudget = Number(args[++i]);
9920
10162
  else if (a === "--allow-dirty") o.allowDirty = true;
9921
10163
  else if (a === "--settle") o.settle = Number(args[++i]);
9922
10164
  else if (a === "--allow-dep-drift") o.allowDepDrift = true;
@@ -10014,13 +10256,14 @@ Formats:
10014
10256
  fails closed (2)
10015
10257
  tamperward run [opts] -- <agent cmd...> enforcement envelope: record the trusted
10016
10258
  [--base R] [--cmd C] base, run the agent, treat its exit as
10017
- [--budget S] [--allow-dirty] untrusted, then re-adjudicate the tree it
10018
- [--settle S] [--allow-dep-drift] left (policy over base...HEAD and the
10019
- [--cwd D]
10020
- worktree, plus verify). Exit: agent's code
10021
- when clean; 1 on any blocking finding or
10022
- masked failure; 2 when it cannot
10023
- adjudicate (fails closed)
10259
+ [--budget S] [--agent-budget S] untrusted, then re-adjudicate the tree it
10260
+ [--allow-dirty] [--settle S] left (policy over base...HEAD and the
10261
+ [--allow-dep-drift] [--cwd D] worktree, plus verify). --agent-budget
10262
+ bounds the agent runtime itself and still
10263
+ adjudicates after timeout. Exit: agent's
10264
+ code when clean; 124 on clean AGENT_TIMEOUT;
10265
+ 1 on any blocking finding/masked failure;
10266
+ 2 when it cannot adjudicate (fails closed)
10024
10267
  tamperward allow <rule> --reason "..." record a human sign-off (local audit ledger)
10025
10268
  [--file F] [--cwd D]
10026
10269
  tamperward init [--dry-run] wire the policy file plus every
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tamperward",
3
- "version": "2.11.2",
3
+ "version": "2.11.3",
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",