#!/usr/bin/env bash
# _contract-probe.sh — smoke-test each wrapper against a mock engine.
# Validates the WRAPPER-CONTRACT flag set and exit-code map 0/124/2/3.
#
# USAGE: _contract-probe.sh <wrapper> [wrapper2 ...]
# Example: _contract-probe.sh wrappers/na.sh wrappers/ca.sh wrappers/codex.sh

set -uo pipefail

MOCK_BIN_DIR=""
MOCK_HTTP_DIR=""

say_ok() { printf '  %s\n' "$1"; }
say_fail() { printf '  ✗ %s\n' "$1"; }

setup_mock_bin() {
  MOCK_BIN_DIR="$(mktemp -d)"
  cat > "$MOCK_BIN_DIR/cursor-agent" <<'EOF'
#!/usr/bin/env bash
set -eu

printf '%s\n' "$*" >> "${MOCK_CURSOR_LOG:?}"

if [[ "${1:-}" == "create-chat" ]]; then
  printf '%s\n' "${MOCK_CHAT_ID:-chat-mock-123}"
  exit 0
fi

if [[ "${1:-}" == "status" ]]; then
  printf '%s\n' "${MOCK_STATUS_JSON:-{\"ok\":true}}"
  exit "${MOCK_STATUS_EXIT_CODE:-0}"
fi

if [[ "${1:-}" == "models" ]]; then
  printf '%s\n' "${MOCK_MODELS_OUTPUT:-composer-2.5}"
  exit "${MOCK_MODELS_EXIT_CODE:-0}"
fi

[[ -n "${MOCK_FORCE_EMPTY_OUTPUT:-}" ]] && exit "${MOCK_EXIT_CODE:-0}"
printf '%s\n' "${MOCK_RUN_OUTPUT:-{\"type\":\"result\",\"detail\":\"done\"}}"
exit "${MOCK_EXIT_CODE:-0}"
EOF
  cat > "$MOCK_BIN_DIR/claude" <<'EOF'
#!/usr/bin/env bash
set -eu

printf '%s\n' "$*" >> "${MOCK_CLAUDE_LOG:?}"
[[ -n "${MOCK_FORCE_EMPTY_OUTPUT:-}" ]] && exit "${MOCK_EXIT_CODE:-0}"
printf '%s\n' "${MOCK_CLAUDE_OUTPUT:-{\"type\":\"result\",\"subtype\":\"success\"}}"
exit "${MOCK_EXIT_CODE:-0}"
EOF
  cat > "$MOCK_BIN_DIR/mock-engine" <<'EOF'
#!/usr/bin/env bash
set -eu

printf '%s\n' "$*" >> "${MOCK_CODEX_LOG:?}"

if [[ "${1:-}" == "--version" ]]; then
  printf '%s\n' "${MOCK_CODEX_VERSION_OUTPUT:-codex 0.0.0}"
  exit "${MOCK_CODEX_VERSION_EXIT_CODE:-0}"
fi

if [[ "${1:-}" == "models" ]]; then
  printf '%s\n' "${MOCK_CODEX_MODELS_OUTPUT:-{\"data\":[]}}"
  exit "${MOCK_CODEX_MODELS_EXIT_CODE:-0}"
fi

[[ -n "${MOCK_FORCE_EMPTY_OUTPUT:-}" ]] && exit "${MOCK_EXIT_CODE:-0}"
printf '%s\n' "${MOCK_CODEX_RUN_OUTPUT:-{\"type\":\"thread.started\",\"thread_id\":\"thread-mock-123\"}}"
printf '%s\n' "${MOCK_CODEX_RUN_OUTPUT_2:-{\"type\":\"item.completed\",\"item\":{\"content\":[{\"type\":\"output_text\",\"text\":\"{\\\"clean\\\":true,\\\"findings\\\":[]}\"}]}}}"
exit "${MOCK_EXIT_CODE:-0}"
EOF
  cat > "$MOCK_BIN_DIR/pi" <<'EOF'
#!/usr/bin/env bash
set -eu

printf '%s\n' "$*" >> "${MOCK_PI_LOG:?}"

if [[ "${1:-}" == "--version" ]]; then
  printf '%s\n' "${MOCK_PI_VERSION_OUTPUT:-pi 0.0.0}"
  exit "${MOCK_PI_VERSION_EXIT_CODE:-0}"
fi

if [[ "${1:-}" == "models" ]]; then
  printf '%s\n' "${MOCK_PI_MODELS_OUTPUT:-{\"models\":[]}}"
  exit "${MOCK_PI_MODELS_EXIT_CODE:-0}"
fi

[[ -n "${MOCK_FORCE_EMPTY_OUTPUT:-}" ]] && exit "${MOCK_EXIT_CODE:-0}"
printf '%s\n' "${MOCK_PI_RUN_OUTPUT:-{\"event\":\"session.started\",\"session_id\":\"pi-session-123\"}}"
printf '%s\n' "${MOCK_PI_RUN_OUTPUT_2:-{\"event\":\"agent.finished\",\"result\":{\"status\":\"ok\"},\"usage\":{\"input_tokens\":11,\"output_tokens\":7,\"cached_input_tokens\":2}}}"
exit "${MOCK_EXIT_CODE:-0}"
EOF
  cat > "$MOCK_BIN_DIR/ccr-up-mock.sh" <<'EOF'
#!/usr/bin/env bash
echo '{"up":true}'
EOF
  cat > "$MOCK_BIN_DIR/grok" <<'EOF'
#!/usr/bin/env bash
set -eu

printf '%s\n' "$*" >> "${MOCK_GROK_LOG:?}"

if [[ "${1:-}" == "version" || "${1:-}" == "--version" ]]; then
  printf '%s\n' "${MOCK_GROK_VERSION_OUTPUT:-grok 0.0.0-mock}"
  exit "${MOCK_GROK_VERSION_EXIT_CODE:-0}"
fi

if [[ "${1:-}" == "models" ]]; then
  printf '%s\n' "${MOCK_GROK_MODELS_OUTPUT:-grok-4.5}"
  exit "${MOCK_GROK_MODELS_EXIT_CODE:-0}"
fi

[[ -n "${MOCK_FORCE_EMPTY_OUTPUT:-}" ]] && exit "${MOCK_EXIT_CODE:-0}"
printf '%s\n' "${MOCK_GROK_RUN_OUTPUT:-{\"sessionId\":\"grok-session-mock\"}}"
exit "${MOCK_EXIT_CODE:-0}"
EOF
  chmod +x "$MOCK_BIN_DIR"/cursor-agent "$MOCK_BIN_DIR"/claude "$MOCK_BIN_DIR"/mock-engine "$MOCK_BIN_DIR"/pi "$MOCK_BIN_DIR"/ccr-up-mock.sh "$MOCK_BIN_DIR"/grok

  MOCK_HTTP_DIR="$(mktemp -d)"
  printf '%s\n' '{"ok": true, "service": "ccr"}' > "$MOCK_HTTP_DIR/na-health.json"
  printf '%s\n' '{"data": [{"id": "cohere/north-mini-code:free"}]}' > "$MOCK_HTTP_DIR/na-models.json"
}

