#!/usr/bin/env node
// pids-cap-guard — decide whether a command writes a pids ceiling.
//
// Raising pids.max/TasksMax under a fork bomb feeds the bomb: on 2026-08-07 an assistant went
// 4096 -> 12288 -> 13500 -> 14200 while the runaway kept pace, and the cgroup stayed wedged the
// whole time. The correct action is to kill the runaway, never to move the ceiling.
//
// Writes only. Reading a cap (cat/grep/systemctl show) is forensics and stays allowed.
// The ceilings themselves are set by the tested launchers (confine.sh) and by unit files in the
// repo, so a command naming one of those is the mechanism, not a bypass.
//
// API:  evaluate(commandString) -> { deny, reason }
// CLI:  node pids-cap-guard.mjs "<command>"   — exit 0 allow, exit 1 deny with the reason on stderr.

const CAP_TOKEN = /pids\.max|TasksMax/i;

const SANCTIONED = /confine\.sh|cpu-guard\.sh|monitor\/install\.sh/;

// A redirect, tee, dd, cp or truncate landing on a pids.max file.
const FILE_WRITE = [
  />>?\s*\S*pids\.max/,
  /\btee\b(?:\s+-\S+)*\s+\S*pids\.max/,
  /\bdd\b[^\n]*\bof=\S*pids\.max/,
  /\bcp\b[^\n]*\s\S*pids\.max\b/,
  /\btruncate\b[^\n]*\s\S*pids\.max\b/,
];

// systemd's own routes to the same ceiling.
const SYSTEMD_WRITE = [
  /\bsystemctl\b[^\n]*\bset-property\b[^\n]*TasksMax=/i,
  /\bsystemd-run\b[^\n]*TasksMax=/i,
];

// A unit file or drop-in carrying TasksMax=, written by any of the usual means.
const UNIT_WRITE = /(?:>>?\s*\S*|\btee\b(?:\s+-\S+)*\s+\S*|\bof=\S*)\S*(?:\.conf|\.slice|\.scope|\.service)\b/;

const REASON = [
  "Blocked: writing a pids ceiling (pids.max / TasksMax) from a command line.",
  "",
  "A cgroup at its pids cap is wedged because a workload inside it is runaway. Raising the cap",
  "gives the runaway more room and leaves everyone else in the cgroup unable to fork — that is",
  "exactly how the 2026-08-07 incident ran 4096 -> 14200 without ever recovering.",
  "",
  "Correct action — kill the runaway:",
  "  * find the saturated scope:   pids-guard reads it; ~/.local/state/pids-guard/events.log",
  "  * kill an agent scope:        systemctl --user stop <scope>",
  "  * wedged, with a session to save:",
  "      pids-rescue <cgroup-path> --keep-comm claude,codex,cursor-agent",
  "  * pids-guard already kills any agent scope past its threshold, automatically.",
  "",
  "Caps are owned by confine.sh and by the slice units in modules/monitor/systemd/user/.",
  "Change them there, in a commit. There is no override flag.",
].join("\n");

function isWrite(command) {
  if (FILE_WRITE.some((re) => re.test(command))) return true;
  if (SYSTEMD_WRITE.some((re) => re.test(command))) return true;
  return /TasksMax=/i.test(command) && UNIT_WRITE.test(command);
}

export function evaluate(command) {
  if (typeof command !== "string" || !command || !CAP_TOKEN.test(command)) {
    return { deny: false, reason: "" };
  }
  if (SANCTIONED.test(command) || !isWrite(command)) return { deny: false, reason: "" };
  return { deny: true, reason: REASON };
}

if (import.meta.url === `file://${process.argv[1]}`) {
  let out;
  try {
    out = evaluate(process.argv.slice(2).join(" "));
  } catch (err) {
    out = {
      deny: true,
      reason: `pids-cap-guard could not evaluate this command (${err.message}); refusing.\n${REASON}`,
    };
  }
  if (out.deny) {
    process.stderr.write(`${out.reason}\n`);
    process.exit(1);
  }
  process.exit(0);
}
