#!/usr/bin/env bash
# gate-w2-f17.sh — doyle's TARGETED re-gate of todlando's F17 fixup on top of f3c8495b, run
# SEQUENTIALLY in his released pool from the gate worktree RE-POINTED (detached) at the fixup tip.
# Everything at f3c8495b (F1-F16, arms A/B1/B2/C, 2403+7+1+1 cells) already holds; this driver
# re-witnesses only what the fixup touches plus the one cell that never executed green:
#   claim . treqs . prebuild . xtask check . clippy -D warnings . nextest LIST (name-assert incl. the
#   NEW classify cell) . spt-daemon + spt-net libs . the three W2 e2e --no-capture (witnesses) .
#   XBOX pair none (helper cell PASS in BOTH raws, Summary==1 each) . mutation D = revert the classify
#   arm on THIS tree (unit cell red) AND on the OWNER tree (kitsubito, helper cell red) . revert.
# Arm D's OLD/NEW come from env (D_OLD / D_NEW, exact source lines of the fixup) — REFUSED if unset.
set -u
GW=/c/Users/decid/Documents/projects/spt-core/.worktrees/gate-w2-401a19ad
POOL='C:\Users\decid\Documents\projects\spt-core\.worktrees\ws272-w2\target'
KW=/home/reavus/spt-core-gate-w2
TIP=${TIP:?TIP=<fixup sha> required}
: "${D_OLD:?D_OLD required}"; : "${D_NEW:?D_NEW required}"; : "${CLASSIFY_CELL:?CLASSIFY_CELL=<new unit cell name> required}"
OUT=$GW/.spt/gate-f17-${TIP:0:8}; PAIR=$GW/.spt/twohost-web-xbox; mkdir -p "$OUT"; cd "$GW" || exit 9
if ! mkdir "$OUT/lock" 2>/dev/null; then echo "REFUSED: $OUT/lock exists — another battery owns this output dir" >&2; exit 9; fi
trap 'rmdir "$OUT/lock" 2>/dev/null' EXIT
export CARGO_TARGET_DIR="$POOL"
unset OWL_SESSION_ID SPT_AGENT_ID SPT_ENDPOINT_ID SPT_HOME SPT_POOL_UNCHECKED
LOG=$OUT/driver.log
leg() { local n=$1; shift; case ",${SKIP:-}," in *",$n,"*) echo "[$(date -u +%H:%M:%SZ)] LEG $n SKIPPED (SKIP=$SKIP)" >> "$LOG"; echo SKIPPED > "$OUT/$n.exit"; return 0;; esac; echo "[$(date -u +%H:%M:%SZ)] LEG $n START" >> "$LOG"; "$@" > "$OUT/$n.raw" 2>&1; local e=$?; echo "$e" > "$OUT/$n.exit"; echo "[$(date -u +%H:%M:%SZ)] LEG $n EXIT $e summaries=$(grep -c 'Summary \[' "$OUT/$n.raw") panics=$(grep -c 'panicked at' "$OUT/$n.raw") free_gb=$(df -BG --output=avail /c 2>/dev/null | tail -1 | tr -dc 0-9)" >> "$LOG"; }
pairline() {
  for r in a b; do
    echo "  xbox $1 $r: exit=$(cat "$PAIR/$1/$r.exit" 2>/dev/null) summaries=$(grep -c 'Summary \[' "$PAIR/$1/$r.raw" 2>/dev/null) serve_for=$(grep -c 'WEB_SERVE_FOR:' "$PAIR/$1/$r.raw" 2>/dev/null) web_stream=$(grep -c 'WEB_STREAM' "$PAIR/$1/$r.raw" 2>/dev/null) unknown=$(grep -c 'family=Unknown' "$PAIR/$1/$r.raw" 2>/dev/null) zero_s_cells=$(grep -cE 'PASS \[ +0\.0[0-9]s\]' "$PAIR/$1/$r.raw" 2>/dev/null)" >> "$LOG"
    grep -E 'PASS|FAIL|TIMEOUT' "$PAIR/$1/$r.raw" 2>/dev/null | grep two_host_web | sed 's/^/    /' >> "$LOG"
  done
}

