#!/usr/bin/env bash
# buildbox harden keeps its whole deadman-protected sequence and additionally runs the
# fleet harden pass inside that window. No real buildbox is contacted: the registry, the
# fleet declaration, ssh, tailscale and the harden module are all fixtures under $TMP.
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
BUILDBOX="$ROOT/modules/buildbox/bin/buildbox"
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT

pass=0
fail=0
ok() { pass=$((pass + 1)); printf 'PASS %s\n' "$1"; }
bad() { fail=$((fail + 1)); printf 'FAIL %s\n' "$1"; }

# ---------------------------------------------------------------- fixtures

write_registry() {
  cat >"$TMP/buildbox-hosts.json" <<'EOF'
{
  "schema_version": 1,
  "hosts": [
    {
      "name": "stubbox",
      "ssh_alias": "stubbox",
      "state": "reachable",
      "machine_id": null,
      "roles": ["builder"],
      "access": {
        "lan": null,
        "tailscale_ip": { "host": "127.0.0.1", "port": 2222, "user": "test", "identity_file": null },
        "tailscale_ssh": null
      },
      "rustdesk": null,
      "notes": "stubbox fixture"
    }
  ],
  "orders": {
    "build": ["stubbox"]
  }
}
EOF
}

write_fleet() { # profiles-json-array
  cat >"$TMP/fleet.json" <<EOF
{
  "schema_version": 1,
  "nodes": {
    "workstation": {
      "transport": "local",
      "roles": ["control"],
      "profiles": ["workstation"],
      "execution": "last-resort"
    },
    "stubbox": {
      "transport": "ssh",
      "host_ref": "stubbox",
      "roles": ["builder"],
      "profiles": $1,
      "execution": "normal"
    }
  },
  "fallback": {
    "enabled": true,
    "node": "workstation",
    "requires_all_unavailable": ["stubbox"],
    "max_concurrent_local_jobs": 1,
    "activation_windows": 3,
    "health_window_sec": 60,
    "lease_ttl_sec": 900
  }
}
EOF
}

# The stub harden module keeps the real module's interaction shape — arm, per-item root
# write, reboot, boot-id proof, disarm, all through the injected sshExec — and replaces
# only the box. What it wrote lands in a host-state file, so a later assertion reads the
# post-state instead of the fact that a seam was called.
write_stub_harden() {
  cat >"$TMP/stub-harden.mjs" <<'EOF'
import { appendFileSync, readFileSync, writeFileSync } from "node:fs";

const order = process.env.ORDER_LOG;
const specPath = process.env.STUB_HARDEN_SPEC;
const statePath = process.env.STUB_HARDEN_STATE;

export class HardenError extends Error {
  constructor(message) {
    super(message);
    this.name = "HardenError";
  }
}

export async function hardenNode(nodeName, items, transport, options) {
  const hardenItems = items.filter((item) => item.mode === "harden");
  appendFileSync(
    order,
    `HARDEN-CALL ${nodeName} items=${hardenItems.length} transport=${transport.name}\n`,
  );
  appendFileSync(order, `HARDEN-BUDGET ${options.rebootTimeoutSec}\n`);

  const run = async (remote) => {
    const result = await options.sshExec(["ssh", "-p", "2222", "test@127.0.0.1", remote]);
    if (result.code !== 0) {
      throw new HardenError(`${remote} failed: ${result.stderr.trim()}`);
    }
    return result.stdout.trim();
  };

  const drift = new Set(JSON.parse(readFileSync(specPath, "utf8")).drift);
  const state = JSON.parse(readFileSync(statePath, "utf8"));
  const changed = [];

  if (drift.size > 0) {
    await run(`sudo -n /usr/local/sbin/buildbox-deadman arm --id ${nodeName} --deadline-sec 1200`);
    for (const item of hardenItems) {
      if (!drift.has(item.id) || item.owner !== "fleet") continue;
      state[item.destination] = item.content_digest;
      appendFileSync(order, `HARDEN-WRITE ${item.id} ${item.destination}\n`);
      changed.push(item.id);
    }
    writeFileSync(statePath, JSON.stringify(state, null, 2));
    writeFileSync(specPath, JSON.stringify({ drift: [] }));
  }

  let rebooted = false;
  if (changed.length > 0) {
    const before = await run("cat /proc/sys/kernel/random/boot_id");
    await run("sudo -n systemctl reboot");
    for (let attempt = 0; attempt < 4 && !rebooted; attempt += 1) {
      rebooted = (await run("cat /proc/sys/kernel/random/boot_id")) !== before;
    }
    if (!rebooted) {
      throw new HardenError(`${nodeName} did not come back under a new boot id`);
    }
    await run(`sudo -n /usr/local/sbin/buildbox-deadman disarm ${nodeName}`);
  }

  return {
    node: nodeName,
    revision: "stub",
    changed,
    rebooted,
    finalAudit: { node: nodeName, revision: "stub", ok: true, drift: [], unreachable: false },
  };
}
EOF
}

