tamperward 2.29.3 → 2.29.5

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 +94 -51
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -10801,14 +10801,14 @@ function renderText(input, opts) {
10801
10801
  const isBlock = f.severity === "block";
10802
10802
  const mark = paint(isBlock ? "BLOCK" : "warn ", isBlock ? BOLD + RED : YELLOW, c);
10803
10803
  const loc = f.file ? `${stripControl(escapeControl(f.file))}${f.line ? `:${f.line}` : ""}` : "(command)";
10804
- out3.push(` ${mark} ${paint(stripControl(f.rule), BOLD, c)} ${paint(loc, DIM, c)}`);
10804
+ out3.push(` ${mark} ${paint(stripControl(f.rule), BOLD, c)} ${paint(loc, CYAN, c)}`);
10805
10805
  for (const l of wrap(f.message, opts.width - 4)) out3.push(` ${l}`);
10806
10806
  const row = (key2, lines) => {
10807
10807
  lines.forEach((l, i) => out3.push(` ${paint(pad(i === 0 ? key2 : ""), DIM, c)} ${l}`));
10808
10808
  };
10809
10809
  row("evidence", [paint(clip(f.evidence, body), DIM, c)]);
10810
10810
  row("instead", wrap(f.remediation, body));
10811
- if (f.signoff.required) row("sign-off", [stripControl(f.signoff.command)]);
10811
+ if (f.signoff.required) row("sign-off", [paint(stripControl(f.signoff.command), YELLOW, c)]);
10812
10812
  out3.push("");
10813
10813
  }
10814
10814
  if (blocks2.length > 0) {
@@ -10818,7 +10818,7 @@ function renderText(input, opts) {
10818
10818
  }
10819
10819
  return out3.join("\n").replace(/\n+$/, "") + "\n";
10820
10820
  }