# identity = the checked-out REF, never the dir name (ir57)
HEAD=$(git rev-parse HEAD)
[ "$HEAD" = "$TIP" ] || { echo "REFUSED: HEAD $HEAD != TIP $TIP — re-point the gate tree first" | tee -a "$LOG" >&2; exit 9; }
echo "gate-w2-f17 at $HEAD dirty_tracked=$(git status --short -uno | wc -l) $(date -u +%FT%TZ) treqs=$(traceable-reqs --version) pool=$POOL" > "$LOG"
echo "frozen=${FROZEN:-UNFROZEN} $(sha256sum "${FROZEN:-.spt}"/gate-w2-f17.sh "${FROZEN:-.spt}"/twohost-web-xbox.sh "${FROZEN:-.spt}"/mutate.py "${FROZEN:-.spt}"/twohost-web-local.sh 2>/dev/null | cut -c1-12 | tr '\n' ' ')" >> "$LOG"
echo "procs-before: cargo=$(tasklist | grep -ci '^cargo') nextest=$(tasklist | grep -ci nextest) rustc=$(tasklist | grep -ci '^rustc') env-leak: OWL=${OWL_SESSION_ID:-unset} AGENT=${SPT_AGENT_ID:-unset} EP=${SPT_ENDPOINT_ID:-unset} HATCH=${SPT_POOL_UNCHECKED:-unset}" >> "$LOG"
echo "kitsubito clone: $(ssh reavus@kitsubito "cd $KW && git rev-parse HEAD && git status --porcelain | wc -l" 2>&1 | tr '\n' ' ')" >> "$LOG"
echo "arm D sites on this tree: $(grep -cF -- "$D_OLD" crates/spt-daemon/src/dispatch.rs) (must be 1)" >> "$LOG"

leg 0-claim env SPT_POOL_UNCHECKED=1 cargo run -p xtask -- pool-claim --foreign-pool --pool "$POOL" --label "gate-w2-f17-${TIP:0:8}"
echo "claim verdict: $(grep -m1 -E 'CLAIM|claim|TAKEOVER|REFUS|POOL' "$OUT/0-claim.raw" | head -1)" >> "$LOG"
leg 1-treqs    traceable-reqs check
leg 2-prebuild cargo build -p spt -p mock-adapter --bins
leg 4-xtask    cargo run -p xtask -- check
leg 5-clippy   cargo clippy --workspace --all-targets -- -D warnings

FILTER='((package(spt-msg) | package(spt-store) | package(spt-daemon) | package(spt-net)) & kind(lib)) | (package(spt) & (kind(bin) | binary(io_events_undriven_kinds_e2e) | binary(webserve_attachment_e2e) | binary(webserve_cross_node_e2e)))'
leg 6-list cargo nextest list -E "$FILTER"
for want in "$CLASSIFY_CELL" a_reap_shaped_pass_cannot_clobber_a_concurrent_add a_msg_envelope_is_excerpted_by_its_text_and_other_envelopes_are_untouched \
            a_typed_msg_envelope_still_carries_the_recipients_monic a_typed_user_msg_envelope_is_still_verbatim_and_monicless \
            a_typed_msg_envelope_carries_the_trust_warning a_senders_own_monic_and_warning_are_stripped_before_ours_are_attached \
            the_one_short_id_names_the_message_on_both_edges an_attachment_is_snapshot_served_fetched_back_and_named_by_its_message \
            a_scoped_entry_is_still_served_on_loopback; do
  echo "list-has $want: $(grep -c "$want" "$OUT/6-list.raw")" >> "$LOG"
done
echo "list-has xnode binary: $(grep -c 'webserve_cross_node_e2e' "$OUT/6-list.raw")" >> "$LOG"
# PRESENCE FIRST (hertz RIDER 5 addendum): the twohost_web binary is outside FILTER above, so its cells are
# listed here by NAME; a quality check on a cell that never ran is vacuous and prints nothing, which reads as pass.
leg 6b-list-twohost cargo nextest list -p spt-daemon --test twohost_web
for want in two_host_web_deny_role_a two_host_web_fetch_role_a two_host_web_helper_role_a two_host_web_range_role_a two_host_web_role_b; do
  echo "twohost-list-has $want: $(grep -c "$want" "$OUT/6b-list-twohost.raw")" >> "$LOG"