# A host whose deadman refuses a second arm exactly as modules/buildbox/deadman does, and
# whose boot id only turns over one read after a reboot: a proof taken against a boot id
# read before some earlier reboot would pass on a host that has not booted yet.
write_fake_ssh() {
  mkdir -p "$TMP/bin"
  cat >"$TMP/bin/ssh" <<'EOF'
#!/usr/bin/env bash
remote="${*: -1}"
printf 'SSH %s\n' "$remote" >>"${ORDER_LOG:?}"
st="${FAKE_HOST_DIR:?}"
case "$remote" in
  *"/proc/sys/kernel/random/boot_id"*)
    if [ -f "$st/pending" ]; then rm -f "$st/pending"; cat "$st/bootid-prev"; else cat "$st/bootid"; fi
    exit 0 ;;
  *"buildbox-deadman arm"*)
    [ ! -f "$st/armed" ] || { echo "already armed for $(cat "$st/armed")" >&2; exit 1; }
    printf '%s\n' "$remote" >"$st/armed"; exit 0 ;;
  *"buildbox-deadman disarm"*)
    [ -f "$st/armed" ] || { echo "not armed" >&2; exit 1; }
    rm -f "$st/armed"; exit 0 ;;
  *"systemctl reboot"*)
    cp "$st/bootid" "$st/bootid-prev"
    n=$(( $(cat "$st/boots") + 1 ))
    echo "$n" >"$st/boots"
    echo "boot-$n" >"$st/bootid"
    : >"$st/pending"
    exit 0 ;;
esac
cat >/dev/null
exit "${FAKE_SSH_RC:-0}"
EOF
  chmod +x "$TMP/bin/ssh"
  cat >"$TMP/bin/tailscale" <<'EOF'
#!/usr/bin/env bash
printf '{"Peer":{"x":{"HostName":"stubbox","DNSName":"stubbox.tail.ts.net.","TailscaleIPs":["100.64.0.9"]}}}\n'
EOF
  chmod +x "$TMP/bin/tailscale"
}

reset_host() {
  rm -rf "$TMP/host"
  mkdir -p "$TMP/host"
  echo 1 >"$TMP/host/boots"
  echo "boot-1" >"$TMP/host/bootid"
  echo "boot-1" >"$TMP/host/bootid-prev"
}

NODE_DIR="$(dirname "$(command -v node)")"
SANDBOX_PATH="$TMP/bin:/usr/bin:/bin:$NODE_DIR"

mkdir -p "$TMP/home"
printf '{ "enabled": false }\n' >"$TMP/build-remote.json"
write_registry
write_stub_harden
write_fake_ssh