cleanup_mocks() {
  [[ -n "$MOCK_BIN_DIR" && -d "$MOCK_BIN_DIR" ]] && rm -rf "$MOCK_BIN_DIR"
  [[ -n "$MOCK_HTTP_DIR" && -d "$MOCK_HTTP_DIR" ]] && rm -rf "$MOCK_HTTP_DIR"
}

setup_env_files() {
  local tmp_skill_dir
  tmp_skill_dir="$(mktemp -d)"
  mkdir -p "$tmp_skill_dir/wrappers" "$tmp_skill_dir/workflows/lib" "$tmp_skill_dir/.claude/bin"
  touch "$tmp_skill_dir/CURSOR.env" "$tmp_skill_dir/NORTH.env" "$tmp_skill_dir/workflows/lib/CODEX.env"
  # HOME is redirected here, so the wrappers look for their dispatch guard inside this fixture.
  # Absent, every wrapper fails closed at exit 97 before the contract under test is reached.
  printf '#!/usr/bin/env bash\nexit 0\n' > "$tmp_skill_dir/.claude/bin/local-dispatch-guard"
  chmod +x "$tmp_skill_dir/.claude/bin/local-dispatch-guard"
  echo "$tmp_skill_dir"
}

contract_wrapper() {
  local wrapper_path="$1"
  shift
  if [[ "${wrapper_path##*/}" == "codex.sh" ]]; then
    HARNESS_SEAT_CONTAINER=1 "$wrapper_path" "$@" --permission-mode safe
    return
  fi
  "$wrapper_path" "$@"
}

run_wrapper() {
  local wrapper_path="$1"
  local mock_exit_code="$2"
  shift 2

  local tmp_workspace
  tmp_workspace="$(mktemp -d)"
  local wrapper_name
  wrapper_name="$(basename "$wrapper_path")"
  local model_arg="mock-model"

  if [[ "$wrapper_name" == "pi.sh" ]]; then
    mkdir -p "$HOME/.pi/agent"
    printf '%s\n' '{"openai-codex":{"type":"oauth","access":"a","refresh":"r","expires":9999999999999,"accountId":"acct"}}' > "$HOME/.pi/agent/auth.json"
    model_arg="gpt-5.5-high"
  fi

  MOCK_EXIT_CODE="$mock_exit_code" \
  PATH="$MOCK_BIN_DIR:$PATH" \
  _NA_CCR_UP_BIN="$MOCK_BIN_DIR/ccr-up-mock.sh" \
  _CODEX_ENGINE_BIN="$MOCK_BIN_DIR/mock-engine" \
  _PI_BIN="$MOCK_BIN_DIR/pi" \
  _NA_HEALTH_URL="file://$MOCK_HTTP_DIR/na-health.json" \
  _NA_MODELS_URL="file://$MOCK_HTTP_DIR/na-models.json" \
  MOCK_CURSOR_LOG="$tmp_workspace/cursor.log" \
  MOCK_CLAUDE_LOG="$tmp_workspace/claude.log" \
  MOCK_CODEX_LOG="$tmp_workspace/codex.log" \
  MOCK_PI_LOG="$tmp_workspace/pi.log" \
  MOCK_GROK_LOG="$tmp_workspace/grok.log" \
  _GROK_ENGINE_BIN="$MOCK_BIN_DIR/grok" \
  contract_wrapper "$wrapper_path" \
    --workspace "$tmp_workspace" \
    --trust "mock" \
    --task-slug "test" \
    --model "$model_arg" \
    --timeout 30 \
    "$@"
  local rc=$?
  rm -rf "$tmp_workspace"
  return "$rc"
}

# Agent returns exit 0 but produces empty captured output (silent write failure / disk full).
# Contract requires success be backed by evidence -> wrapper must map this to exit 3.
assert_empty_output_contract() {
  local wrapper_path="$1"

  export MOCK_FORCE_EMPTY_OUTPUT=1
  local actual_rc=0
  run_wrapper "$wrapper_path" 0 >/dev/null 2>&1 || actual_rc=$?
  unset MOCK_FORCE_EMPTY_OUTPUT

  if [[ $actual_rc -eq 3 ]]; then
    echo "  ✓ agent exit 0 with empty output -> exit 3"
    return 0
  fi
  echo "  ✗ agent exit 0 with empty output: got $actual_rc, expected 3"
  return 1
}