done
HELPER_PRESENT=$(grep -c two_host_web_helper_role_a "$OUT/6b-list-twohost.raw")

leg 7-libs     cargo nextest run --no-fail-fast -E '(package(spt-daemon) | package(spt-net)) & kind(lib)'
echo "classify cell in 7-libs: PASS=$(grep -E "$CLASSIFY_CELL" "$OUT/7-libs.raw" | grep -c PASS)" >> "$LOG"
leg 8-ioedges  cargo nextest run --no-fail-fast -p spt --test io_events_undriven_kinds_e2e --no-capture
leg 9-attach   cargo nextest run --no-fail-fast -p spt --test webserve_attachment_e2e --no-capture
leg 10-xnode   cargo nextest run --no-fail-fast -p spt --test webserve_cross_node_e2e --no-capture
for w in 'W2_ARM=one-short-id' 'W2_ARM=fetch-exit3' 'F1_ARM='; do echo "witness $w: ioedges=$(grep -c "$w" "$OUT/8-ioedges.raw") attach=$(grep -c "$w" "$OUT/9-attach.raw") xnode=$(grep -c "$w" "$OUT/10-xnode.raw")" >> "$LOG"; done

# ── HELPER WITNESS (ruled doyle 2026-09-07 ~12:55Z): the F17 defect was first witnessed END TO END on the ONE-BOX
# pair (03:31Z run at f3c8495b, .spt/twohost-web/none: A `HELPER_SERVE_FOR … outcome=unanswered`, B `DISPATCH:4:Unknown`),
# so the one-box pair drives the REAL dispatcher on both roles and IS the witness; it runs on BOTH OSes (this tree,
# kitsubito clone at TIP). The cross-box helper cell needs kitsubito->hfenduleam inbound UDP into a hash-named test
# exe, blocked by F18 (tailnet ACL one-way + host firewall, both PRE-DATING the lane; golden runs no twohost_web
# cross-box) until the operator's rule lands: with rule=0 its helper/range TIMEOUTs are F18-INFRA, not a verdict.
witness() {  # <dir> <label> : the HELPER WITNESS line for one a.raw/b.raw pair (presence FIRST, then quality)
  local d=$1 l=$2 b_sf b_reg b_unk a_help a_skip a_ran
  b_sf=$(grep -c 'WEB_SERVE_FOR:' "$d/b.raw" 2>/dev/null); b_reg=$(grep -c 'WEB_SERVE_FOR:.*outcome=registered' "$d/b.raw" 2>/dev/null)
  b_unk=$(grep -c 'family=Unknown' "$d/b.raw" 2>/dev/null)
  a_ran=$(grep -cE 'two_host_web_helper_role_a' "$d/a.raw" 2>/dev/null)
  a_help=$(grep -E 'two_host_web_helper_role_a' "$d/a.raw" 2>/dev/null | grep -E 'PASS \[ +[0-9]+\.[0-9]+s\]' | grep -vE 'PASS \[ +0\.0[0-9]s\]' | grep -c .)
  a_skip=$(grep -E 'two_host_web_role_b' "$d/a.raw" 2>/dev/null | grep -cE 'PASS \[ +0\.0[0-9]s\]')
  for r in a b; do
    echo "  $l $r: exit=$(cat "$d/$r.exit" 2>/dev/null) summaries=$(grep -c 'Summary \[' "$d/$r.raw" 2>/dev/null) panics=$(grep -c 'panicked at' "$d/$r.raw" 2>/dev/null)" >> "$LOG"
    grep -E 'PASS|FAIL|TIMEOUT' "$d/$r.raw" 2>/dev/null | grep two_host_web | sort -u | sed 's/^/    /' >> "$LOG"
  done
  echo "[$(date -u +%H:%M:%SZ)] HELPER WITNESS $l: present-in-list=${HELPER_PRESENT:-0} ran-in-a.raw=${a_ran:-0} | owner WEB_SERVE_FOR lines=${b_sf:-0} registered=${b_reg:-0} family=Unknown=${b_unk:-0}, helper_role_a non-skip PASS=${a_help:-0}, role_b-on-A skipped-as-it-must=${a_skip:-0} -> $( if [ "${HELPER_PRESENT:-0}" -eq 0 ] || [ "${a_ran:-0}" -eq 0 ]; then echo NOT-A-WITNESS:NEVER-RAN; elif [ "${b_reg:-0}" -gt 0 ] && [ "${a_help:-0}" -gt 0 ]; then echo WITNESSED; else echo NOT-A-WITNESS:RAN-BUT-VACUOUS; fi )" >> "$LOG"
}
# THREE-VALUE probe (hertz 12:59Z): a two-value read turns its own failure into "absent", and F18-INFRA is an
# absorbing bucket — every real cross-box red would be filed as known-blocked. PROBE-FAILED = neither witness nor INFRA.
RULE_RAW=$(netsh advfirewall firewall show rule name=spt-twohost-rig 2>&1); RULE_EXIT=$?
if [ -n "$RULE_RAW" ] && echo "$RULE_RAW" | grep -q 'Rule Name'; then RULE=1; RULE_STATE=PRESENT
elif [ -n "$RULE_RAW" ] && echo "$RULE_RAW" | grep -q 'No rules match'; then RULE=0; RULE_STATE=ABSENT
else RULE=0; RULE_STATE=PROBE-FAILED; fi
if [ "$RULE_STATE" = ABSENT ]; then XCLASS='F18-INFRA (not a verdict)'; else XCLASS='UNCLASSIFIED: probe failed, this run cannot tell INFRA from a real red'; fi
echo "[$(date -u +%H:%M:%SZ)] firewall probe: exit=$RULE_EXIT state=$RULE_STATE raw=$(printf '%s' "$RULE_RAW" | head -3 | tr -s ' ' | tr '\n' '|') (PRESENT: xbox pair is a witness too; ABSENT: xbox helper/range = F18-INFRA, xbox-D skipped; PROBE-FAILED: xbox helper/range unclassified)" >> "$LOG"
LBL="f17-${TIP:0:8}"
LOCAL=$GW/.spt/twohost-web
LRIG="${FROZEN:-.spt/rig}/twohost-web-local.sh"