run_harden() { # drift-ids-json-array
  : >"$TMP/order.log"
  : >"$TMP/ledger.jsonl"
  printf '{"drift":%s}\n' "$1" >"$TMP/harden-spec.json"
  printf '{}\n' >"$TMP/host-state.json"
  reset_host
  set +e
  env -i \
    PATH="$SANDBOX_PATH" \
    HOME="$TMP/home" \
    TERM=dumb \
    ORDER_LOG="$TMP/order.log" \
    FAKE_HOST_DIR="$TMP/host" \
    FAKE_SSH_RC="${FAKE_SSH_RC:-0}" \
    FLEET_LEDGER="$TMP/ledger.jsonl" \
    HARDEN_DEADMAN_SEC=360 \
    BUILDBOX_HOSTS_CONFIG="$TMP/buildbox-hosts.json" \
    BUILDBOX_CONFIG="$TMP/build-remote.json" \
    BUILDBOX_FLEET_HARDEN="$TMP/stub-harden.mjs" \
    DECKCTL_FLEET_FILE="$TMP/fleet.json" \
    STUB_HARDEN_SPEC="$TMP/harden-spec.json" \
    STUB_HARDEN_STATE="$TMP/host-state.json" \
    "$BUILDBOX" harden stubbox >"$TMP/out" 2>&1
  rc=$?
  set -e
}

# Line index of the first (or last) order-log entry matching a pattern; -1 when absent.
idx() { grep -n -- "$1" "$TMP/order.log" | head -1 | cut -d: -f1 || true; }
last_idx() { grep -n -- "$1" "$TMP/order.log" | tail -1 | cut -d: -f1 || true; }
count() { grep -c -- "$1" "$TMP/order.log" || true; }

write_fleet '["buildbox", "buildbox-root"]'

# The first real root file the buildbox-root profile installs, and the bytes it must carry.
read -r HARDEN_ID HARDEN_DEST HARDEN_DIGEST HARDEN_SRC <<<"$(
  cd "$ROOT" && DECKCTL_FLEET_FILE="$TMP/fleet.json" BUILDBOX_HOSTS_CONFIG="$TMP/buildbox-hosts.json" \
    CPU_GUARD_ACTIVE=1 node --input-type=module -e '
import { loadFleet } from "./lib/fleet/loader.mjs";
import { expandNode } from "./lib/fleet/expand.mjs";
const fleet = loadFleet(process.env.DECKCTL_FLEET_FILE);
const item = expandNode(fleet, "stubbox").find((entry) => entry.mode === "harden");
process.stdout.write(`${item.id} ${item.destination} ${item.content_digest} ${item.source.path}\n`);
' 2>/dev/null
)" || true

[ -n "${HARDEN_DIGEST:-}" ] || { echo "fixture: buildbox-root expands to no harden item" >&2; exit 2; }

# ------------------------------------------------- 1. a clean engine leaves the sequence as it was

run_harden '[]'
order_ok=0
if [ "$(idx 'buildbox-deadman arm')" -lt "$(idx 'apply.sh')" ] \
  && [ "$(idx 'apply.sh')" -lt "$(idx '^HARDEN-CALL ')" ] \
  && [ "$(idx '^HARDEN-CALL ')" -lt "$(idx 'systemctl reboot')" ] \
  && [ "$(idx 'systemctl reboot')" -lt "$(idx 'buildbox-deadman disarm')" ]; then
  order_ok=1
fi
if [ "$rc" -eq 0 ] && [ "$order_ok" = 1 ]; then
  ok 'the harden pass runs after the deadman is armed and before the reboot'
else
  bad "the harden pass runs after the deadman is armed and before the reboot (rc=$rc order=$(tr '\n' '|' <"$TMP/order.log"))"
fi
steps_ok=1
for step in 'install -m 0755 /tmp/buildbox-deadman' 'buildbox-deadman arm' 'apply.sh' \
  '/proc/sys/kernel/random/boot_id' 'systemctl reboot' 'buildbox-deadman disarm'; do
  [ "$(count "$step")" -ge 1 ] || { steps_ok=0; bad "pre-existing harden step still runs: $step"; }
done
if [ "$steps_ok" = 1 ] \
  && grep -q '^FLEET fanout-gate ok ' "$TMP/out" \
  && grep -q '^FLEET second-door ok ' "$TMP/out" \
  && grep -q '^HARDEN stubbox change=.* reboot-proven$' "$TMP/out" \
  && grep -q '"event": "reboot-proven"' "$TMP/ledger.jsonl"; then
  ok 'every pre-existing harden step still runs and the host is still reboot-proven'
else
  bad 'every pre-existing harden step still runs and the host is still reboot-proven'
