#!/usr/bin/env bash
# twohost-web-xbox.sh — the CROSS-BOX twohost_web pair on the real hosts (real QUIC over
# tailscale, real access rule): kitsubito = role B (owner), this box = role A (requester).
# Role A's three #[test] fns are separate nextest cells = the separate-invocation witness for
# the two mutation arms (A: owner deny → allow; B: owner strips Range). Mutation applies on the
# OWNER (kitsubito) tree, never here — the arms live in serve_web.
#
# usage: twohost-web-xbox.sh <local-worktree> <kitsubito-worktree> <label> [none|A|B]
# Sequence: (1) kitsubito role B launched DETACHED (nohup + flock on the output dir + pid file,
# because a stopped local ssh does NOT stop its remote command — memory 2026-09-06);
# (2) local role A; (3) wait for B by remote pid; (4) fetch B's raw/exit; (5) revert the owner
# mutation by `git checkout --` and assert 0 dirty lines. procs-before is logged on both ends.
set -u
LW=$1; KW=$2; LABEL=$3; MUT=${4:-none}
KHOST=reavus@kitsubito
PEER_K=100.98.197.12   # kitsubito (tailscale)  — matches golden.yml role-A step
PEER_H=100.68.35.65    # hfenduleam (tailscale) — matches golden.yml role-B step
OUT=$LW/.spt/twohost-web-xbox/$LABEL; mkdir -p "$OUT"
ROUT=/tmp/twohost-web-xbox-$LABEL
SECRET=rig-doyle-w1-$LABEL
FILE=crates/spt-daemon/src/webproxy.rs
case "$MUT" in
  none) OLD=; NEW= ;;
  A) OLD='if access_check(&subject, origin_node, surface::WEB, InboundClass::Unsolicited).is_deny() {'
     NEW='if false && access_check(&subject, origin_node, surface::WEB, InboundClass::Unsolicited).is_deny() {' ;;
  B) OLD='let plan = webserve::plan_file(len, content_type, range.as_deref());'
     NEW='let plan = webserve::plan_file(len, content_type, None);' ;;
  *) echo "unknown mutation $MUT" >&2; exit 8 ;;
esac

# ── (0) owner-side mutation on kitsubito (delimiter files, MATCH_COUNT==1, print the landed line)
if [ "$MUT" != none ]; then
  printf '%s' "$OLD" > "$OUT/old.txt"; printf '%s' "$NEW" > "$OUT/new.txt"
  scp -q "$OUT/old.txt" "$OUT/new.txt" "$KHOST:/tmp/" || exit 7
  ssh "$KHOST" "cd $KW && c=\$(grep -cF -- \"\$(cat /tmp/old.txt)\" $FILE); echo MATCH_COUNT=\$c; [ \"\$c\" = 1 ] || exit 7; \
    OLD=/tmp/old.txt NEW=/tmp/new.txt perl -0pi -e 'BEGIN{local \$/; open F,\"<\",\$ENV{OLD}; \$o=<F>; open G,\"<\",\$ENV{NEW}; \$n=<G>;} s/\\Q\$o\\E/\$n/' $FILE; \
    grep -nF -- \"\$(cat /tmp/new.txt)\" $FILE" | tee "$OUT/mutated.line" || { echo "REFUSE: mutation did not land on kitsubito" >&2; exit 6; }
fi

# ── (1) role B on kitsubito, detached + flocked; build first so the lock is not held by a compile
date -u +%FT%TZ > "$OUT/started"
# A plain ssh command is a NON-LOGIN shell on kitsubito: PATH has no ~/.cargo/bin, so `cargo` is
# "No such file" (exit 127, measured 03:05Z run "none"; the kitsubito-remote-launch-traps memory).
ssh "$KHOST" "export PATH=\$HOME/.cargo/bin:\$HOME/.local/bin:\$PATH; mkdir -p $ROUT && cd $KW && echo procs-before: \$(pgrep -c -f 'twohost_web-[0-9a-f]+' || true) > $ROUT/procs-before && \
  unset OWL_SESSION_ID SPT_AGENT_ID SPT_ENDPOINT_ID SPT_HOME; \
  cargo nextest run -p spt-daemon --test twohost_web --no-run > $ROUT/build.raw 2>&1; echo \$? > $ROUT/build.exit; \
  SPT_TWO_HOST=1 SPT_TWO_HOST_ROLE=b SPT_TWO_HOST_SECRET=$SECRET SPT_TWO_HOST_PEER_IP=$PEER_H SPT_TWO_HOST_WAIT_SECS=${WAIT:-600} \
  nohup flock -n $ROUT/lock cargo nextest run -p spt-daemon --test twohost_web --no-fail-fast --no-capture > $ROUT/b.raw 2>&1 < /dev/null & \
  echo \$! > $ROUT/b.pid; cat $ROUT/b.pid" | tee "$OUT/b.pid"
sleep 20

# ── (2) role A here (local pool must already hold the built test bin — build first, once)
cd "$LW" || exit 9
unset OWL_SESSION_ID SPT_AGENT_ID SPT_ENDPOINT_ID SPT_HOME
echo "procs-before: $(tasklist | grep -ci twohost_web || true)" > "$OUT/procs-before"
cargo nextest run -p spt-daemon --test twohost_web --no-run > "$OUT/build.raw" 2>&1; echo $? > "$OUT/build.exit"
SPT_TWO_HOST=1 SPT_TWO_HOST_ROLE=a SPT_TWO_HOST_SECRET=$SECRET SPT_TWO_HOST_PEER_IP=$PEER_K SPT_TWO_HOST_WAIT_SECS=${WAIT:-600} \
  cargo nextest run -p spt-daemon --test twohost_web --no-fail-fast --no-capture > "$OUT/a.raw" 2>&1
echo $? > "$OUT/a.exit"

# ── (3) wait for B by remote pid, (4) fetch, (5) revert
BPID=$(cat "$OUT/b.pid")
ssh "$KHOST" "while kill -0 $BPID 2>/dev/null; do sleep 5; done; wait $BPID 2>/dev/null; echo done" >/dev/null
scp -q "$KHOST:$ROUT/b.raw" "$KHOST:$ROUT/build.exit" "$KHOST:$ROUT/procs-before" "$OUT/" 2>/dev/null
ssh "$KHOST" "grep -c Summary $ROUT/b.raw; grep -E 'PASS|FAIL' $ROUT/b.raw | grep role_b; tail -3 $ROUT/b.raw" > "$OUT/b.summary" 2>&1
if [ "$MUT" != none ]; then
  ssh "$KHOST" "cd $KW && git checkout -- $FILE && echo DIRTY_LINES=\$(git diff -- $FILE | wc -l)" | tee "$OUT/revert.dirty"
fi
date -u +%FT%TZ > "$OUT/finished"
echo "== $LABEL mut=$MUT a.exit=$(cat "$OUT/a.exit") Summaries a=$(grep -c Summary "$OUT/a.raw")"
grep -E 'PASS|FAIL|panicked at' "$OUT/a.raw" | grep -E 'role_a|panicked' | sed 's/^/A: /'
sed 's/^/B: /' "$OUT/b.summary"