test_wrapper() {
  local wrapper_path="$1"
  local wrapper_name
  wrapper_name="$(basename "$wrapper_path")"
  local wrapper_dir
  wrapper_dir="$(cd "$(dirname "$wrapper_path")" && pwd)"
  local failed=0

  echo "=== Testing wrapper: $wrapper_name ==="
  local tmp_skill_dir
  tmp_skill_dir="$(setup_env_files)"
  cp "$wrapper_path" "$tmp_skill_dir/wrappers/$(basename "$wrapper_path")" && chmod +x "$tmp_skill_dir/wrappers/$(basename "$wrapper_path")"
  cp -R "$wrapper_dir/lib" "$tmp_skill_dir/wrappers/"
  local tmp_wrapper_path="$tmp_skill_dir/wrappers/$(basename "$wrapper_path")"
  export HOME="$tmp_skill_dir"

  local mock_rc expected_rc label
  for mock_rc in 0 124 2 3; do
    expected_rc="$mock_rc"
    label="MOCK_EXIT_CODE=$mock_rc -> expect exit $expected_rc"
    echo "  $label"
    if run_wrapper "$tmp_wrapper_path" "$mock_rc"; then
      actual_rc=0
    else
      actual_rc=$?
    fi
    if [[ $actual_rc -ne $expected_rc ]]; then
      echo "  ✗ $label: got $actual_rc"
      failed=1
    else
      echo "  ✓ $label"
    fi
  done

  echo "  missing --workspace -> expect exit 2"
  if MOCK_EXIT_CODE=0 PATH="$MOCK_BIN_DIR:$PATH" \
     _NA_CCR_UP_BIN="$MOCK_BIN_DIR/ccr-up-mock.sh" \
     _CODEX_ENGINE_BIN="$MOCK_BIN_DIR/mock-engine" \
     contract_wrapper "$tmp_wrapper_path" --trust "mock" --task-slug "test" --model "mock-model" --timeout 30 2>/dev/null; then
    echo "  ✗ missing --workspace: got 0, expected 2"
    failed=1
  else
    local usage_rc=$?
    if [[ $usage_rc -eq 2 ]]; then
      echo "  ✓ missing --workspace -> exit 2"
    else
      echo "  ✗ missing --workspace: got $usage_rc, expected 2"
      failed=1
    fi
  fi

  # Test that timeout-killed (SIGKILL=137) maps to contract exit 124
  for kill_rc in 137; do
    label="MOCK_EXIT_CODE=$kill_rc -> expect exit 124 (timeout-killed mapping)"
    echo "  $label"
    if run_wrapper "$tmp_wrapper_path" "$kill_rc"; then
      actual_rc=0
    else
      actual_rc=$?
    fi
    if [[ $actual_rc -ne 124 ]]; then
      echo "  ✗ $label: got $actual_rc"
      failed=1
    else
      echo "  ✓ $label"
    fi
  done

  # Test that unexpected engine exit codes map to contract exit 3
  for raw_rc in 1 5; do
    label="MOCK_EXIT_CODE=$raw_rc -> expect exit 3 (contract mapping)"
    echo "  $label"
    if run_wrapper "$tmp_wrapper_path" "$raw_rc"; then
      actual_rc=0
    else
      actual_rc=$?
    fi
    if [[ $actual_rc -ne 3 ]]; then
      echo "  ✗ $label: got $actual_rc"
      failed=1
    else
      echo "  ✓ $label"
    fi
  done

  if ! assert_completion_metadata "$tmp_wrapper_path" "$wrapper_name"; then
    failed=1
  fi

  if ! assert_probe_modes "$tmp_wrapper_path" "$wrapper_name"; then
    failed=1
  fi

  if ! assert_usage_and_transcript "$tmp_wrapper_path" "$wrapper_name"; then
    failed=1
  fi

  if ! assert_pi_contract "$tmp_wrapper_path" "$wrapper_name"; then
    failed=1
  fi

  if ! assert_spend_cap_contract "$tmp_wrapper_path" "$wrapper_name"; then
    failed=1
  fi

  if ! assert_empty_output_contract "$tmp_wrapper_path"; then
    failed=1
  fi

  rm -rf "$tmp_skill_dir"
  if [[ $failed -eq 0 ]]; then
    echo "=== All tests passed for $wrapper_name ==="
    return 0
  else
    echo "=== FAILED: $wrapper_name ==="
    return 1
  fi
}