fi
if [ "$(count '^HARDEN-CALL ')" = 1 ] \
  && grep -q '^HARDEN-CALL stubbox items=[1-9][0-9]* transport=ssh$' "$TMP/order.log" \
  && grep -q '^HARDEN-ENGINE OK stubbox items=[1-9][0-9]* changed=0 rebooted=no$' "$TMP/out"; then
  ok 'the pass receives the fleet node resolved from host_ref with its expanded harden items'
else
  bad "the pass receives the fleet node resolved from host_ref with its expanded harden items ($(grep '^HARDEN-ENGINE' "$TMP/out" | tr '\n' '|'))"
fi
budget="$(sed -n 's/^HARDEN-BUDGET //p' "$TMP/order.log" | tail -1)"
# 360s window, 60s reserved for the reboot below, 30s slack: 270s less however long
# arming and apply.sh took.
if [ -n "$budget" ] && [ "$budget" -le 270 ] && [ "$budget" -ge 200 ]; then
  ok 'the pass gets only the part of the armed window the reboot below does not need'
else
  bad "the pass gets only the part of the armed window the reboot below does not need (budget=${budget:-none}, deadman=360, reboot wait=60)"
fi
if [ "$(count 'systemctl reboot')" = 1 ] \
  && [ "$(python3 -c 'import json,sys; print(json.loads(sys.stdin.readlines()[-1])["bootIdAfter"])' <"$TMP/ledger.jsonl")" = "$(cat "$TMP/host/bootid")" ]; then
  ok 'a clean engine adds no reboot and the reboot proof still names the boot the host came up under'
else
  bad 'a clean engine adds no reboot and the reboot proof still names the boot the host came up under'
fi

# ------------------------------------------------- 2. engine drift is written and observed in the post-state

run_harden "[\"$HARDEN_ID\"]"
if [ "$rc" -eq 0 ] \
  && [ "$(python3 -c 'import json,sys; s=json.load(open(sys.argv[1])); print(s.get(sys.argv[2], "-"))' "$TMP/host-state.json" "$HARDEN_DEST")" = "$HARDEN_DIGEST" ] \
  && [ "$HARDEN_DIGEST" = "$(sha256sum "$ROOT/$HARDEN_SRC" 2>/dev/null | cut -d' ' -f1)" ]; then
  ok 'the drifted root file is on the host afterwards, carrying the repo bytes the item declares'
else
  bad "the drifted root file is on the host afterwards, carrying the repo bytes the item declares (rc=$rc dest=$HARDEN_DEST)"
fi
if [ "$(count 'buildbox-deadman arm')" = 1 ] && [ "$(count 'buildbox-deadman disarm')" = 1 ]; then
  ok 'the pass reuses the armed window instead of arming a second deadman the host would refuse'
else
  bad "the pass reuses the armed window instead of arming a second deadman the host would refuse (arm=$(count 'buildbox-deadman arm') disarm=$(count 'buildbox-deadman disarm'))"
fi
if [ "$(last_idx '^HARDEN-WRITE ')" -gt "$(idx 'buildbox-deadman arm')" ] \
  && [ "$(last_idx '^HARDEN-WRITE ')" -lt "$(last_idx 'systemctl reboot')" ]; then
  ok 'every engine write lands inside the armed window and before the last reboot'
else
  bad 'every engine write lands inside the armed window and before the last reboot'
fi
if [ "$(python3 -c 'import json,sys; print(json.loads(sys.stdin.readlines()[-1])["bootIdAfter"])' <"$TMP/ledger.jsonl")" = "$(cat "$TMP/host/bootid")" ] \
  && [ ! -f "$TMP/host/armed" ]; then
  ok 'the reboot proof names the boot that followed the change, and the window is closed'
else
  bad "the reboot proof names the boot that followed the change, and the window is closed (ledger=$(tail -1 "$TMP/ledger.jsonl") bootid=$(cat "$TMP/host/bootid"))"
fi

# ------------------------------------------------- 3. zero selected items is a hard error

write_fleet '["buildbox"]'
run_harden '[]'
if [ "$rc" -ne 0 ] \
  && grep -q 'HARDEN-ENGINE FAIL stubbox stubbox selects no harden item' "$TMP/out" \
  && grep -q 'buildbox-root' "$TMP/out"; then
  ok 'a node that no longer declares buildbox-root fails the harden, naming the lost profile'