# (5a) one-box pair on THIS box (Windows), clean tree at TIP
WAIT=120 leg 11-local-none bash "$LRIG" "$GW" "$LBL-local-none" none
witness "$LOCAL/$LBL-local-none" "local-win-none"

# (5b) one-box pair on kitsubito (Linux), clone at TIP; the rig script travels as the FROZEN copy, raws come back by scp
KLBL="$LBL-local-none"
# the rig file is CRLF on this tree (git autocrlf) and Linux bash dies at `set -u<CR>` (control 12:57Z): ship an LF copy
tr -d '\r' < "$LRIG" > "$OUT/twohost-web-local.lf.sh"
scp -q "$OUT/twohost-web-local.lf.sh" "reavus@kitsubito:/tmp/twohost-web-local-${TIP:0:8}.sh" || echo "[$(date -u +%H:%M:%SZ)] scp of the local rig to kitsubito FAILED" >> "$LOG"
leg 11k-local-none ssh reavus@kitsubito "bash -lc 'cd $KW && echo kitsubito HEAD=\$(git rev-parse --short HEAD) && WAIT=120 bash /tmp/twohost-web-local-${TIP:0:8}.sh $KW $KLBL none'"
mkdir -p "$OUT/k-local-none"; scp -q "reavus@kitsubito:$KW/.spt/twohost-web/$KLBL/*" "$OUT/k-local-none/" 2>>"$LOG"
echo "  k-local-none files: $(ls "$OUT/k-local-none" 2>/dev/null | tr '\n' ' ')" >> "$LOG"
witness "$OUT/k-local-none" "local-linux-none"

# (5c) cross-box pair, owner = kitsubito clone at TIP: A->B cells (deny/fetch) must PASS; helper/range are
#      F18-INFRA while rule=0 (their TIMEOUT is the receiver dropping inbound, measured 3 runs), a witness when rule=1
if [ "$RULE" -gt 0 ]; then XW=600; else XW=120; fi
WAIT=$XW leg 11-xbox-none bash "${FROZEN:-.spt}/twohost-web-xbox.sh" "$GW" "$KW" "$LBL-none" none
pairline "$LBL-none"
if [ "$RULE" -gt 0 ]; then witness "$PAIR/$LBL-none" "xbox-none"; else
  echo "[$(date -u +%H:%M:%SZ)] xbox-none: rule=$RULE_STATE -> helper/range = $XCLASS; A->B cells: $(grep -E 'PASS|FAIL|TIMEOUT' "$PAIR/$LBL-none/a.raw" 2>/dev/null | grep -E 'deny_role_a|fetch_role_a' | sort -u | tr -s ' ' | tr '\n' ';')" >> "$LOG"