assert_completion_metadata() {
  local wrapper_path="$1"
  local wrapper_name="$2"
  local tmp_workspace out rc
  tmp_workspace="$(mktemp -d)"

  case "$wrapper_name" in
    na.sh)
      if out="$(
        MOCK_EXIT_CODE=0 \
        PATH="$MOCK_BIN_DIR:$PATH" \
        _NA_CCR_UP_BIN="$MOCK_BIN_DIR/ccr-up-mock.sh" \
        _NA_HEALTH_URL="file://$MOCK_HTTP_DIR/na-health.json" \
        _NA_MODELS_URL="file://$MOCK_HTTP_DIR/na-models.json" \
        MOCK_CLAUDE_LOG="$tmp_workspace/claude.log" \
        MOCK_CLAUDE_OUTPUT='{"type":"result","subtype":"success"}' \
        HOME="$HOME" \
        contract_wrapper "$wrapper_path" --workspace "$tmp_workspace" --trust "mock" --task-slug "test" --model "mock-model" --timeout 30 --session-id "session-na-123" 2>&1
      )"; then
        rc=0
      else
        rc=$?
      fi
      if [[ $rc -eq 0 && "$out" == *"session_id: session-na-123"* ]]; then
        say_ok "completion surfaces session id"
        rm -rf "$tmp_workspace"
        return 0
      fi
      say_fail "completion surfaces session id: rc=$rc out=$out"
      rm -rf "$tmp_workspace"
      return 1
      ;;
    ca.sh)
      if out="$(
        MOCK_EXIT_CODE=0 \
        PATH="$MOCK_BIN_DIR:$PATH" \
        _NA_CCR_UP_BIN="$MOCK_BIN_DIR/ccr-up-mock.sh" \
        MOCK_CURSOR_LOG="$tmp_workspace/cursor.log" \
        MOCK_CHAT_ID="chat-ca-123" \
        MOCK_RUN_OUTPUT='{"type":"result","detail":"done"}' \
        HOME="$HOME" \
        contract_wrapper "$wrapper_path" --workspace "$tmp_workspace" --trust "mock" --task-slug "test" --model "mock-model" --timeout 30 2>&1
      )"; then
        rc=0
      else
        rc=$?
      fi
      if [[ $rc -eq 0 && "$out" == *'"session_id":"chat-ca-123"'* ]]; then
        say_ok "completion surfaces session id"
        rm -rf "$tmp_workspace"
        return 0
      fi
      say_fail "completion surfaces session id: rc=$rc out=$out"
      rm -rf "$tmp_workspace"
      return 1
      ;;
    codex.sh)
      if out="$(
        MOCK_EXIT_CODE=0 \
        PATH="$MOCK_BIN_DIR:$PATH" \
        _CODEX_ENGINE_BIN="$MOCK_BIN_DIR/mock-engine" \
        MOCK_CODEX_LOG="$tmp_workspace/codex.log" \
        MOCK_CODEX_RUN_OUTPUT='{"type":"thread.started","thread_id":"thread-codex-123"}' \
        MOCK_CODEX_RUN_OUTPUT_2='{"type":"item.completed","item":{"content":[{"type":"output_text","text":"{\"clean\":true,\"findings\":[]}"}]}}' \
        HOME="$HOME" \
        contract_wrapper "$wrapper_path" --workspace "$tmp_workspace" --trust "mock" --task-slug "test" --model "mock-model" --thread-id "thread-codex-123" --timeout 30 2>&1
      )"; then
        rc=0
      else
        rc=$?
      fi
      if [[ $rc -eq 0 && "$out" == *'"thread_id":"thread-codex-123"'* ]]; then
        say_ok "completion surfaces session id"
        rm -rf "$tmp_workspace"
        return 0
      fi
      say_fail "completion surfaces session id: rc=$rc out=$out"
      rm -rf "$tmp_workspace"
      return 1
      ;;
    pi.sh)
      if out="$(
        MOCK_EXIT_CODE=0 \
        PATH="$MOCK_BIN_DIR:$PATH" \
        _PI_BIN="$MOCK_BIN_DIR/pi" \
        MOCK_PI_LOG="$tmp_workspace/pi.log" \
        MOCK_PI_RUN_OUTPUT='{"event":"session.started","session_id":"pi-session-123"}' \
        MOCK_PI_RUN_OUTPUT_2='{"event":"agent.finished","result":{"clean":true,"findings":[]},"usage":{"input_tokens":11,"output_tokens":7}}' \
        PI_AUTH_HOME="$HOME/.pi" \
        HOME="$HOME" \
        contract_wrapper "$wrapper_path" --workspace "$tmp_workspace" --trust "mock" --task-slug "test" --model "gpt-5.5-high" --session-id "pi-session-123" --timeout 30 2>&1
      )"; then
        rc=0
      else
        rc=$?
      fi
      if [[ $rc -eq 0 && "$out" == *'"session_id":"pi-session-123"'* ]]; then
        say_ok "completion surfaces session id"
        rm -rf "$tmp_workspace"
        return 0
      fi
      say_fail "completion surfaces session id: rc=$rc out=$out"
      rm -rf "$tmp_workspace"
      return 1
      ;;
    *)
      say_ok "completion session metadata not asserted for $wrapper_name"
      rm -rf "$tmp_workspace"
      return 0
      ;;
  esac
}