else
  bad "a node that no longer declares buildbox-root fails the harden, naming the lost profile (rc=$rc)"
fi
if [ "$(count '^HARDEN-CALL ')" = 0 ] \
  && [ "$(count 'buildbox-deadman disarm')" = 0 ] \
  && [ -f "$TMP/host/armed" ] \
  && ! grep -q '^HARDEN stubbox change=.* reboot-proven$' "$TMP/out"; then
  ok 'the zero-item failure stops before the reboot and leaves the deadman to revert'
else
  bad "the zero-item failure stops before the reboot and leaves the deadman to revert (calls=$(count '^HARDEN-CALL ') disarm=$(count 'buildbox-deadman disarm') armed=$([ -f "$TMP/host/armed" ] && echo yes || echo no))"
fi

# ------------------------------------------------- 4. the pass cannot mask, or be masked by, apply.sh

write_fleet '["buildbox", "buildbox-root"]'
FAKE_SSH_RC=1 run_harden '[]'
if [ "$rc" -ne 0 ] && [ "$(count '^HARDEN-CALL ')" = 0 ]; then
  ok 'a failing apply.sh still exits nonzero and never reaches the engine pass'
else
  bad "a failing apply.sh still exits nonzero and never reaches the engine pass (rc=$rc)"
fi

# -------------- 5. the suppression pattern still matches the commands the real module sends

# The scenarios above drive a stub, so nothing else ties the adapter's suppression pattern
# to the strings lib/fleet/harden.mjs actually sends.
pattern="$(sed -n 's/^const OUTER_DEADMAN = \/\(.*\)\/;$/\1/p' "$ROOT/modules/buildbox/bin/buildbox")"
real_arm="$(grep -o 'buildbox-deadman arm --id [^`]*' "$ROOT/lib/fleet/harden.mjs" | head -1)"
real_disarm="$(grep -o 'buildbox-deadman disarm [^`]*' "$ROOT/lib/fleet/harden.mjs" | head -1)"
if [ -n "$pattern" ] && [ -n "$real_arm" ] && [ -n "$real_disarm" ] \
  && printf '%s\n' "$real_arm" | grep -Eq "$pattern" \
  && printf '%s\n' "$real_disarm" | grep -Eq "$pattern"; then
  ok 'the adapter suppresses the arm and disarm commands the real harden module sends'
else
  bad "the adapter suppresses the arm and disarm commands the real harden module sends (pattern=${pattern:-none} arm=${real_arm:-none} disarm=${real_disarm:-none})"
fi

# ---------------- 6. user-managed slices stay outside the legacy root apply path

write_fleet '["buildbox", "buildbox-root"]'
slice_items="$(cd "$ROOT" && DECKCTL_FLEET_FILE="$TMP/fleet.json" BUILDBOX_HOSTS_CONFIG="$TMP/buildbox-hosts.json" \
  CPU_GUARD_ACTIVE=1 node --input-type=module -e '
import { loadFleet } from "./lib/fleet/loader.mjs";
import { expandNode } from "./lib/fleet/expand.mjs";
const fleet = loadFleet(process.env.DECKCTL_FLEET_FILE);
for (const item of expandNode(fleet, "stubbox")) {
  if (item.id === "buildbox:user-config:systemd/ci.slice" || item.id === "buildbox-root:slice-ceilings:agent.slice.d/90-ceiling.conf") console.log(item.id);
}
')"
if printf '%s\n' "$slice_items" | grep -qx 'buildbox:user-config:systemd/ci.slice' \
  && printf '%s\n' "$slice_items" | grep -qx 'buildbox-root:slice-ceilings:agent.slice.d/90-ceiling.conf' \
  && ! grep -q '^install_tree systemd-user-root ' "$ROOT/modules/buildbox/host-config/apply.sh"; then
  ok 'fleet expansion owns user slices; legacy apply.sh has no dead user-root install'
else
  bad "fleet expansion owns user slices; legacy apply.sh has no dead user-root install ($slice_items)"
fi