10821
- var ESC, RESET, BOLD, DIM, RED, YELLOW, GREEN, SIGNOFF_NOTE;
10821
+ var ESC, RESET, BOLD, DIM, truecolour, RED, YELLOW, GREEN, CYAN, SIGNOFF_NOTE;
10822
10822
  var init_text = __esm({
10823
10823
  "src/cli/render/text.ts"() {
10824
10824
  "use strict";
@@ -10827,9 +10827,11 @@ var init_text = __esm({
10827
10827
  RESET = `${ESC}[0m`;
10828
10828
  BOLD = `${ESC}[1m`;
10829
10829
  DIM = `${ESC}[2m`;
10830
- RED = `${ESC}[31m`;
10831
- YELLOW = `${ESC}[33m`;
10832
- GREEN = `${ESC}[32m`;
10830
+ truecolour = (r, g, b) => `${ESC}[38;2;${r};${g};${b}m`;
10831
+ RED = truecolour(255, 107, 107);
10832
+ YELLOW = truecolour(227, 179, 65);
10833
+ GREEN = truecolour(63, 185, 80);
10834
+ CYAN = truecolour(86, 212, 221);
10833
10835
  SIGNOFF_NOTE = "A blocking finding clears only with a human sign-off. In CI that sign-off is out-of-band \u2014 a PR label applied by a reviewer \u2014 never a file committed on the branch under review. See SPEC \xA75.4.";
10834
10836
  }
10835
10837
  });
@@ -12759,6 +12761,7 @@ async function startHookService(opts) {
12759
12761
  removeQuietly(paths.state);
12760
12762
  const cache3 = opts.cache ?? new SnapshotCache();
12761
12763
  let served = 0;
12764
+ let inFlight = false;
12762
12765
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
12763
12766
  const respond = (sock, body) => {
12764
12767
  try {
@@ -12789,19 +12792,27 @@ async function startHookService(opts) {
12789
12792
  if (!within(root, req2.cwd)) return respond(sock, { refused: "cwd outside the bound repository" });
12790
12793
  const named = payloadCwd(req2.raw);
12791
12794
  if (named !== null && !within(root, named)) return respond(sock, { refused: "payload cwd outside the bound repository" });
12792
- sock.write(JSON.stringify({ v: HOOK_SERVICE_PROTOCOL, version: TW_VERSION, accepted: true }) + "\n", () => {
12793
- let result;
12795
+ if (inFlight) return respond(sock, { refused: "another request is in flight" });
12796
+ inFlight = true;
12797
+ sock.write(JSON.stringify({ v: HOOK_SERVICE_PROTOCOL, version: TW_VERSION, accepted: true }) + "\n");
12798
+ setImmediate(() => {
12794
12799
  try {
12795
- result = withEnv(req2.env, () => req2.kind === "PreToolUse" ? preToolUseFromRaw(req2.raw, req2.cwd) : stopFromRaw(req2.raw, req2.cwd));
12796
- } catch (e) {
12797
- const detail = e instanceof Error ? e.message : String(e);
12798
- const reason = `Tamperward's hook service could not complete the accepted evaluation (${detail}), so it is denied rather than retried concurrently.`;
12799
- const stdout = req2.kind === "PreToolUse" ? JSON.stringify({ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: reason } }) + "\n" : JSON.stringify({ decision: "block", reason }) + "\n";
12800
+ if (sock.destroyed || sock.writableEnded) return;
12801
+ let result;
12802
+ try {
12803
+ result = withEnv(req2.env, () => req2.kind === "PreToolUse" ? preToolUseFromRaw(req2.raw, req2.cwd) : stopFromRaw(req2.raw, req2.cwd));
12804
+ } catch (e) {
12805
+ const detail = e instanceof Error ? e.message : String(e);
12806
+ const reason = `Tamperward's hook service could not complete the accepted evaluation (${detail}), so it is denied rather than retried concurrently.`;
12807
+ const stdout = req2.kind === "PreToolUse" ? JSON.stringify({ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: reason } }) + "\n" : JSON.stringify({ decision: "block", reason }) + "\n";
12808
+ served++;
12809
+ return respond(sock, { exitCode: 0, stdout });
12810
+ }
12800
12811
  served++;
12801
- return respond(sock, { exitCode: 0, stdout });
12812
+ respond(sock, { exitCode: result.exitCode, stdout: result.stdout });
12813
+ } finally {
12814
+ inFlight = false;
12802
12815
  }
12803
- served++;
12804
- respond(sock, { exitCode: result.exitCode, stdout: result.stdout });
12805
12816
  });
12806
12817
  };
12807
12818
  const server = createServer((sock) => {
@@ -12860,36 +12871,44 @@ async function startHookService(opts) {
12860
12871
  function sleepSync(ms) {
12861
12872
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
12862
12873
  }
12874
+ async function probeSocket(paths) {
12875
+ if (socketRefusal(paths) !== null) return { answered: false, status: null };
12876
+ const req2 = { v: HOOK_SERVICE_PROTOCOL, version: TW_VERSION, kind: "status", raw: "", cwd: process.cwd(), env: {} };
12877
+ const res = await exchange(paths, req2, 1e3);
12878
+ if (res === null) return { answered: false, status: null };
12879
+ if (isRecord(res) && res.v === HOOK_SERVICE_PROTOCOL && typeof res.pid === "number" && typeof res.root === "string" && typeof res.version === "string") {
12880
+ return { answered: true, status: { pid: res.pid, root: res.root, version: res.version } };
12881
+ }
12882
+ return { answered: true, status: null };
12883
+ }
12884
+ function describeStateFile(state) {
12885
+ return state ? `records pid ${state.pid}, root ${state.root}, tamperward@${state.version}` : "is absent or unreadable";
12886
+ }
12863
12887
  async function stopHookService(paths) {
12888
+ const probe = await probeSocket(paths);
12864
12889
  const state = readServiceState(paths);
12865
- let outcome = "not-running";
12866
- let confirmed = false;
12867
- if (state && state.pid !== process.pid && pidAlive2(state.pid) && state.version === TW_VERSION && socketRefusal(paths) === null) {
12868
- const req2 = {
12869
- v: HOOK_SERVICE_PROTOCOL,
12870
- version: TW_VERSION,
12871
- kind: "status",
12872
- raw: "",
12873
- cwd: process.cwd(),
12874
- env: {}
12875
- };
12876
- const res = await exchange(paths, req2, 1e3);
12877
- confirmed = isRecord(res) && res.v === HOOK_SERVICE_PROTOCOL && res.version === TW_VERSION && res.pid === state.pid && res.root === state.root;
12878
- }
12879
- if (state && confirmed) {
12880
- try {
12881
- process.kill(state.pid, "SIGTERM");
12882
- } catch {
12890
+ if (probe.answered) {
12891
+ const st = probe.status;
12892
+ const agrees = st !== null && state !== null && state.pid === st.pid && state.root === st.root && state.version === st.version;
12893
+ if (agrees && st.pid !== process.pid) {
12894
+ try {
12895
+ process.kill(st.pid, "SIGTERM");
12896
+ } catch {
12897
+ }
12898
+ const gone = () => !pidAlive2(st.pid) || !existsSync11(paths.socket);
12899
+ const until = Date.now() + STOP_WAIT_MS;
12900
+ while (!gone() && Date.now() < until) sleepSync(50);
12901
+ if (!gone()) throw new Error(`hook service pid ${st.pid} did not exit within ${STOP_WAIT_MS / 1e3}s`);
12902
+ removeQuietly(paths.socket);
12903
+ removeQuietly(paths.state);
12904
+ return { outcome: "stopped", pid: st.pid };
12883
12905
  }
12884
- const gone = () => !pidAlive2(state.pid) || !existsSync11(paths.socket);
12885
- const until = Date.now() + STOP_WAIT_MS;
12886
- while (!gone() && Date.now() < until) sleepSync(50);
12887
- if (!gone()) throw new Error(`hook service pid ${state.pid} did not exit within ${STOP_WAIT_MS / 1e3}s`);
12888
- outcome = "stopped";
12906
+ const detail = st ? `a listener answered on ${paths.socket} (pid ${st.pid}, root ${st.root}, tamperward@${st.version}) but the state file ${describeStateFile(state)}` : `a listener answered on ${paths.socket} but did not report a readable status (possibly a different tamperward version); the state file ${describeStateFile(state)}`;
12907
+ return { outcome: "mismatch", pid: st?.pid ?? state?.pid, root: st?.root ?? state?.root, detail };
12889
12908
  }
12890
12909
  removeQuietly(paths.socket);
12891
12910
  removeQuietly(paths.state);
12892
- return outcome;
12911
+ return { outcome: "not-running" };
12893
12912
  }
12894
12913
  function parseStart(args) {
12895
12914
  let dir = process.cwd();
@@ -12928,8 +12947,21 @@ function runHookService(args) {
12928
12947
  return -1;
12929
12948
  }
12930
12949
  if (sub === "stop") {
12931
- return stopHookService(paths).then((outcome) => {
12932
- process.stdout.write(`tamperward hook-service: ${outcome === "stopped" ? "stopped" : "not running"}; ${paths.socket} removed
12950
+ return stopHookService(paths).then((res) => {
12951
+ if (res.outcome === "stopped") {
12952
+ process.stdout.write(`tamperward hook-service: stopped (pid ${res.pid}); ${paths.socket} removed
12953
+ `);
12954
+ return 0;
12955
+ }
12956
+ if (res.outcome === "mismatch") {
12957
+ process.stderr.write(
12958
+ `tamperward hook-service: NOT stopped \u2014 ${res.detail}. The socket was left intact; a live listener is never orphaned. ` + (res.pid ? `If you mean to stop it, signal it directly: kill ${res.pid}.
12959
+ ` : `Inspect the process holding ${paths.socket} to stop it.
12960
+ `)
12961
+ );
12962
+ return 1;
12963
+ }
12964
+ process.stdout.write(`tamperward hook-service: not running; ${paths.socket} removed
12933
12965
  `);
12934
12966
  return 0;
12935
12967
  });
@@ -12937,22 +12969,33 @@ function runHookService(args) {
12937
12969
  const state = readServiceState(paths);
12938
12970
  const refusal = socketRefusal(paths);
12939
12971
  const optIn = hookServiceEnabled() ? `${HOOK_SERVICE_ENV}=1 (hooks consult the service)` : `${HOOK_SERVICE_ENV} unset (hooks run in-process)`;
12940
- if (!state || !pidAlive2(state.pid) || refusal !== null) {
12941
- process.stdout.write(`tamperward hook-service: not running (${refusal ?? "no live pid"}); ${optIn}
12972
+ if (refusal !== null) {
12973
+ process.stdout.write(`tamperward hook-service: not running (${refusal}); ${optIn}
12942
12974
  `);
12943
12975
  return 0;
12944
12976
  }
12945
12977
  const req2 = { v: HOOK_SERVICE_PROTOCOL, version: TW_VERSION, kind: "status", raw: "", cwd: process.cwd(), env: {} };
12946
12978
  void exchange(paths, req2, 5e3).then((res) => {
12947
- if (!isRecord(res) || typeof res.served !== "number") {
12948
- process.stdout.write(`tamperward hook-service: not running (pid ${state.pid} did not answer, or is another version); ${optIn}
12979
+ if (res === null) {
12980
+ process.stdout.write(`tamperward hook-service: not running (socket did not connect); ${optIn}
12949
12981
  `);
12950
12982
  exitAfterFlush(0);
12951
12983
  return;
12952
12984
  }
12985
+ if (!isRecord(res) || typeof res.served !== "number" || typeof res.pid !== "number" || typeof res.root !== "string" || typeof res.version !== "string") {
12986
+ process.stdout.write(
12987
+ `tamperward hook-service: a listener answered on ${paths.socket} but did not report a readable status (possibly a different tamperward version); the state file ${state ? `records pid ${state.pid}, tamperward@${state.version}` : "is absent or unreadable"}; ${optIn}
12988
+ `
12989
+ );
12990
+ exitAfterFlush(0);
12991
+ return;
12992
+ }
12953
12993
  const cache3 = isRecord(res.cache) ? res.cache : {};
12994
+ const startedAt = typeof res.started_at === "string" ? res.started_at : state?.started_at ?? "unknown";
12995
+ const disagrees = !state || state.pid !== res.pid || state.root !== res.root || state.version !== res.version;
12996
+ const warn = disagrees ? `WARNING: the state file ${describeStateFile(state)}, which disagrees with the live listener; ` : "";
12954
12997
  process.stdout.write(
12955
- `tamperward hook-service: running (pid ${state.pid}, tamperward@${state.version}, root ${state.root}, since ${state.started_at}); served: ${res.served}; snapshot cache hits/misses/entries: ${String(cache3.hits ?? 0)}/${String(cache3.misses ?? 0)}/${String(cache3.size ?? 0)}; ${optIn}
12998
+ `tamperward hook-service: running (pid ${res.pid}, tamperward@${res.version}, root ${res.root}, since ${startedAt}); served: ${res.served}; snapshot cache hits/misses/entries: ${String(cache3.hits ?? 0)}/${String(cache3.misses ?? 0)}/${String(cache3.size ?? 0)}; ${warn}${optIn}
12956
12999
  `
12957
13000
  );
12958
13001
  exitAfterFlush(0);
@@ -18153,7 +18196,7 @@ async function runOnboard(opts, io = {}) {
18153
18196
  if (kind === "ok") return GREEN2;
18154
18197
  if (kind === "warn") return YELLOW2;
18155
18198
  if (kind === "bad") return RED2;
18156
- if (kind === "info") return CYAN;
18199
+ if (kind === "info") return CYAN2;
18157
18200
  return DIM2;
18158
18201
  };
18159
18202
  const status = (label, text, kind = "info") => {
@@ -18210,7 +18253,7 @@ async function runOnboard(opts, io = {}) {
18210
18253
  const section = (n) => {
18211
18254
  sectionNo = n;
18212
18255
  out3("");
18213
- out3(paint2(n + "/" + SECTIONS.length + " " + SECTIONS[n - 1], BOLD2 + CYAN, colour));
18256
+ out3(paint2(n + "/" + SECTIONS.length + " " + SECTIONS[n - 1], BOLD2 + CYAN2, colour));
18214
18257
  };
18215
18258
  const wrote = [];
18216
18259
  let verifyCommand;
@@ -18552,7 +18595,7 @@ function runDemo(cwd, head, runners, out3) {
18552
18595
  out3("changed it while the demo ran; inspect with `git status`.");
18553
18596
  }
18554
18597
  }
18555
- var Aborted, SECTIONS, MANUAL_CONTROLS, ESC2, RESET2, BOLD2, DIM2, RED2, YELLOW2, GREEN2, CYAN, JS_TEST_PATH, JS_TEST_BLOCK;
18598
+ var Aborted, SECTIONS, MANUAL_CONTROLS, ESC2, RESET2, BOLD2, DIM2, RED2, YELLOW2, GREEN2, CYAN2, JS_TEST_PATH, JS_TEST_BLOCK;
18556
18599
  var init_onboard = __esm({
18557
18600
  "src/cli/onboard.ts"() {
18558
18601
  "use strict";
@@ -18589,7 +18632,7 @@ var init_onboard = __esm({
18589
18632
  RED2 = `${ESC2}[31m`;
18590
18633
  YELLOW2 = `${ESC2}[33m`;
18591
18634
  GREEN2 = `${ESC2}[32m`;
18592
- CYAN = `${ESC2}[36m`;
18635
+ CYAN2 = `${ESC2}[36m`;
18593
18636
  JS_TEST_PATH = /(?:^|\/)(?:[^/]+\.(?:test|spec)\.[cm]?[jt]sx?|__tests__\/[^/]+\.[cm]?[jt]sx?)$/;
18594
18637
  JS_TEST_BLOCK = /^(\s*)(it|test|describe)(\s*\()/m;
18595
18638
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tamperward",
3
- "version": "2.29.3",
3
+ "version": "2.29.5",
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",