#!/usr/bin/env bash
# slt1-runner.sh — RFC-002 §20 Phase 3 Synthetic Self-Loop Test
# THR-63 / SLT.1
#
# Fires §1, §2, §3, §4 CRITICAL via sandbox; verifies cleanup oracle
# + log-snapshot guard (Refinement 2). Real cron is not interrupted.
#
# Watchdog source baseline:
#   sha256: c003f29dd13614067ca373e2d29f72360effdfb3a40b892175912e3f6881ab83
#   path:   /Users/openclaw/.openclaw/workspace/scripts/circuit-breaker-watchdog.py
#
# Exit codes:
#   0  success — 4 synthetic fires + clean cleanup + real state/ unchanged
#   1  sandbox leak — flag(s) appeared in real state/ with NO real FIRE in real log
#   2  real cron fired CRITICAL during test window — synthetic results recorded
#      but FINAL ASSERTION inconclusive; separate triage required

set -uo pipefail

WATCHDOG=/Users/openclaw/.openclaw/workspace/scripts/circuit-breaker-watchdog.py
SANDBOX=/tmp/slt1-sandbox
REAL_STATE_DIR=/Users/openclaw/.openclaw/workspace/state
REAL_LOG=/tmp/openclaw/circuit-breaker-watchdog.log
RUNNER_LOG=$SANDBOX/runner.log

EXIT_OK=0
EXIT_LEAK=1
EXIT_REAL_CRON_FIRED=2

# ------------------------------------------------------------------
# Pre-flight
# ------------------------------------------------------------------
mkdir -p "$SANDBOX"
echo "=== SLT.1 RUNNER START $(date -u '+%Y-%m-%dT%H:%M:%SZ') ==="