assert_probe_modes() {
  local wrapper_path="$1"
  local wrapper_name="$2"
  local out rc tmp_workspace
  tmp_workspace="$(mktemp -d)"

  case "$wrapper_name" in
    na.sh)
      if out="$(
        PATH="$MOCK_BIN_DIR:$PATH" \
        _NA_CCR_UP_BIN="$MOCK_BIN_DIR/ccr-up-mock.sh" \
        _NA_HEALTH_URL="file://$MOCK_HTTP_DIR/na-health.json" \
        _NA_MODELS_URL="file://$MOCK_HTTP_DIR/na-models.json" \
        HOME="$HOME" \
        "$wrapper_path" --health 2>&1
      )"; then rc=0; else rc=$?; fi
      if [[ $rc -eq 0 && "$out" == *'"service": "ccr"'* ]]; then
        say_ok "--health mode"
      else
        say_fail "--health mode: rc=$rc out=$out"
        rm -rf "$tmp_workspace"
        return 1
      fi
      if out="$(
        PATH="$MOCK_BIN_DIR:$PATH" \
        _NA_CCR_UP_BIN="$MOCK_BIN_DIR/ccr-up-mock.sh" \
        _NA_HEALTH_URL="file://$MOCK_HTTP_DIR/na-health.json" \
        _NA_MODELS_URL="file://$MOCK_HTTP_DIR/na-models.json" \
        HOME="$HOME" \
        "$wrapper_path" --list-models 2>&1
      )"; then rc=0; else rc=$?; fi
      if [[ $rc -eq 0 && "$out" == *'cohere/north-mini-code:free'* ]]; then
        say_ok "--list-models mode"
      else
        say_fail "--list-models mode: rc=$rc out=$out"
        rm -rf "$tmp_workspace"
        return 1
      fi
      ;;
    ca.sh)
      if out="$(
        PATH="$MOCK_BIN_DIR:$PATH" \
        MOCK_CURSOR_LOG="$tmp_workspace/cursor.log" \
        MOCK_STATUS_JSON='{"authenticated":true}' \
        HOME="$HOME" \
        "$wrapper_path" --health 2>&1
      )"; then rc=0; else rc=$?; fi
      if [[ $rc -eq 0 && "$out" == *'"authenticated":true'* ]]; then
        say_ok "--health mode"
      else
        say_fail "--health mode: rc=$rc out=$out"
        rm -rf "$tmp_workspace"
        return 1
      fi
      if out="$(
        PATH="$MOCK_BIN_DIR:$PATH" \
        MOCK_CURSOR_LOG="$tmp_workspace/cursor.log" \
        MOCK_MODELS_OUTPUT=$'model-a\nmodel-b' \
        HOME="$HOME" \
        "$wrapper_path" --list-models 2>&1
      )"; then rc=0; else rc=$?; fi
      if [[ $rc -eq 0 && "$out" == $'model-a\nmodel-b' ]]; then
        say_ok "--list-models mode"
      else
        say_fail "--list-models mode: rc=$rc out=$out"
        rm -rf "$tmp_workspace"
        return 1
      fi
      ;;
    codex.sh)
      if out="$(
        PATH="$MOCK_BIN_DIR:$PATH" \
        _CODEX_ENGINE_BIN="$MOCK_BIN_DIR/mock-engine" \
        MOCK_CODEX_LOG="$tmp_workspace/codex.log" \
        MOCK_CODEX_VERSION_OUTPUT='codex 1.2.3' \
        HOME="$HOME" \
        "$wrapper_path" --health 2>&1
      )"; then rc=0; else rc=$?; fi
      if [[ $rc -eq 0 && "$out" == *'"version":"codex 1.2.3"'* ]]; then
        say_ok "--health mode"
      else
        say_fail "--health mode: rc=$rc out=$out"
        rm -rf "$tmp_workspace"
        return 1
      fi
      if out="$(
        PATH="$MOCK_BIN_DIR:$PATH" \
        _CODEX_ENGINE_BIN="$MOCK_BIN_DIR/mock-engine" \
        MOCK_CODEX_LOG="$tmp_workspace/codex.log" \
        MOCK_CODEX_MODELS_OUTPUT='{"data":[{"id":"gpt-5.5"},{"id":"gpt-5.5-mini"}]}' \
        HOME="$HOME" \
        "$wrapper_path" --list-models 2>&1
      )"; then rc=0; else rc=$?; fi
      if [[ $rc -eq 0 && "$out" == *'"id":"gpt-5.5-mini"'* ]]; then
        say_ok "--list-models mode"
      else
        say_fail "--list-models mode: rc=$rc out=$out"
        rm -rf "$tmp_workspace"
        return 1
      fi
      ;;
    pi.sh)
      if out="$(
        PATH="$MOCK_BIN_DIR:$PATH" \
        _PI_BIN="$MOCK_BIN_DIR/pi" \
        MOCK_PI_LOG="$tmp_workspace/pi.log" \
        MOCK_PI_VERSION_OUTPUT='pi 1.2.3' \
        PI_AUTH_HOME="$HOME/.pi" \
        HOME="$HOME" \
        "$wrapper_path" --health 2>&1
      )"; then rc=0; else rc=$?; fi
      if [[ $rc -eq 0 && "$out" == *'"version":"pi 1.2.3"'* ]]; then
        say_ok "--health mode"
      else
        say_fail "--health mode: rc=$rc out=$out"
        rm -rf "$tmp_workspace"
        return 1
      fi
      if out="$(
        PATH="$MOCK_BIN_DIR:$PATH" \
        _PI_BIN="$MOCK_BIN_DIR/pi" \
        MOCK_PI_LOG="$tmp_workspace/pi.log" \
        MOCK_PI_MODELS_OUTPUT='{"models":[{"id":"gpt-5.5"},{"id":"gpt-5.4-mini"}]}' \
        PI_AUTH_HOME="$HOME/.pi" \
        HOME="$HOME" \
        "$wrapper_path" --list-models 2>&1
      )"; then rc=0; else rc=$?; fi
      if [[ $rc -eq 0 && "$out" == *'"id":"gpt-5.4-mini"'* ]]; then
        say_ok "--list-models mode"
      else
        say_fail "--list-models mode: rc=$rc out=$out"
        rm -rf "$tmp_workspace"
        return 1
      fi
      ;;
  esac

  rm -rf "$tmp_workspace"
  return 0
}

