#!/usr/bin/env bash
# local-dispatch-guard — both branches, on the guard and on every dispatcher wired to it.
set -uo pipefail

REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)"
GUARD_SRC="$REPO/modules/workstation/claude/bin/local-dispatch-guard"
CA_SH="$REPO/modules/harness/wrappers/ca.sh"
CODEX_SH="$REPO/modules/harness/wrappers/codex.sh"
CDX_PY="$REPO/modules/systray/cdx.py"

pass=0 fail=0
ok()   { pass=$((pass+1)); printf 'ok   %s\n' "$1"; }
bad()  { fail=$((fail+1)); printf 'FAIL %s: %s\n' "$1" "$2" >&2; }
check(){ [[ "$2" == "$3" ]] && ok "$1" || bad "$1" "expected [$3] got [$2]"; }

FAKE_HOME="$(mktemp -d)"
trap 'rm -rf "$FAKE_HOME"' EXIT
mkdir -p "$FAKE_HOME/.claude/bin"
install -m 755 "$GUARD_SRC" "$FAKE_HOME/.claude/bin/local-dispatch-guard"
GUARD="$FAKE_HOME/.claude/bin/local-dispatch-guard"

# codex.sh rebuilds PATH from $HOME and refuses with exit 3 when no cdx binary resolves,
# which would mask the guard. A stub satisfies the precondition and can never reach a model.
mkdir -p "$FAKE_HOME/.npm-global/bin"
printf '#!/bin/sh\nexit 0\n' >"$FAKE_HOME/.npm-global/bin/cdx"
chmod 755 "$FAKE_HOME/.npm-global/bin/cdx"

# --- the guard itself -------------------------------------------------------
err="$("$GUARD" probe 2>&1 </dev/null)"; rc=$?
check "guard/headless-denies" "$rc" 97
case "$err" in
  *"seat-remote launch"*) ok "guard/message-names-remote-path" ;;
  *) bad "guard/message-names-remote-path" "stderr lacked the remote command: $err" ;;
esac
case "$err" in
  *OD_LOCAL_DISPATCH_OK* | *HARNESS_SEAT_CONTAINER*)
    bad "guard/message-hides-bypass" "the deny message advertises an env bypass" ;;
  *) ok "guard/message-hides-bypass" ;;
esac

# script -qec gives a real pty without touching a browser or a danger test.
script -qec "'$GUARD' probe" /dev/null >/dev/null 2>&1; rc=$?
check "guard/tty-admits" "$rc" 0

HARNESS_SEAT_CONTAINER=1 "$GUARD" probe </dev/null >/dev/null 2>&1
check "guard/on-box-admits" "$?" 0

OD_LOCAL_DISPATCH_OK=1 "$GUARD" probe </dev/null >/dev/null 2>&1
check "guard/launcher-marker-admits" "$?" 0

# --- dispatchers ------------------------------------------------------------
# A seat config with "enabled": false selects the local path, which is the path the guard defends.
WS="$(mktemp -d)"; trap 'rm -rf "$FAKE_HOME" "$WS"' EXIT
SEAT_OFF="$FAKE_HOME/seat-remote-off.json"
python3 - "$REPO/modules/harness/seat/seat-remote.json" "$SEAT_OFF" <<'PY'
import json, sys
cfg = json.load(open(sys.argv[1]))
cfg["enabled"] = False
json.dump(cfg, open(sys.argv[2], "w"))
PY
export HARNESS_SEAT_CONFIG="$SEAT_OFF"

CA_ARGS=(--workspace "$WS" --trust p --task-slug t --model composer-1 --timeout 5)
CODEX_ARGS=(--workspace "$WS" --trust p --task-slug t --model gpt-5.3-codex --timeout 5)

out="$(HOME="$FAKE_HOME" "$CA_SH" "${CA_ARGS[@]}" 2>&1 </dev/null)"; rc=$?
check "ca/headless-denies" "$rc" 97
case "$out" in
  *"seat-remote launch"*) ok "ca/message-names-remote-path" ;;
  *) bad "ca/message-names-remote-path" "stderr lacked the remote command: $out" ;;
esac

out="$(HOME="$FAKE_HOME" "$CODEX_SH" "${CODEX_ARGS[@]}" 2>&1 </dev/null)"; rc=$?
check "codex/headless-denies" "$rc" 97
case "$out" in
  *"seat-remote launch"*) ok "codex/message-names-remote-path" ;;
  *) bad "codex/message-names-remote-path" "stderr lacked the remote command: $out" ;;