fi

# (6) mutation D on THIS tree -> the classify unit cell must red AND the one-box helper cell must red (owner drops
#     ServeFor again: family=Unknown>0, registered=0, helper_role_a no non-skip PASS) while deny/fetch stay green
FREE_GB=$(df -BG --output=avail /c 2>/dev/null | tail -1 | tr -dc 0-9)
if [ "${FREE_GB:-0}" -lt "${MUT_FLOOR_GB:-8}" ]; then echo "[$(date -u +%H:%M:%SZ)] MUTATION D SKIPPED: free_gb=$FREE_GB under floor ${MUT_FLOOR_GB:-8} (disk incident 2026-09-07)" >> "$LOG"; echo DISK-FLOOR-SKIP > "$OUT/mut-D.apply"; else python "${FROZEN:-.spt}/mutate.py" apply D > "$OUT/mut-D.apply" 2>&1; fi
if grep -q 'MUTATION LANDED' "$OUT/mut-D.apply"; then
  leg mD-libs cargo nextest run --no-fail-fast -E '(package(spt-daemon) | package(spt-net)) & kind(lib)'
  echo "mutation D unit: classify cell FAIL=$(grep -E "$CLASSIFY_CELL" "$OUT/mD-libs.raw" | grep -c FAIL) PASS=$(grep -E "$CLASSIFY_CELL" "$OUT/mD-libs.raw" | grep -c PASS)" >> "$LOG"
  WAIT=120 leg mD-local bash "$LRIG" "$GW" "$LBL-local-D" none
  witness "$LOCAL/$LBL-local-D" "local-win-D(EXPECT NOT-A-WITNESS:RAN-BUT-VACUOUS, deny/fetch PASS)"
  echo "mutation D pair: deny/fetch on A: $(grep -E 'PASS|FAIL|TIMEOUT' "$LOCAL/$LBL-local-D/a.raw" 2>/dev/null | grep -E 'deny_role_a|fetch_role_a' | sort -u | tr -s ' ' | tr '\n' ';')" >> "$LOG"
  python "${FROZEN:-.spt}/mutate.py" revert D > "$OUT/mut-D.revert" 2>&1; echo "[$(date -u +%H:%M:%SZ)] MUTATION D reverted: $(tail -1 "$OUT/mut-D.revert")" >> "$LOG"
else
  echo "[$(date -u +%H:%M:%SZ)] MUTATION D did not land locally (see mut-D.apply)" >> "$LOG"
fi

# (7) owner-tree mutation D over the cross-box pair: meaningful only with the rule present (with rule=0 the helper
#     cell is already a TIMEOUT and a red-on-red proves nothing)
if [ "$RULE" -gt 0 ]; then
  D_OLD="$D_OLD" D_NEW="$D_NEW" WAIT=600 leg 12-xbox-D bash "${FROZEN:-.spt}/twohost-web-xbox.sh" "$GW" "$KW" "$LBL-D" D
  pairline "$LBL-D"
else
  echo "[$(date -u +%H:%M:%SZ)] 12-xbox-D SKIPPED: rule=$RULE_STATE" >> "$LOG"
fi
echo "[$(date -u +%H:%M:%SZ)] GATE-F17 DONE dirty_tracked=$(git status --short -uno | wc -l) HEAD=$(git rev-parse --short HEAD) kitsubito_dirty=$(ssh reavus@kitsubito "cd $KW && git status --porcelain | wc -l" 2>&1)" >> "$LOG"
echo "procs-after: cargo=$(tasklist | grep -ci '^cargo') nextest=$(tasklist | grep -ci nextest) spt_from_pool=$(wmic process where "ExecutablePath like '%ws272-w2%'" get ProcessId 2>/dev/null | grep -c '[0-9]')" >> "$LOG"