assert_usage_and_transcript() {
  local wrapper_path="$1"
  local wrapper_name="$2"
  local tmp_workspace transcript out rc model_arg="mock-model"
  tmp_workspace="$(mktemp -d)"
  transcript="$tmp_workspace/transcript.jsonl"

  if [[ "$wrapper_name" == "pi.sh" ]]; then
    mkdir -p "$HOME/.pi/agent"
    printf '%s\n' '{"openai-codex":{"type":"oauth","access":"a","refresh":"r","expires":9999999999999,"accountId":"acct"}}' > "$HOME/.pi/agent/auth.json"
    model_arg="gpt-5.5-high"
  elif [[ "$wrapper_name" == "grok.sh" ]]; then
    model_arg="grok-4.5"
  fi

  if out="$(
    MOCK_EXIT_CODE=0 \
    PATH="$MOCK_BIN_DIR:$PATH" \
    _NA_CCR_UP_BIN="$MOCK_BIN_DIR/ccr-up-mock.sh" \
    _CODEX_ENGINE_BIN="$MOCK_BIN_DIR/mock-engine" \
    _GROK_ENGINE_BIN="$MOCK_BIN_DIR/grok" \
    _PI_BIN="$MOCK_BIN_DIR/pi" \
    _NA_HEALTH_URL="file://$MOCK_HTTP_DIR/na-health.json" \
    _NA_MODELS_URL="file://$MOCK_HTTP_DIR/na-models.json" \
    MOCK_CURSOR_LOG="$tmp_workspace/cursor.log" \
    MOCK_CLAUDE_LOG="$tmp_workspace/claude.log" \
    MOCK_CODEX_LOG="$tmp_workspace/codex.log" \
    MOCK_GROK_LOG="$tmp_workspace/grok.log" \
    MOCK_PI_LOG="$tmp_workspace/pi.log" \
    MOCK_CODEX_RUN_OUTPUT='{"type":"thread.started","thread_id":"thread-mock-123"}' \
    MOCK_CODEX_RUN_OUTPUT_2='{"type":"response.completed","response":{"usage":{"input_tokens":11,"output_tokens":7,"cached_input_tokens":2}}}' \
    HARNESS_TRANSCRIPT_PATH="$transcript" \
    PI_AUTH_HOME="$HOME/.pi" \
    HOME="$HOME" \
    contract_wrapper "$wrapper_path" --workspace "$tmp_workspace" --trust "mock" --task-slug "test" --model "$model_arg" --timeout 30 2>&1
  )"; then
    rc=0
  else
    rc=$?
  fi

  if [[ $rc -eq 0 && "$out" == *'"kind":"usage"'* && -f "$transcript" ]] && grep -Fq '"kind":"usage"' "$transcript"; then
    say_ok "success path emits usage and tees transcript"
  else
    say_fail "success path emits usage and tees transcript: rc=$rc out=$out transcript=$(cat "$transcript" 2>/dev/null)"
    rm -rf "$tmp_workspace"
    return 1
  fi

  : > "$transcript"
  if out="$(
    MOCK_EXIT_CODE=3 \
    PATH="$MOCK_BIN_DIR:$PATH" \
    _NA_CCR_UP_BIN="$MOCK_BIN_DIR/ccr-up-mock.sh" \
    _CODEX_ENGINE_BIN="$MOCK_BIN_DIR/mock-engine" \
    _GROK_ENGINE_BIN="$MOCK_BIN_DIR/grok" \
    _PI_BIN="$MOCK_BIN_DIR/pi" \
    _NA_HEALTH_URL="file://$MOCK_HTTP_DIR/na-health.json" \
    _NA_MODELS_URL="file://$MOCK_HTTP_DIR/na-models.json" \
    MOCK_CURSOR_LOG="$tmp_workspace/cursor.log" \
    MOCK_CLAUDE_LOG="$tmp_workspace/claude.log" \
    MOCK_CODEX_LOG="$tmp_workspace/codex.log" \
    MOCK_GROK_LOG="$tmp_workspace/grok.log" \
    MOCK_PI_LOG="$tmp_workspace/pi.log" \
    HARNESS_TRANSCRIPT_PATH="$transcript" \
    PI_AUTH_HOME="$HOME/.pi" \
    HOME="$HOME" \
    contract_wrapper "$wrapper_path" --workspace "$tmp_workspace" --trust "mock" --task-slug "test" --model "$model_arg" --timeout 30 2>&1
  )"; then
    rc=0
  else
    rc=$?
  fi

  if [[ $rc -ne 0 && "$out" == *'"kind":"usage"'* && -f "$transcript" ]] && grep -Fq '"kind":"usage"' "$transcript"; then
    say_ok "failure path emits usage and tees transcript"
    rm -rf "$tmp_workspace"
    return 0
  fi

  say_fail "failure path emits usage and tees transcript: rc=$rc out=$out transcript=$(cat "$transcript" 2>/dev/null)"
  rm -rf "$tmp_workspace"
  return 1
}

assert_pi_contract() {
  local wrapper_path="$1"
  local wrapper_name="$2"
  local tmp_workspace out rc

  [[ "$wrapper_name" == "pi.sh" ]] || return 0
  tmp_workspace="$(mktemp -d)"

  rm -rf "$HOME/.pi"
  if out="$(
    PATH="$MOCK_BIN_DIR:$PATH" \
    _PI_BIN="$MOCK_BIN_DIR/pi" \
    MOCK_PI_LOG="$tmp_workspace/pi.log" \
    PI_AUTH_HOME="$HOME/.pi" \
    HOME="$HOME" \
    contract_wrapper "$wrapper_path" --workspace "$tmp_workspace" --trust "mock" --task-slug "test" --model "gpt-5.5-high" --timeout 30 2>&1
  )"; then
    rc=0
  else
    rc=$?
  fi
  if [[ $rc -eq 3 ]]; then
    say_ok "auth precondition exits 3 without dispatch"
  else
    say_fail "auth precondition exits 3 without dispatch: rc=$rc out=$out"
    rm -rf "$tmp_workspace"
    return 1
  fi

  mkdir -p "$HOME/.pi/agent"
  printf '%s\n' '{"openai-codex":{"type":"oauth","access":"a","refresh":"r","expires":9999999999999,"accountId":"acct"}}' > "$HOME/.pi/agent/auth.json"

  if out="$(
    MOCK_EXIT_CODE=3 \
    PATH="$MOCK_BIN_DIR:$PATH" \
    _PI_BIN="$MOCK_BIN_DIR/pi" \
    MOCK_PI_LOG="$tmp_workspace/pi.log" \
    MOCK_PI_RUN_OUTPUT='rate limit exceeded, try again at 11:45 PM' \
    MOCK_PI_RUN_OUTPUT_2='provider openai-codex quota exhausted' \
    PI_AUTH_HOME="$HOME/.pi" \
    HOME="$HOME" \
    contract_wrapper "$wrapper_path" --workspace "$tmp_workspace" --trust "mock" --task-slug "test" --model "gpt-5.5-high" --timeout 30 2>&1
  )"; then
    rc=0
  else
    rc=$?
  fi
  if [[ $rc -eq 75 && "$out" == *'"resume_at":"'*
        && "$out" == *'"errorKind":"quota"'* ]]; then
    say_ok "quota maps to 75 with resume_at"
  else
    say_fail "quota maps to 75 with resume_at: rc=$rc out=$out"
    rm -rf "$tmp_workspace"
    return 1
  fi

  : > "$tmp_workspace/pi.log"
  if out="$(
    MOCK_EXIT_CODE=0 \
    PATH="$MOCK_BIN_DIR:$PATH" \
    _PI_BIN="$MOCK_BIN_DIR/pi" \
    MOCK_PI_LOG="$tmp_workspace/pi.log" \
    MOCK_PI_RUN_OUTPUT='{"event":"session.started","session_id":"pi-session-123"}' \
    MOCK_PI_RUN_OUTPUT_2='{"event":"agent.finished","result":{"clean":true,"findings":[]},"usage":{"input_tokens":11,"output_tokens":7}}' \
    PI_AUTH_HOME="$HOME/.pi" \
    HOME="$HOME" \
    contract_wrapper "$wrapper_path" --workspace "$tmp_workspace" --trust "mock" --task-slug "test" --model "gpt-5.5-high" --timeout 30 2>&1
  )"; then
    rc=0
  else
    rc=$?
  fi
  if [[ $rc -eq 0 ]] && grep -Fq -- '--provider openai-codex' "$tmp_workspace/pi.log" \
    && grep -Fq -- '--model gpt-5.5' "$tmp_workspace/pi.log" \
    && grep -Fq -- '--reasoning-effort high' "$tmp_workspace/pi.log"; then
    say_ok "dispatch pins provider, model, and effort"
    rm -rf "$tmp_workspace"
    return 0
  fi

  say_fail "dispatch pins provider, model, and effort: rc=$rc out=$out log=$(cat "$tmp_workspace/pi.log" 2>/dev/null)"
  rm -rf "$tmp_workspace"
  return 1
}