# ---------------- 7. apply.sh ships every root destination the engine pass will audit

# The install list in host-config/apply.sh and HOST_CONFIG_INSTALLS in lib/fleet/expand.mjs
# are two independent declarations of one set, and nothing links them. A destination the
# engine knows but apply.sh does not ship is drift the pass finds on a freshly applied
# host: apply, reboot, write, reboot again, two reboots inside one deadman window.
# Root trees only; user-level slices are engine-owned (case 6). Destination and bytes only.
coverage_gaps() { # apply.sh-path
  local applysh="$1" payload shipped subdir dest mode src rel trees=0
  payload="$(mktemp -d)"
  shipped="$(mktemp)"
  cp -a "$ROOT/modules/buildbox/host-config/." "$payload/"
  while read -r _ subdir dest mode; do
    trees=$((trees + 1))
    [ -d "$payload/$subdir" ] || continue
    while IFS= read -r -d '' src; do
      rel="${src#"$payload/$subdir"/}"
      printf '%s %s\n' "$dest/$rel" "$(sha256sum "$src" | cut -d' ' -f1)" >>"$shipped"
    done < <(find "$payload/$subdir" -type f -print0)
  done < <(grep -E '^install_tree ' "$applysh")
  local bins=0
  while read -r dest; do
    bins=$((bins + 1))
    printf '%s %s\n' "$dest" "$(sha256sum "$payload/bin/$(basename "$dest")" | cut -d' ' -f1)" >>"$shipped"
  done < <(grep -E 'install -D -m [0-7]+ -o root -g root "\$HERE/bin/' "$applysh" | awk '{print $NF}')
  [ "$trees" -gt 0 ] && [ "$bins" -gt 0 ] || { echo "PARSE-FAILED $applysh"; rm -rf "$payload" "$shipped"; return 0; }
  (cd "$ROOT" && DECKCTL_FLEET_FILE="$TMP/fleet.json" BUILDBOX_HOSTS_CONFIG="$TMP/buildbox-hosts.json" \
    CPU_GUARD_ACTIVE=1 SHIPPED="$shipped" node --input-type=module -e '
import { readFileSync } from "node:fs";
import { loadFleet } from "./lib/fleet/loader.mjs";
import { expandNode } from "./lib/fleet/expand.mjs";
const fleet = loadFleet(process.env.DECKCTL_FLEET_FILE);
const shipped = new Map(
  readFileSync(process.env.SHIPPED, "utf8").split("\n").filter(Boolean).map((line) => line.split(" ")),
);
for (const item of expandNode(fleet, "stubbox").filter((entry) => entry.mode === "harden" && entry.id.startsWith("buildbox-root:host-config:"))) {
  const have = shipped.get(item.destination);
  if (have === undefined) process.stdout.write(`NOT-SHIPPED ${item.destination}\n`);
  else if (have !== item.content_digest) process.stdout.write(`DIGEST-MISMATCH ${item.destination}\n`);
}
')
  rm -rf "$payload" "$shipped"
}

write_fleet '["buildbox", "buildbox-root"]'
gaps="$(coverage_gaps "$ROOT/modules/buildbox/host-config/apply.sh")"
if [ -z "$gaps" ]; then
  ok 'apply.sh ships every root destination and byte the engine harden pass audits'
else
  bad "apply.sh ships every root destination and byte the engine harden pass audits ($(printf '%s' "$gaps" | tr '\n' '|'))"
fi

sed '/^install_tree sysctl.d /d' "$ROOT/modules/buildbox/host-config/apply.sh" >"$TMP/apply-no-sysctl.sh"
mutant_gaps="$(coverage_gaps "$TMP/apply-no-sysctl.sh")"
if printf '%s\n' "$mutant_gaps" | grep -q '^NOT-SHIPPED /etc/sysctl.d/'; then
  ok 'dropping an install_tree line is reported as an uncovered harden destination'
else
  bad "dropping an install_tree line is reported as an uncovered harden destination (got=$(printf '%s' "$mutant_gaps" | tr '\n' '|'))"
fi

printf '\n%s passed, %s failed\n' "$pass" "$fail"
[ "$fail" -eq 0 ]