esac

# The human branch: a terminal on any std fd must never be refused. The dispatch fails
# later for its own reasons (no engine in this fixture) — it must simply not be 97.
script -qec "HOME='$FAKE_HOME' '$CA_SH' ${CA_ARGS[*]}" /dev/null >/dev/null 2>&1
[[ "$?" -ne 97 ]] && ok "ca/tty-admits" || bad "ca/tty-admits" "a terminal caller was refused with 97"
script -qec "HOME='$FAKE_HOME' '$CODEX_SH' ${CODEX_ARGS[*]}" /dev/null >/dev/null 2>&1
[[ "$?" -ne 97 ]] && ok "codex/tty-admits" || bad "codex/tty-admits" "a terminal caller was refused with 97"

# cdx guards its CLI entry point, never the importable main() the systray tests drive.
out="$(cd "$REPO/modules/systray" && HOME="$FAKE_HOME" python3 "$CDX_PY" chat 2>&1 </dev/null)"; rc=$?
check "cdx/headless-denies" "$rc" 97
case "$out" in
  *"seat-remote launch"*) ok "cdx/message-names-remote-path" ;;
  *) bad "cdx/message-names-remote-path" "stderr lacked the remote command: $out" ;;
esac

# factory-dispatch.sh wraps ca.sh instead of spawning a seat itself, so it carries no guard
# of its own. This measures that the wrapped copy is the guarded one — the deny reaches the
# factory path, and its 97 is not swallowed by the ledger.
# factory-dispatch.sh also places itself onto a buildbox before dispatching. A fake
# self-host registry makes this probe resolve local, same as it would on a real registry
# host, so this test keeps exercising the guard and not the placement seam.
mkdir -p "$FAKE_HOME/.claude"
cat >"$FAKE_HOME/.claude/buildbox-hosts.json" <<'JSON'
{"schema_version":1,"hosts":[{"name":"guardprobe-self","machine_id":"deadbeefdeadbeefdeadbeefdeadbeef","ssh_alias":"guardprobe-self","state":"reachable","roles":["build"],"notes":"test fixture","rustdesk":null,"access":{"lan":null,"tailscale_ip":null,"tailscale_ssh":null}}],"orders":{"build":["guardprobe-self"]}}
JSON
FD_SH="$REPO/modules/harness/tools/factory-dispatch.sh"
out="$(HOME="$FAKE_HOME" OVERDECK_SEAT_HOST="guardprobe-self" "$FD_SH" --task guardprobe --workspace "$WS" \
  --trust p --model composer-1 --timeout 5 2>&1 </dev/null)"; rc=$?
check "factory-dispatch/headless-denies" "$rc" 97
case "$out" in
  *"seat-remote launch"*) ok "factory-dispatch/message-names-remote-path" ;;
  *) bad "factory-dispatch/message-names-remote-path" "stderr lacked the remote command: $out" ;;
esac

# --- inside a seat container the guard is absent by design ------------------
# The container IS the sanctioned execution plane; a workstation-only guard binary never
# ships into the image, and its absence must not refuse the seat's own dispatch.
rm -f "$GUARD"
HOME="$FAKE_HOME" HARNESS_SEAT_CONTAINER=1 "$CA_SH" \
  --workspace "$WS" --trust p --task-slug t --model composer-1 --timeout 5 >/dev/null 2>&1
[[ "$?" -ne 97 ]] && ok "ca/in-container-admits" || bad "ca/in-container-admits" "a seat container dispatch was refused with 97"
HOME="$FAKE_HOME" HARNESS_SEAT_CONTAINER=1 "$CODEX_SH" \
  --workspace "$WS" --trust p --task-slug t --model gpt-5.3-codex --timeout 5 >/dev/null 2>&1
[[ "$?" -ne 97 ]] && ok "codex/in-container-admits" || bad "codex/in-container-admits" "a seat container dispatch was refused with 97"

# --- fail-closed when the guard is absent -----------------------------------
HOME="$FAKE_HOME" "$CA_SH" \
  --workspace "$WS" --trust p --task-slug t --model composer-1 --timeout 5 >/dev/null 2>&1
check "ca/missing-guard-denies" "$?" 97

printf 'local-dispatch-guard: %d passed, %d failed\n' "$pass" "$fail"
[[ "$fail" -eq 0 ]]