spend_cap_fixture_backup=""

spend_cap_fixture_dir() {
  printf '%s\n' "$HOME/.local/state/overdeck/systray/runtime"
}

spend_cap_fixture_begin() {
  local dir
  dir="$(spend_cap_fixture_dir)"
  spend_cap_fixture_backup=""
  if [[ -d "$dir" ]]; then
    spend_cap_fixture_backup="$(mktemp -d)"
    cp -a "$dir" "$spend_cap_fixture_backup/runtime"
  fi
}

spend_cap_fixture_apply() {
  local account="$1"
  local secondary_used_pct="$2"
  local dir
  dir="$(spend_cap_fixture_dir)"
  mkdir -p "$dir"
  printf '%s\n' "{\"$account\":{\"status\":\"ok\",\"primary_used_pct\":null,\"secondary_used_pct\":$secondary_used_pct,\"secondary_reset_at\":1786365653.0}}" > "$dir/health_cache.json"
  printf '%s\n' '{"version":"routing/v2","projects":{},"default":"'"$account"'","fallback_chain":[],"account_caps":{"'"$account"'":{"7d":10}}}' > "$dir/routing_rules.json"
  printf '%s\n' '{"version":"routing/v2","projects":{},"default":"avi","fallback_chain":[],"account_caps":{}}' > "$dir/claude_routing_rules.json"
}

spend_cap_fixture_end() {
  local dir
  dir="$(spend_cap_fixture_dir)"
  if [[ -n "$spend_cap_fixture_backup" && -d "$spend_cap_fixture_backup/runtime" ]]; then
    rm -rf "$dir"
    cp -a "$spend_cap_fixture_backup/runtime" "$dir"
    rm -rf "$spend_cap_fixture_backup"
  else
    rm -rf "$dir"
  fi
  spend_cap_fixture_backup=""
}