# Real state/ baseline — must be zero flag files to begin.
real_flags_pre=$(ls "$REAL_STATE_DIR"/*.flag 2>/dev/null | wc -l | tr -d ' ')
if [ "$real_flags_pre" != "0" ]; then
  echo "ABORT: real state/ has $real_flags_pre flag file(s) before test"
  ls -la "$REAL_STATE_DIR"/*.flag 2>/dev/null
  exit $EXIT_LEAK
fi

# Real watchdog log snapshot (Refinement 2 guard)
log_snapshot_size=$(stat -f '%z' "$REAL_LOG" 2>/dev/null || echo 0)
log_snapshot_mtime_epoch=$(stat -f '%m' "$REAL_LOG" 2>/dev/null || echo 0)
log_snapshot_mtime_iso=$(date -u -r "$log_snapshot_mtime_epoch" '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || echo "epoch=0")
echo "Pre-test snapshot:"
echo "  real state/ flag count: $real_flags_pre"
echo "  real log size: $log_snapshot_size  mtime_utc: $log_snapshot_mtime_iso"

# Sandbox-isolated sentinel paths
SBX_PAUSED=$SANDBOX/paused.flag
SBX_WARN=$SANDBOX/warn.flag
SBX_STALE_B1=$SANDBOX/stale-b1.flag
SBX_STALE_B2=$SANDBOX/stale-b2.flag
SBX_B3=$SANDBOX/b3.flag
SBX_STALE_B3=$SANDBOX/stale-b3.flag
SBX_B4=$SANDBOX/b4.flag
SBX_STALE_B4=$SANDBOX/stale-b4.flag

# ------------------------------------------------------------------
# Per-fire helper
# ------------------------------------------------------------------
fire_breaker() {
  local section_label="$1"
  local mode="$2"
  local expected_fire_token="$3"

  echo
  echo "--- FIRE $section_label  $(date -u '+%H:%M:%SZ') ---"

  python3 "$WATCHDOG" \
    --simulate "$mode" \
    --sentinel-path "$SBX_PAUSED" \
    --warning-sentinel-path "$SBX_WARN" \
    --stale-b1-sentinel-path "$SBX_STALE_B1" \
    --stale-b2-sentinel-path "$SBX_STALE_B2" \
    --b3-sentinel-path "$SBX_B3" \
    --stale-b3-sentinel-path "$SBX_STALE_B3" \
    --b4-sentinel-path "$SBX_B4" \
    --stale-b4-sentinel-path "$SBX_STALE_B4" \
    --log-path "$RUNNER_LOG" \
    --test-suffix " [SYNTHETIC SLT.1 — $section_label]"
  local rc=$?
  echo "watchdog rc=$rc"

  if [ "$rc" -ne 0 ]; then
    echo "STOP: watchdog rc=$rc for $section_label (process did not exit cleanly)"
    return $EXIT_LEAK
  fi

  if ! grep -q "$expected_fire_token" "$RUNNER_LOG"; then
    echo "STOP: expected '$expected_fire_token' not found in runner log after $section_label"
    return $EXIT_LEAK
  fi
  echo "ASSERT: '$expected_fire_token' in runner log ✓"

  if [ ! -f "$SBX_PAUSED" ]; then
    echo "STOP: $SBX_PAUSED not written for $section_label"
    return $EXIT_LEAK
  fi
  echo "ASSERT: sandbox pause-flag written ✓"

  rm -f "$SANDBOX"/*.flag
  if ls "$SANDBOX"/*.flag >/dev/null 2>&1; then
    echo "STOP: sandbox cleanup failed"
    return $EXIT_LEAK
  fi

  local real_flags_after
  real_flags_after=$(ls "$REAL_STATE_DIR"/*.flag 2>/dev/null | wc -l | tr -d ' ')
  if [ "$real_flags_after" != "0" ]; then
    echo "STOP: real state/ leaked $real_flags_after flag(s) during $section_label fire"
    ls -la "$REAL_STATE_DIR"/*.flag 2>/dev/null
    return $EXIT_LEAK
  fi
  echo "VERIFY: real state/ clean ✓"

  return 0
}

# ------------------------------------------------------------------
# Sequence 4 fires
# ------------------------------------------------------------------
fire_breaker "§1" "critical"              "FIRE CRITICAL"    || exit $?
echo "CLEANUP: §1 done"

fire_breaker "§2" "self_traffic_critical" "FIRE B2_CRITICAL" || exit $?
echo "CLEANUP: §2 done"

fire_breaker "§3" "b3_critical"           "FIRE B3_CRITICAL" || exit $?
echo "CLEANUP: §3 done"

fire_breaker "§4" "b4_critical"           "FIRE B4_CRITICAL" || exit $?
echo "CLEANUP: §4 done"

# ------------------------------------------------------------------
# Refinement 2 — log-snapshot guard final assertion
# ------------------------------------------------------------------
echo
echo "=== FINAL ASSERTION  $(date -u '+%H:%M:%SZ') ==="

real_flags_post=$(ls "$REAL_STATE_DIR"/*.flag 2>/dev/null | wc -l | tr -d ' ')

if [ "$real_flags_post" = "0" ]; then
  echo "FINAL: real state/ unchanged — exit 0 (success)"
  rm -rf "$SANDBOX"
  exit $EXIT_OK
fi

echo "FINAL: real state/ has $real_flags_post flag(s); investigating cause"
ls -la "$REAL_STATE_DIR"/*.flag 2>/dev/null

# Extract real log content emitted strictly after pre-test snapshot byte offset.
# Watchdog log is append-only (Python `open(path, 'a')`); /tmp is not rotated
# within runner runtime (~10-15s). Byte offset tail is safe.
real_log_now_size=$(stat -f '%z' "$REAL_LOG")
echo "Real log delta: $log_snapshot_size → $real_log_now_size bytes"

new_real_log=$(tail -c +$((log_snapshot_size + 1)) "$REAL_LOG" 2>/dev/null)
real_fires=$(echo "$new_real_log" | grep -E "FIRE |B[1234]_CRITICAL")

if [ -n "$real_fires" ]; then
  echo "REAL CRON FIRED during test window — separate triage required"
  echo "Real FIRE entries since snapshot:"
  echo "$real_fires"
  echo "Synthetic results: §1/§2/§3/§4 fired and cleaned up; FINAL ASSERTION inconclusive"
  exit $EXIT_REAL_CRON_FIRED
else
  echo "Sandbox leak — flag(s) in real state/ with NO corresponding real FIRE in real log"
  echo "Leaked file(s):"
  ls -la "$REAL_STATE_DIR"/*.flag 2>/dev/null
  exit $EXIT_LEAK
fi