assert_spend_cap_contract() {
  local wrapper_path="$1"
  local wrapper_name="$2"
  local tmp_workspace out rc account="" extra_args=() model_arg="mock-model" provider_log="" real_home=""
  tmp_workspace="$(mktemp -d)"

  real_home="$(getent passwd "$(id -un)" 2>/dev/null | cut -d: -f6 || true)"
  [[ -z "$real_home" ]] && real_home="$(cd ~ && pwd -P)"
  if [[ "$HOME" == "$real_home" ]]; then
    say_fail "refusing spend-cap fixture: HOME is real user home ($HOME)"
    rm -rf "$tmp_workspace"
    return 1
  fi

  case "$wrapper_name" in
    ca.sh) account="roy-grok"; provider_log="$tmp_workspace/cursor.log" ;;
    codex.sh) account="zync2"; extra_args=(--profile zync2); provider_log="$tmp_workspace/codex.log" ;;
    na.sh) account="north"; provider_log="$tmp_workspace/claude.log" ;;
    grok.sh) account="roy-grok"; model_arg="grok-4.5"; provider_log="$tmp_workspace/grok.log" ;;
    pi.sh)
      account="zync2"
      model_arg="gpt-5.5-high"
      provider_log="$tmp_workspace/pi.log"
      mkdir -p "$HOME/.pi/agent"
      printf '%s\n' '{"openai-codex":{"type":"oauth","access":"a","refresh":"r","expires":9999999999999,"accountId":"acct"}}' > "$HOME/.pi/agent/auth.json"
      ;;
    *) rm -rf "$tmp_workspace"; return 0 ;;
  esac

  spend_cap_fixture_begin
  spend_cap_fixture_apply "$account" 90

  : > "$provider_log"
  if out="$(
    MOCK_EXIT_CODE=0 \
    PATH="$MOCK_BIN_DIR:$PATH" \
    _NA_CCR_UP_BIN="$MOCK_BIN_DIR/ccr-up-mock.sh" \
    _CODEX_ENGINE_BIN="$MOCK_BIN_DIR/mock-engine" \
    _GROK_ENGINE_BIN="$MOCK_BIN_DIR/grok" \
    _PI_BIN="$MOCK_BIN_DIR/pi" \
    _NA_HEALTH_URL="file://$MOCK_HTTP_DIR/na-health.json" \
    _NA_MODELS_URL="file://$MOCK_HTTP_DIR/na-models.json" \
    MOCK_CURSOR_LOG="$tmp_workspace/cursor.log" \
    MOCK_CLAUDE_LOG="$tmp_workspace/claude.log" \
    MOCK_CODEX_LOG="$tmp_workspace/codex.log" \
    MOCK_GROK_LOG="$tmp_workspace/grok.log" \
    MOCK_PI_LOG="$tmp_workspace/pi.log" \
    PI_AUTH_HOME="$HOME/.pi" \
    HOME="$HOME" \
    contract_wrapper "$wrapper_path" --workspace "$tmp_workspace" --trust "mock" --task-slug "test" --model "$model_arg" --timeout 30 "${extra_args[@]}" 2>&1
  )"; then
    rc=0
  else
    rc=$?
  fi

  if [[ $rc -eq 75 && "$out" == *'"resume_at":"'* && ! -s "$provider_log" ]]; then
    say_ok "capped account exits 75 with resume_at and spawns no provider"
  else
    say_fail "capped account exits 75 with resume_at and spawns no provider: rc=$rc out=$out log=$(cat "$provider_log" 2>/dev/null)"
    spend_cap_fixture_end
    rm -rf "$tmp_workspace"
    return 1
  fi

  spend_cap_fixture_end
  spend_cap_fixture_begin
  spend_cap_fixture_apply "$account" 88
  : > "$provider_log"
  if out="$(
    MOCK_EXIT_CODE=0 \
    PATH="$MOCK_BIN_DIR:$PATH" \
    _NA_CCR_UP_BIN="$MOCK_BIN_DIR/ccr-up-mock.sh" \
    _CODEX_ENGINE_BIN="$MOCK_BIN_DIR/mock-engine" \
    _GROK_ENGINE_BIN="$MOCK_BIN_DIR/grok" \
    _PI_BIN="$MOCK_BIN_DIR/pi" \
    _NA_HEALTH_URL="file://$MOCK_HTTP_DIR/na-health.json" \
    _NA_MODELS_URL="file://$MOCK_HTTP_DIR/na-models.json" \
    MOCK_CURSOR_LOG="$tmp_workspace/cursor.log" \
    MOCK_CLAUDE_LOG="$tmp_workspace/claude.log" \
    MOCK_CODEX_LOG="$tmp_workspace/codex.log" \
    MOCK_GROK_LOG="$tmp_workspace/grok.log" \
    MOCK_PI_LOG="$tmp_workspace/pi.log" \
    MOCK_RUN_OUTPUT='{"type":"result","detail":"done"}' \
    MOCK_CLAUDE_OUTPUT='{"type":"result","subtype":"success"}' \
    MOCK_CODEX_RUN_OUTPUT='{"type":"thread.started","thread_id":"thread-mock-123"}' \
    MOCK_CODEX_RUN_OUTPUT_2='{"type":"item.completed","item":{"content":[{"type":"output_text","text":"{\"clean\":true,\"findings\":[]}"}]}}' \
    MOCK_GROK_RUN_OUTPUT='{"sessionId":"grok-session-mock"}' \
    MOCK_PI_RUN_OUTPUT='{"event":"session.started","session_id":"pi-session-123"}' \
    MOCK_PI_RUN_OUTPUT_2='{"event":"agent.finished","result":{"status":"ok"},"usage":{"input_tokens":11,"output_tokens":7}}' \
    PI_AUTH_HOME="$HOME/.pi" \
    HOME="$HOME" \
    contract_wrapper "$wrapper_path" --workspace "$tmp_workspace" --trust "mock" --task-slug "test" --model "$model_arg" --timeout 30 "${extra_args[@]}" 2>&1
  )"; then
    rc=0
  else
    rc=$?
  fi

  if [[ $rc -eq 0 && -s "$provider_log" ]]; then
    say_ok "uncapped account dispatches provider"
  else
    say_fail "uncapped account dispatches provider: rc=$rc out=$out log=$(cat "$provider_log" 2>/dev/null)"
    spend_cap_fixture_end
    rm -rf "$tmp_workspace"
    return 1
  fi

  spend_cap_fixture_end

  if [[ "$wrapper_name" == codex.sh ]]; then
    spend_cap_fixture_begin
    spend_cap_fixture_apply "$account" 90
    : > "$provider_log"
    if out="$(
      MOCK_EXIT_CODE=0 \
      PATH="$MOCK_BIN_DIR:$PATH" \
      _CODEX_ENGINE_BIN="$MOCK_BIN_DIR/mock-engine" \
      MOCK_CODEX_LOG="$tmp_workspace/codex.log" \
      HOME="$HOME" \
      contract_wrapper "$wrapper_path" --workspace "$tmp_workspace" --trust "mock" --task-slug "test" --model "$model_arg" --timeout 30 2>&1
    )"; then
      rc=0
    else
      rc=$?
    fi
    if [[ $rc -eq 75 && "$out" == *'"resume_at":"'* && ! -s "$provider_log" ]]; then
      say_ok "unprofiled codex gates on routing default account"
    else
      say_fail "unprofiled codex gates on routing default account: rc=$rc out=$out log=$(cat "$provider_log" 2>/dev/null)"
      spend_cap_fixture_end
      rm -rf "$tmp_workspace"
      return 1
    fi
    spend_cap_fixture_end
  fi

  rm -rf "$tmp_workspace"
  return 0
}

main() {
  if [[ $# -eq 0 ]]; then
    echo "Usage: $0 <wrapper> [wrapper2 ...]"
    echo "Example: $0 wrappers/na.sh wrappers/ca.sh wrappers/codex.sh"
    exit 1
  fi

  setup_mock_bin
  trap cleanup_mocks EXIT

  local probe_dir
  probe_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  if ! bash "$probe_dir/lib/spend-cap.sh" --self-test; then
    echo "✗ spend-cap.sh self-test failed"
    exit 1
  fi
  echo "✓ spend-cap.sh self-test passed"

  local failed_wrappers=()
  for wrapper_path in "$@"; do
    if [[ ! -f "$wrapper_path" ]]; then
      echo "✗ Wrapper not found: $wrapper_path"
      failed_wrappers+=("$wrapper_path")
      continue
    fi
    if test_wrapper "$wrapper_path"; then
      echo "✓ $wrapper_path passed all contract tests"
    else
      echo "✗ $wrapper_path FAILED contract tests"
      failed_wrappers+=("$wrapper_path")
    fi
  done

  if [[ ${#failed_wrappers[@]} -gt 0 ]]; then
    echo ""
    echo "=== FAILED WRAPPERS ==="
    for failed in "${failed_wrappers[@]}"; do
      echo "- $failed"
    done
    exit 1
  else
    echo ""
    echo "=== ALL WRAPPERS PASSED CONTRACT TESTS ==="
    exit 0
  fi
}

if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
  main "$@"
fi
