#!/usr/bin/env bash
# claude-multiacc sandboxed test suite — covers BOTH providers (claude + codex).
# No network, no real accounts, no quota: fake `claude`/`codex` binaries + file://
# usage/token fixtures.
set -u

REPO_DIR="$(cd "$(dirname "$0")/.." && pwd -P)"
WORK="$(mktemp -d "${TMPDIR:-/tmp}/multiacc-test.XXXXXX")"
# Loopback stub servers register their pid here. Deleting $WORK does not kill a running
# python process, so an interrupted run (CI cancel, Ctrl-C) would otherwise leave one
# listening until the machine goes away.
STUB_PIDS=""
cleanup() {
  local p
  for p in $STUB_PIDS; do
    kill "$p" 2>/dev/null
    wait "$p" 2>/dev/null || true
  done
  rm -rf "$WORK"
}
trap cleanup EXIT

PASS=0
FAIL=0
t_ok() { PASS=$((PASS+1)); printf 'ok   %s\n' "$1"; }
t_fail() { FAIL=$((FAIL+1)); printf 'FAIL %s%s\n' "$1" "${2:+ — $2}"; }
check() { # check <name> <expected-substring> <actual>
  case "$3" in
    *"$2"*) t_ok "$1" ;;
    *) t_fail "$1" "expected substring '$2', got: $(printf '%s' "$3" | head -c 200)" ;;
  esac
}

# ---- sandbox layout -------------------------------------------------------
export CLAUDE_ACCOUNTS_DIR="$WORK/accounts"
ACC="$CLAUDE_ACCOUNTS_DIR"
FAKEBIN="$WORK/fakebin"
mkdir -p "$ACC/tmp" "$FAKEBIN"

# Fake "real" claude: prints which config dir/token it ran under; scriptable failures.
cat > "$FAKEBIN/claude" <<'EOF'
#!/usr/bin/env bash
# fake real claude for tests (not a multiacc shim)
if [ "${1:-}" = "auth" ] && [ "${2:-}" = "status" ]; then
  if [ -n "${FAKE_AUTH_FAIL:-}" ]; then echo '{"loggedIn": false}'; exit 0; fi
  # A setup token is minted with scope user:inference ALONE, so the real CLI answers
  # {loggedIn, authMethod, apiProvider} and NO email — only an OAuth login (config dir)
  # reports one. The fake used to hand an email to both, which is precisely why every
  # --token identity path passed here and died in the field.
  if [ -n "${CLAUDE_CODE_OAUTH_TOKEN:-}" ] && [ -z "${CLAUDE_CONFIG_DIR:-}" ]; then
    echo '{"loggedIn": true, "authMethod": "oauth_token", "apiProvider": "firstParty"}'
    exit 0
  fi
  printf '{"loggedIn": true, "email": "%s"}\n' "${FAKE_EMAIL:-fake@test}"
  exit 0
fi
if [ "${1:-}" = "auth" ] && [ "${2:-}" = "login" ]; then
  [ -n "${FAKE_LOGIN_FAIL:-}" ] && { echo "login aborted" >&2; exit 1; }
  # simulate a completed full-scope login: write auto-refreshing creds to the config dir
  printf '{"claudeAiOauth":{"accessToken":"sk-ant-oat01-login","refreshToken":"r","expiresAt":9999999999999,"refreshTokenExpiresAt":9999999999999,"scopes":["user:inference"],"subscriptionType":"max"}}' > "${CLAUDE_CONFIG_DIR:-/dev/null}/.credentials.json"
  echo "Logged in."
  exit 0
fi
if [ "${1:-}" = "setup-token" ]; then
  echo "Open this sign-in link: https://claude.ai/oauth/authorize?fake=1"
  [ -n "${FAKE_TOKEN_FAIL:-}" ] && { echo "sign-in aborted" >&2; exit 1; }
  echo "Your token: sk-ant-oat01-FAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKE"
  exit 0
fi
if [ $# -eq 0 ] && [ -n "${FAKE_DO_LOGIN:-}" ] && [ -n "${CLAUDE_CONFIG_DIR:-}" ]; then
  # simulate an interactive session in which the user completed /login
  printf '{"claudeAiOauth":{"accessToken":"sk-ant-oat01-new","refreshToken":"r","expiresAt":9999999999999,"refreshTokenExpiresAt":9999999999999}}' > "$CLAUDE_CONFIG_DIR/.credentials.json"
  exit 0
fi
ctl="${FAKE_CTL:-/nonexistent}"
acct="$(basename "${CLAUDE_CONFIG_DIR:-none}")"
if [ -f "$ctl" ] && grep -qx "fail:$acct" "$ctl" 2>/dev/null; then
  echo "API Error: 429 rate limit exceeded" >&2
  exit 1
fi
if [ -f "$ctl" ] && grep -qx "authfail:$acct" "$ctl" 2>/dev/null; then
  # the exact failure a dead OAuth grant produces
  echo "Failed to authenticate: OAuth session expired and could not be refreshed" >&2
  exit 1
fi
if [ -f "$ctl" ] && grep -qx "orgfail:$acct" "$ctl" 2>/dev/null; then
  # the exact failure an org-disabled account produces (authenticates, cannot infer)
  echo "Your organization has disabled Claude subscription access for Claude Code · Use an Anthropic API key instead, or ask your admin to enable access"
  exit 1
fi
# A limit scoped to ONE MODEL, on EVERY account: rotating cannot help, only
# switching model can. Satisfied the moment the run carries the fallback model.
# The shape every app-robot task actually produces: --output-format stream-json
# reports the API error INSIDE the stream and exits 0.
if [ -f "$ctl" ] && grep -qx "streamlimit" "$ctl" 2>/dev/null; then
  case " $* " in
    *" claude-opus-5 "*|*"--model=claude-opus-5"*)
      echo '{"type":"result","subtype":"success","is_error":false,"result":"done"}' ;;
    *)
      echo '{"type":"result","subtype":"success","is_error":true,"api_error_status":429,"result":"You'"'"'ve reached your Fable 5 limit. Switch to another model, or manage usage credits to continue."}'
      exit 0 ;;
  esac
fi
if [ -f "$ctl" ] && grep -qx "modellimit" "$ctl" 2>/dev/null; then
  case " $* " in
    *" claude-opus-5 "*|*"--model=claude-opus-5"*) : ;;
    *) echo "You've reached your Fable 5 limit. Switch to another model, or manage usage credits at claude.ai/settings/usage to continue." >&2
       exit 1 ;;
  esac
fi
for a in "$@"; do
  case "$a" in
    --exit7) echo "ordinary failure, not auth related" >&2; exit 7 ;;
    --echo-stdin) cat; exit 0 ;;
  esac
done
if [ -n "${FAKE_SESSION_ID:-}" ] && [ -n "${CLAUDE_CONFIG_DIR:-}" ]; then
  # Mimic the real client: register the run in the ACCOUNT dir for its lifetime, write
  # the session transcript into the (shared) projects tree, delete the registry on exit.
  mkdir -p "$CLAUDE_CONFIG_DIR/sessions" "$CLAUDE_CONFIG_DIR/projects/-proj"
  printf '{"pid":%s,"sessionId":"%s","cwd":"/proj","kind":"interactive","startedAt":%s000}\n' \
    "$$" "$FAKE_SESSION_ID" "$(date -u +%s)" > "$CLAUDE_CONFIG_DIR/sessions/$$.json"
  printf '{"type":"mode","mode":"normal","sessionId":"%s"}\n' "$FAKE_SESSION_ID" \
    > "$CLAUDE_CONFIG_DIR/projects/-proj/$FAKE_SESSION_ID.jsonl"
  if [ -n "${FAKE_LIMIT_RESET:-}" ]; then
    printf '{"type":"assistant","timestamp":"%s","message":{"content":[{"type":"text","text":"You'"'"'ve hit your session limit"}]},"quotaLimits":{"status":"rejected","resetsAt":%s,"unifiedRateLimitFallbackAvailable":false,"rateLimitType":"five_hour","overageStatus":"rejected"},"error":"rate_limit","isApiErrorMessage":true,"apiErrorStatus":429,"sessionId":"%s"}\n' \
      "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)" "$FAKE_LIMIT_RESET" "$FAKE_SESSION_ID" \
      >> "$CLAUDE_CONFIG_DIR/projects/-proj/$FAKE_SESSION_ID.jsonl"
  fi
  sleep "${FAKE_SESSION_HOLD:-4}"
  rm -f "$CLAUDE_CONFIG_DIR/sessions/$$.json"
fi
echo "CFG=$acct TOK=${CLAUDE_CODE_OAUTH_TOKEN:-none} ARGS=$*"
EOF
chmod +x "$FAKEBIN/claude"

export PATH="$REPO_DIR/bin:$FAKEBIN:$PATH"
export FAKE_CTL="$WORK/ctl"
# Neutralize any ambient state from the invoking environment.
unset CLAUDE_CONFIG_DIR CLAUDE_CODE_OAUTH_TOKEN CLAUDE_ACCOUNT CLAUDE_SHIM_ACTIVE 2>/dev/null || true
export CLAUDE_MULTIACC_NO_SYNC=1
# Fixtures are local file:// URLs with no rate limit, so the anti-429 fetch throttle
# is off by default here; the throttle test re-enables it explicitly.
export CLAUDE_MULTIACC_MIN_FETCH=0
# The client-rate-limit scan memoizes a CLEAN result for a few seconds; tests plant
# rejections and expect them seen on the very next run, so the memo is off by default
# here (12i re-enables it to prove the memo itself works).
export CLAUDE_MULTIACC_CLIENT_SCAN_TTL=0
# The oauth token endpoint must NEVER be hit for real from tests: default to a missing
# file:// fixture (refresh fails fast, offline); the refresh tests override per-case.
export CLAUDE_MULTIACC_TOKEN_URL="file://$WORK/token-endpoint-missing.json"
# Same for the usage endpoint: the SHIM's opportunistic background `limits --quiet`
# kick inherits this default, so it can never reach a real endpoint from tests
# (every explicit limits test overrides the URL inline). The pre-armed .limits-kick
# throttle keeps those background kicks from racing explicit limits runs.
export CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-endpoint-missing.json"
: > "$ACC/.limits-kick"

now="$(date +%s)"

# ---- 1. passthrough: no manifest yet -------------------------------------
out="$(claude 2>&1)"
check "passthrough without manifest" "CFG=none" "$out"

# ---- manifest + two oauth accounts ----------------------------------------
cat > "$ACC/accounts.json" <<EOF
{
  "version": 1,
  "server": "root@203.0.113.1",
  "server_root": "/root/.claude-accounts",
  "server_repo": "/root/claude-multiacc",
  "threshold": 90,
  "accounts": [
    {"id": "acct-01", "email": "a@test", "home": "mac", "added_at": "2026-07-13T00:00:00Z"},
    {"id": "acct-02", "email": "b@test", "home": "mac", "added_at": "2026-07-13T00:00:00Z"}
  ]
}
EOF
for i in 01 02; do
  mkdir -p "$ACC/acct-$i"
  printf '{"claudeAiOauth":{"accessToken":"sk-ant-oat01-test%s","refreshToken":"r","expiresAt":9999999999999,"refreshTokenExpiresAt":9999999999999,"scopes":["user:inference"],"subscriptionType":"max"}}' "$i" > "$ACC/acct-$i/.credentials.json"
done

# ---- 2-4. passthrough guards ----------------------------------------------
out="$(CLAUDE_CONFIG_DIR=/tmp/other claude 2>&1)"
check "passthrough with CLAUDE_CONFIG_DIR" "CFG=other" "$out"
out="$(CLAUDE_CODE_OAUTH_TOKEN=sk-test claude 2>&1)"
check "passthrough with CLAUDE_CODE_OAUTH_TOKEN" "CFG=none" "$out"
out="$(CLAUDE_MULTIACC_DISABLE=1 claude 2>&1)"
check "passthrough when disabled" "CFG=none" "$out"

# ---- 5. headroom selection: most WEEKLY headroom wins (session is only a tiebreaker) --
lj() { # lj <weekly> <session> <max>  -> a fresh limits.json body
  printf '{"fetched_at":%s,"weekly_percent":%s,"session_percent":%s,"max_percent":%s,"buckets":[]}' "$now" "$1" "$2" "$3"
}
# acct-01 weekly 80, acct-02 weekly 20 => always acct-02.
lj 80 10 80 > "$ACC/acct-01/limits.json"
lj 20 10 20 > "$ACC/acct-02/limits.json"
all2=1
for _ in $(seq 1 15); do
  case "$(claude 2>&1)" in *CFG=acct-02*) ;; *) all2=0 ;; esac
done
[ "$all2" = "1" ] && t_ok "picks the highest weekly-headroom account (weekly 20 over 80)" \
  || t_fail "headroom selection" "picked the more-utilized account"

# THE KEY CASE from the research: a high (but sub-threshold) SESSION bucket must NOT
# deprioritize an account whose weekly headroom is better. acct-01: session 85, weekly 10;
# acct-02: session 20, weekly 70. Both eligible (max<90). acct-01 is the better pick —
# its near-full bucket is the 5h session (self-heals), weekly is nearly untouched.
lj 10 85 85 > "$ACC/acct-01/limits.json"
lj 70 20 70 > "$ACC/acct-02/limits.json"
all1=1
for _ in $(seq 1 15); do
  case "$(claude 2>&1)" in *CFG=acct-01*) ;; *) all1=0 ;; esac
done
[ "$all1" = "1" ] && t_ok "high session does NOT beat better weekly headroom (10w/85s over 70w/20s)" \
  || t_fail "weekly-over-session" "ranked the account with less weekly headroom higher"

# equal weekly => session breaks the tie toward the account with more session headroom
lj 40 20 40 > "$ACC/acct-01/limits.json"
lj 40 80 80 > "$ACC/acct-02/limits.json"
all1=1
for _ in $(seq 1 15); do
  case "$(claude 2>&1)" in *CFG=acct-01*) ;; *) all1=0 ;; esac
done
[ "$all1" = "1" ] && t_ok "equal weekly: lower session wins the tiebreak" \
  || t_fail "session tiebreak" "did not use session to break a weekly tie"

# fully equal scores spread load across accounts
lj 10 10 10 > "$ACC/acct-01/limits.json"
lj 10 10 10 > "$ACC/acct-02/limits.json"
hits1=0; hits2=0
for _ in $(seq 1 40); do
  case "$(claude 2>&1)" in
    *CFG=acct-01*) hits1=$((hits1+1)) ;;
    *CFG=acct-02*) hits2=$((hits2+1)) ;;
  esac
done
{ [ "$hits1" -gt 0 ] && [ "$hits2" -gt 0 ] && [ $((hits1+hits2)) -eq 40 ]; } \
  && t_ok "equal scores spread randomly (acct-01=$hits1 acct-02=$hits2)" \
  || t_fail "tie spreading" "acct-01=$hits1 acct-02=$hits2 (want both >0, total 40)"

# opt-out: legacy uniform-random mode still available
hits1=0; hits2=0
lj 80 80 80 > "$ACC/acct-01/limits.json"
for _ in $(seq 1 40); do
  case "$(CLAUDE_SHIM_SELECT=random claude 2>&1)" in
    *CFG=acct-01*) hits1=$((hits1+1)) ;;
    *CFG=acct-02*) hits2=$((hits2+1)) ;;
  esac
done
{ [ "$hits1" -gt 0 ] && [ "$hits2" -gt 0 ]; } \
  && t_ok "CLAUDE_SHIM_SELECT=random restores uniform spread" \
  || t_fail "random opt-out" "acct-01=$hits1 acct-02=$hits2"

# Unknown telemetry ranks behind every truthful reading, never as neutral or free.
printf '{"fetched_at":1,"weekly_percent":1,"session_percent":1,"max_percent":1,"buckets":[]}' > "$ACC/acct-01/limits.json"
lj 30 30 30 > "$ACC/acct-02/limits.json"
out="$(claude 2>&1)"
check "stale 1% loses to fresh 30% (stale is not trusted)" "CFG=acct-02" "$out"
lj 88 88 88 > "$ACC/acct-01/limits.json"
rm -f "$ACC/acct-02/limits.json" "$ACC/.last-pick"
all1=1
for _ in $(seq 1 15); do
  case "$(claude 2>&1)" in *CFG=acct-01*) ;; *) all1=0 ;; esac
done
[ "$all1" = "1" ] && t_ok "unknown telemetry never beats a known account at 88%" \
  || t_fail "unknown telemetry ranking" "the unknown account beat truthful 88% usage"
rm -f "$ACC/acct-01/limits.json" "$ACC/.last-pick"
hits1=0; hits2=0
for _ in $(seq 1 40); do
  case "$(claude 2>&1)" in
    *CFG=acct-01*) hits1=$((hits1+1)) ;;
    *CFG=acct-02*) hits2=$((hits2+1)) ;;
  esac
done
{ [ "$hits1" -gt 0 ] && [ "$hits2" -gt 0 ]; } \
  && t_ok "an entirely unknown Claude pool still fails open" \
  || t_fail "unknown Claude pool fail-open" "acct-01=$hits1 acct-02=$hits2"
rm -f "$ACC"/acct-*/limits.json

# ---- 6. explicit pin -------------------------------------------------------
out="$(CLAUDE_ACCOUNT=acct-02 claude 2>&1)"
check "CLAUDE_ACCOUNT pin" "CFG=acct-02" "$out"

# ---- 6b. pin works for an auth-less dir (login ceremony path) ----------------
mkdir -p "$ACC/acct-07"
out="$(CLAUDE_ACCOUNT=acct-07 claude 2>&1)"
check "pin to auth-less dir (ceremony)" "CFG=acct-07" "$out"
rmdir "$ACC/acct-07"

# ---- 6c. pin uses the portable token when the credential beside it is DEAD ----
# Regression: the pin path used to export the token only when NO credential file
# existed, so pinning an account whose login had died failed with "OAuth session
# expired" even though its setup-token was sitting right there — the exact state a
# fleet-distributed token lands in on a machine that still has a stale login.
mkdir -p "$ACC/acct-07"
printf '{"claudeAiOauth":{"accessToken":"dead","refreshToken":"","expiresAt":0,"refreshTokenExpiresAt":1}}' \
  > "$ACC/acct-07/.credentials.json"
printf 'sk-ant-oat01-PINNEDDEADCREDSPINNEDDEADCREDSPINNEDDEADCREDS00' > "$ACC/acct-07/server.token"
out="$(CLAUDE_ACCOUNT=acct-07 claude 2>&1)"
check "pinned dead-credential account still runs under its token" "TOK=sk-ant-oat01-PINNED" "$out"
rm -rf "$ACC/acct-07"

# ---- 7. limited marker excludes account ------------------------------------
printf '%s\nbucket=weekly_scoped:Fable percent=95 reason=limits\n' "$((now+3600))" > "$ACC/acct-01/.limited"
all2=1
for _ in $(seq 1 15); do
  out="$(claude 2>&1)"
  case "$out" in *CFG=acct-02*) ;; *) all2=0 ;; esac
done
[ "$all2" = "1" ] && t_ok "limited account excluded from pool" || t_fail "limited account excluded" "acct-01 was still picked"

# ---- 7b. pin overrides marker ----------------------------------------------
out="$(CLAUDE_ACCOUNT=acct-01 claude 2>&1)"
check "explicit pin wins over marker" "CFG=acct-01" "$out"

# ---- 8. expired marker auto-clears ------------------------------------------
printf '%s\nbucket=session percent=95 reason=limits\n' "$((now-10))" > "$ACC/acct-01/.limited"
claude >/dev/null 2>&1
[ ! -f "$ACC/acct-01/.limited" ] && t_ok "expired marker auto-cleared" || t_fail "expired marker auto-cleared" "marker still present"

# ---- 9. all limited -> least utilized fallback ------------------------------
printf '%s\nx\n' "$((now+3600))" > "$ACC/acct-01/.limited"
printf '%s\nx\n' "$((now+3600))" > "$ACC/acct-02/.limited"
printf '{"fetched_at":%s,"max_percent":97,"buckets":[]}' "$now" > "$ACC/acct-01/limits.json"
printf '{"fetched_at":%s,"max_percent":91,"buckets":[]}' "$now" > "$ACC/acct-02/limits.json"
out="$(claude 2>&1)"
check "all-limited falls back to least utilized" "CFG=acct-02" "$out"
grep -q "all-limited fallback=acct-02" "$ACC/selection.log" \
  && t_ok "fallback logged" || t_fail "fallback logged" "no all-limited line in selection.log"
rm -f "$ACC/acct-01/.limited" "$ACC/acct-02/.limited"

# ---- 9b. codex-review regressions: auth/marker/threshold hardening -------------
# empty .credentials.json must NOT count as auth (interrupted write)
mkdir -p "$ACC/acct-06"
: > "$ACC/acct-06/.credentials.json"
out="$(claude 2>&1)"
case "$out" in *CFG=acct-06*) t_fail "empty creds not selectable" "acct-06 was picked" ;;
  *) t_ok "empty .credentials.json is not treated as auth" ;; esac
rm -rf "$ACC/acct-06"

# fresh telemetry >= threshold excludes even when the .limited marker is missing
printf '{"fetched_at":%s,"max_percent":95,"buckets":[]}' "$now" > "$ACC/acct-01/limits.json"
printf '{"fetched_at":%s,"max_percent":10,"buckets":[]}' "$now" > "$ACC/acct-02/limits.json"
rm -f "$ACC"/acct-*/.limited
all2=1
for _ in $(seq 1 12); do
  out="$(claude 2>&1)"
  case "$out" in *CFG=acct-02*) ;; *) all2=0 ;; esac
done
[ "$all2" = "1" ] && t_ok "telemetry backstop excludes >=90% without a marker" \
  || t_fail "telemetry backstop" "acct-01 (95%) was still selected"

# ...but STALE >=90% telemetry must not exclude (fail open, no invented exclusions)
printf '{"fetched_at":1,"max_percent":95,"buckets":[]}' > "$ACC/acct-01/limits.json"
rm -f "$ACC/acct-02/.credentials.json"   # leave acct-01 as the only candidate
out="$(claude 2>&1)"
check "stale >=90% telemetry does not block (fail open)" "CFG=acct-01" "$out"
printf '{"claudeAiOauth":{"accessToken":"sk-ant-oat01-test02","refreshToken":"r","expiresAt":9999999999999,"refreshTokenExpiresAt":9999999999999,"scopes":["user:inference"],"subscriptionType":"max"}}' > "$ACC/acct-02/.credentials.json"

# a garbled/partial marker is treated as ACTIVE and never deleted (concurrent-write race)
printf 'GARBAGE-NOT-AN-EPOCH\n' > "$ACC/acct-01/.limited"
printf '{"fetched_at":%s,"max_percent":10,"buckets":[]}' "$now" > "$ACC/acct-01/limits.json"
out="$(claude 2>&1)"
case "$out" in *CFG=acct-02*) t_ok "garbled marker treated as active (excluded)" ;;
  *) t_fail "garbled marker" "acct-01 was selected despite an unparseable marker" ;; esac
[ -f "$ACC/acct-01/.limited" ] && t_ok "garbled marker not deleted by the shim" \
  || t_fail "garbled marker deleted" "shim destroyed a possibly-mid-write marker"
rm -f "$ACC"/acct-*/.limited "$ACC"/acct-*/limits.json

# ---- 9c. DEAD LOGINS are never selected --------------------------------------
# Regression: an account whose refresh token had expired stayed "valid" (it has a
# .credentials.json), so the shim kept picking it and every run died with
# "Failed to authenticate: OAuth session expired and could not be refreshed".
HEALTHY_CREDS='{"claudeAiOauth":{"accessToken":"sk-ant-oat01-live","refreshToken":"r","expiresAt":9999999999999,"refreshTokenExpiresAt":9999999999999,"scopes":["user:inference"]}}'
DEAD_CREDS='{"claudeAiOauth":{"accessToken":"sk-ant-oat01-dead","refreshToken":"r","expiresAt":1000000000000,"refreshTokenExpiresAt":1000000000000}}'
printf '%s' "$DEAD_CREDS" > "$ACC/acct-01/.credentials.json"
all2=1
for _ in $(seq 1 12); do
  out="$(claude 2>&1)"
  case "$out" in *CFG=acct-02*) ;; *) all2=0 ;; esac
done
[ "$all2" = "1" ] && t_ok "expired refresh token excluded from selection" \
  || t_fail "expired login excluded" "the dead account was still selected"
grep -q "skipped-expired: acct-01" "$ACC/selection.log" \
  && t_ok "skipped-expired logged" || t_fail "skipped-expired log" "no line in selection.log"

# ...and a dead login is not even the all-limited fallback: degraded beats down, but
# dead is DOWN — a limit-marked account that can still authenticate wins.
printf '%s\nx\n' "$((now+3600))" > "$ACC/acct-02/.limited"
out="$(claude 2>&1)"
check "limited-but-alive beats a dead login in the fallback" "CFG=acct-02" "$out"
rm -f "$ACC/acct-02/.limited"

# no refreshToken at all + expired access token = dead too (looped: with a single run a
# random tie-break would let a broken implementation pass half the time)
printf '{"claudeAiOauth":{"accessToken":"sk-ant-oat01-x","expiresAt":1000000000000}}' > "$ACC/acct-01/.credentials.json"
all2=1
for _ in $(seq 1 12); do
  out="$(claude 2>&1)"
  case "$out" in *CFG=acct-02*) ;; *) all2=0 ;; esac
done
[ "$all2" = "1" ] && t_ok "credential with no refresh token is dead" \
  || t_fail "no-refresh-token dead" "the dead account was selected"

# expired ACCESS token with a live refresh token is NOT dead (claude refreshes it)
printf '{"claudeAiOauth":{"accessToken":"sk-ant-oat01-stale","refreshToken":"r","expiresAt":1000000000000,"refreshTokenExpiresAt":9999999999999}}' > "$ACC/acct-01/.credentials.json"
rm -f "$ACC/acct-02/.credentials.json"    # acct-01 is the only candidate
out="$(claude 2>&1)"
check "stale access token + live refresh token stays selectable" "CFG=acct-01" "$out"
printf '%s' "$HEALTHY_CREDS" > "$ACC/acct-02/.credentials.json"

# a dead credential beside a portable token still authenticates — via the token
printf '%s' "$DEAD_CREDS" > "$ACC/acct-01/.credentials.json"
printf '%s' "$DEAD_CREDS" > "$ACC/acct-02/.credentials.json"
printf 'sk-ant-oat01-rescue-token' > "$ACC/acct-01/server.token"
out="$(claude 2>&1)"
check "dead creds + portable token still authenticate" "TOK=sk-ant-oat01-rescue-token" "$out"
rm -f "$ACC/acct-01/server.token"

# every account dead => stock passthrough. The reason lands in selection.log; the
# stderr hint is terminal-only, so a service-spawned `claude -p` stays byte-clean.
out="$(claude 2>&1)"
check "all logins dead -> stock passthrough" "CFG=none" "$out"
case "$out" in *claude-multiacc:*) t_fail "all-dead stderr" "wrote a notice to a non-tty stderr" ;;
  *) t_ok "all logins dead -> no stderr noise for services" ;; esac
grep -q "all-expired: falling back" "$ACC/selection.log" \
  && t_ok "all logins dead -> logged with the fix" || t_fail "all-dead log" "no all-expired line"
printf '%s' "$HEALTHY_CREDS" > "$ACC/acct-01/.credentials.json"
printf '%s' "$HEALTHY_CREDS" > "$ACC/acct-02/.credentials.json"

# A short interactive TUI can report auth failure and exit before the -p retry path can
# inspect stderr. Its account-owned transcript must park that setup-token on the next run.
auth_sid="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
mkdir -p "$ACC/acct-01/projects/auth-regression"
printf '%s %s\n' "$auth_sid" '2020-01-01T00:00:00Z' > "$ACC/acct-01/.sessions-index"
auth_line='{"timestamp":"2026-08-22T20:27:29.985Z","error":"authentication_failed",'
auth_line="$auth_line\"session_id\":\"$auth_sid\"}"
printf '%s\n' "$auth_line" \
  > "$ACC/acct-01/projects/auth-regression/$auth_sid.jsonl"
out="$(claude 2>&1)"
check "TUI transcript auth failure excludes rejected account" "CFG=acct-02" "$out"
grep -q 'reason=auth-error' "$ACC/acct-01/.expired" 2>/dev/null \
  && t_ok "TUI transcript auth failure writes .expired" \
  || t_fail "TUI transcript auth marker" "no auth-error marker"
rm -f "$ACC/acct-01/.expired" "$ACC/acct-01/.sessions-index" \
  "$ACC/acct-01/projects/auth-regression/$auth_sid.jsonl"
rmdir "$ACC/acct-01/projects/auth-regression" 2>/dev/null || true

# ---- 9d. the .expired marker: excludes, and self-heals on a newer credential ----
printf '%s\nreason=auth-error marked_at=now detail=test\n' "$now" > "$ACC/acct-01/.expired"
touch -t 202001010101 "$ACC/acct-01/.credentials.json"   # credential OLDER than the marker
all2=1
for _ in $(seq 1 10); do
  out="$(claude 2>&1)"
  case "$out" in *CFG=acct-02*) ;; *) all2=0 ;; esac
done
[ "$all2" = "1" ] && t_ok ".expired marker excludes the account" \
  || t_fail ".expired marker" "marked account was still selected"
touch -t 202001010101 "$ACC/acct-01/.expired"            # credential now NEWER than the marker
touch "$ACC/acct-01/.credentials.json"
out="$(claude 2>&1)"                                     # any selection pass re-evaluates it
[ ! -f "$ACC/acct-01/.expired" ] && t_ok "a newer credential clears the .expired marker" \
  || t_fail ".expired self-heal" "marker survived a re-login"
rm -f "$ACC/acct-01/.expired"

# ---- 9e. an auth failure parks the account (not a 10-minute cooldown) ----------
# Rate limits heal on their own; a dead grant does not — so it gets .expired, and the
# run still completes on another account.
printf '{"fetched_at":%s,"max_percent":1,"weekly_percent":1,"session_percent":1,"buckets":[]}' "$now" > "$ACC/acct-01/limits.json"
printf '{"fetched_at":%s,"max_percent":50,"weekly_percent":50,"session_percent":50,"buckets":[]}' "$now" > "$ACC/acct-02/limits.json"
echo "authfail:acct-01" > "$FAKE_CTL"
out="$(claude -p hello < /dev/null 2>/dev/null)"
rc=$?
{ [ "$rc" = "0" ] && case "$out" in *CFG=acct-02*) true ;; *) false ;; esac; } \
  && t_ok "auth failure retries onto a healthy account" \
  || t_fail "auth failure retry" "rc=$rc out=$out"
[ -f "$ACC/acct-01/.expired" ] && t_ok "auth failure writes .expired" \
  || t_fail "auth failure marker" ".expired missing"
[ ! -f "$ACC/acct-01/.limited" ] && t_ok "auth failure is not treated as a rate limit" \
  || t_fail "auth failure marker" "got a 10-minute .limited cooldown instead"
# The shim's park is a GUESS from one run, so it carries its own expiry: once the soft
# window passes the account returns to the pool with no external help.
grep -q "soft_until=" "$ACC/acct-01/.expired" \
  && t_ok "a shim-written park carries a soft expiry" \
  || t_fail "soft park" "no soft_until in the shim-written marker"
printf '%s\nreason=auth-error soft_until=%s detail=elapsed\n' "$((now-7200))" "$((now-10))" > "$ACC/acct-01/.expired"
touch -t 202001010101 "$ACC/acct-01/.credentials.json"
out="$(CLAUDE_ACCOUNT='' claude 2>&1)"
[ ! -f "$ACC/acct-01/.expired" ] && t_ok "an elapsed soft park releases the account" \
  || t_fail "soft park expiry" "the account stayed parked past soft_until"
touch "$ACC/acct-01/.credentials.json"
rm -f "$FAKE_CTL" "$ACC/acct-01/.expired" "$ACC"/acct-*/limits.json

# ...and the park is NOT triggered by the model merely talking about auth. This is the
# hot path for `claude -p`: the grep also sees the model's own answer on stdout.
cat > "$FAKEBIN/claude-chatty" <<'EOF'
#!/usr/bin/env bash
echo "Your nginx returns 403 Forbidden. Please run: nginx -t to check the config."
echo "hook failed" >&2
exit 2
EOF
chmod +x "$FAKEBIN/claude-chatty"
mv "$FAKEBIN/claude" "$FAKEBIN/claude.real"; mv "$FAKEBIN/claude-chatty" "$FAKEBIN/claude"
claude -p "why does nginx 403" < /dev/null >/dev/null 2>&1
mv "$FAKEBIN/claude" "$FAKEBIN/claude-chatty"; mv "$FAKEBIN/claude.real" "$FAKEBIN/claude"
if [ -f "$ACC/acct-01/.expired" ] || [ -f "$ACC/acct-02/.expired" ]; then
  t_fail "answer text must not park an account" "the model mentioning 403/'please run' parked an account"
else
  t_ok "a model answer mentioning 403 does not park an account"
fi
rm -f "$ACC"/acct-*/.limited "$ACC"/acct-*/.expired

# ---- 9f. org-disabled accounts are parked too ---------------------------------
# "Your organization has disabled Claude subscription access for Claude Code" is not an
# auth failure (the credential is perfectly valid) and not a rate limit — but the
# account fails EVERY call, so it must leave the pool. A re-login cannot fix it.
printf '{"fetched_at":%s,"max_percent":1,"weekly_percent":1,"session_percent":1,"buckets":[]}' "$now" > "$ACC/acct-01/limits.json"
printf '{"fetched_at":%s,"max_percent":50,"weekly_percent":50,"session_percent":50,"buckets":[]}' "$now" > "$ACC/acct-02/limits.json"
echo "orgfail:acct-01" > "$FAKE_CTL"
out="$(claude -p hello < /dev/null 2>/dev/null)"
rc=$?
{ [ "$rc" = "0" ] && case "$out" in *CFG=acct-02*) true ;; *) false ;; esac; } \
  && t_ok "org-disabled account retries onto a healthy one" \
  || t_fail "org-block retry" "rc=$rc out=$out"
grep -q "reason=org-blocked" "$ACC/acct-01/.expired" 2>/dev/null \
  && t_ok "org-disabled account is parked as org-blocked" \
  || t_fail "org-block marker" "no reason=org-blocked marker"
rm -f "$FAKE_CTL" "$ACC"/acct-*/limits.json
# it stays out of the pool...
all2=1
for _ in $(seq 1 10); do
  out="$(claude 2>&1)"
  case "$out" in *CFG=acct-02*) ;; *) all2=0 ;; esac
done
[ "$all2" = "1" ] && t_ok "org-blocked account excluded from selection" \
  || t_fail "org-block exclusion" "the blocked account was still selected"
# ...and a park for an ORG BLOCK survives a credential rewrite. The token refresher
# rewrites .credentials.json every few hours; treating that as "the account recovered"
# handed blocked accounts straight back to the pool and runs kept failing with
# "Your organization has disabled Claude subscription access".
touch "$ACC/acct-01/.credentials.json"          # credential now NEWER than the marker
all2=1
for _ in $(seq 1 10); do
  out="$(claude 2>&1)"
  case "$out" in *CFG=acct-02*) ;; *) all2=0 ;; esac
done
[ "$all2" = "1" ] && t_ok "an org block survives a credential refresh" \
  || t_fail "org-block stickiness" "a rewritten credential un-parked a blocked account"
[ -f "$ACC/acct-01/.expired" ] && t_ok "the org-block marker is not deleted by a refresh" \
  || t_fail "org-block stickiness" "marker removed by a credential rewrite"
# a CREDENTIAL park still self-heals on a newer credential (unchanged behavior)
printf '%s\nreason=auth-error detail=test\n' "$((now-100))" > "$ACC/acct-01/.expired"
touch -t 202001010101 "$ACC/acct-01/.expired"    # marker older than the credential
touch "$ACC/acct-01/.credentials.json"
claude >/dev/null 2>&1
[ ! -f "$ACC/acct-01/.expired" ] && t_ok "a credential park still clears on a newer credential" \
  || t_fail "credential park" "marker survived a fresh credential"
printf '%s\nreason=org-blocked detail=test\n' "$((now-100))" > "$ACC/acct-01/.expired"
# `expired` explains it and points at the same fix as any other dead login
out="$(claude-accounts expired 2>&1)"
check "expired labels an org-blocked account" "BLOCKED" "$out"
check "expired explains the org block" "organization has disabled" "$out"
check "expired points org blocks at relogin" "relogin acct-01" "$out"
out="$(claude-accounts list 2>&1)"
check "list flags org-blocked accounts" "ORG-BLOCKED" "$out"
# relogin targets them like any other dead login
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=a@test claude-accounts relogin --yes 2>&1)"
check "relogin targets org-blocked accounts" "acct-01 login saved" "$out"
[ ! -f "$ACC/acct-01/.expired" ] && t_ok "a successful re-login clears an org block" \
  || t_fail "org-block relogin" "marker survived the re-login"
rm -f "$ACC/acct-01/.expired"

# ---- 10. token-only account exports CLAUDE_CODE_OAUTH_TOKEN ------------------
mkdir -p "$ACC/acct-03"
printf 'sk-ant-oat01-tok-for-03' > "$ACC/acct-03/server.token"
out="$(CLAUDE_ACCOUNT=acct-03 claude 2>&1)"
check "token-only account exports token" "TOK=sk-ant-oat01-tok-for-03" "$out"

# ---- 10b. user args survive selection (regression: pick_best must not touch "$@") --
out="$(claude --echo-stdin </dev/null 2>&1)"
[ -z "$out" ] && t_ok "args reach the real binary intact (no positional clobber)" \
  || t_fail "arg passthrough" "--echo-stdin ignored, got: $out"

# ---- 11. exit code passthrough (no retry on non-auth failure) ----------------
claude -p --exit7 >/dev/null 2>"$WORK/err11"
rc=$?
[ "$rc" = "7" ] && t_ok "exit code passthrough (rc=7)" || t_fail "exit code passthrough" "rc=$rc"

# ---- 12. -p retry on rate limit switches account -----------------------------
rm -f "$ACC"/acct-*/.limited
echo "fail:acct-01" > "$FAKE_CTL"
ok12=1
for _ in $(seq 1 10); do
  out="$(claude -p hello < /dev/null 2>/dev/null)"
  rc=$?
  { [ "$rc" = "0" ] && case "$out" in *CFG=acct-0[23]*) true ;; *) false ;; esac; } || ok12=0
done
[ "$ok12" = "1" ] && t_ok "-p retry recovers via another account" || t_fail "-p retry" "some run failed or used acct-01 output"
[ -f "$ACC/acct-01/.limited" ] && t_ok "failed account got error-cooldown marker" || t_fail "cooldown marker" "missing"
rm -f "$FAKE_CTL" "$ACC/acct-01/.limited"

# ---- 12a. every account out of ONE model: switch model, do not fail ----------
# 2026-08-24: all four accounts crossed the Fable weekly bucket inside an hour and
# every task died in under a second having done no work. Rotating accounts cannot
# fix a limit scoped to a model — the endpoint says so itself ("Switch to another
# model"). The pool still had capacity for every other model.
rm -f "$ACC"/acct-*/.limited
echo "modellimit" > "$FAKE_CTL"
out="$(claude -p --model claude-fable-5 hello < /dev/null 2>/dev/null)"
rc=$?
case "$out" in
  *"ARGS="*"claude-opus-5"*) t_ok "a model-scoped limit falls back to another model" ;;
  *) t_fail "model fallback" "rc=$rc out=$out" ;;
esac
[ "$rc" = "0" ] && t_ok "...and the task succeeds instead of failing" \
  || t_fail "model fallback rc" "rc=$rc"
grep -q "model fallback" "$ACC/selection.log" 2>/dev/null \
  && t_ok "the model switch is recorded in the selection log" \
  || t_fail "model fallback log" "nothing logged"

# The pinned model is the caller's choice and must survive a recoverable failure:
# fall back only once ROTATION has been tried and could not help.
rm -f "$ACC"/acct-*/.limited; : > "$ACC/selection.log"
echo "fail:acct-01" > "$FAKE_CTL"
out="$(claude -p --model claude-fable-5 hello < /dev/null 2>/dev/null)"
case "$out" in
  *"claude-fable-5"*) t_ok "an ordinary rate limit rotates account and KEEPS the model" ;;
  *) t_fail "model preserved" "out=$out" ;;
esac
rm -f "$FAKE_CTL" "$ACC"/acct-*/.limited

# ---- 12a2. an in-stream API error still triggers recovery --------------------
# With --output-format stream-json the CLI exits 0 and reports the 429 as the final
# result object. Every app-robot task takes that path, so gating retry on the exit
# status alone meant the pool never rotated and never fell back for the exact
# failures it exists for — while the shim recorded a clean success every time.
rm -f "$ACC"/acct-*/.limited; : > "$ACC/selection.log"
echo "streamlimit" > "$FAKE_CTL"
out="$(claude -p --model claude-fable-5 --output-format stream-json hello < /dev/null 2>/dev/null)"
rc=$?
case "$out" in
  *'"is_error":false'*) t_ok "an in-stream API error is recovered, not reported as success" ;;
  *) t_fail "stream error recovery" "rc=$rc out=$out" ;;
esac
[ "$rc" = "0" ] && t_ok "...and the caller still gets exit 0, as the CLI would give" \
  || t_fail "stream error exit status" "rc=$rc"

# A stream that merely MENTIONS a limit mid-run and then completes must be left alone:
# re-running it would throw away a finished task.
rm -f "$FAKE_CTL" "$ACC"/acct-*/.limited; : > "$ACC/selection.log"
out="$(claude -p --model claude-fable-5 hello < /dev/null 2>/dev/null)"
grep -q "retry from=" "$ACC/selection.log" 2>/dev/null \
  && t_fail "spurious retry" "a healthy run was retried" \
  || t_ok "a healthy run is never retried"
rm -f "$FAKE_CTL" "$ACC"/acct-*/.limited

# ---- 12b. pipe stdin skips retry buffering but passes bytes through -----------
out="$(printf 'pipe-data' | claude -p --echo-stdin 2>/dev/null)"
[ "$out" = "pipe-data" ] && t_ok "pipe stdin passes through (no retry buffering)" || t_fail "pipe stdin passthrough" "got: $out"

# ---- 12c. HOME unset: shim still fails open into passthrough ------------------
out="$(env -u HOME -u CLAUDE_ACCOUNTS_DIR claude 2>&1)"
check "HOME unset -> passthrough, no crash" "CFG=none" "$out"

# ---- 12d. error-cooldown marker survives a clean limits pass -------------------
printf '%s\nbucket=error-cooldown percent=? reason=error-cooldown\n' "$(( $(date +%s) + 600 ))" > "$ACC/acct-01/.limited"
cat > "$WORK/usage-mid.json" <<'EOF'
{"limits":[{"kind":"session","percent":10,"resets_at":"2099-01-01T00:00:00+00:00","scope":null}]}
EOF
CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-mid.json" claude-accounts limits --quiet
[ -f "$ACC/acct-01/.limited" ] && t_ok "error-cooldown marker survives clean limits refresh" || t_fail "cooldown vs limits" "marker was cleared early"
rm -f "$ACC/acct-01/.limited"

# ---- 12f-12h. client-reported rate limits + rotation (own two-account pool) -------
# The shared pool has picked up a third account by now, so these run in an instance root
# of their own: with exactly two accounts, "went somewhere else" and "rotated" are both
# unambiguous.
CLP="$WORK/client-limit-pool"
mkdir -p "$CLP/tmp"
: > "$CLP/.limits-kick"
cat > "$CLP/accounts.json" <<'EOF'
{"version":1,"threshold":90,"accounts":[
  {"id":"acct-01","email":"cl1@test","home":"mac","added_at":"2026-07-13T00:00:00Z"},
  {"id":"acct-02","email":"cl2@test","home":"mac","added_at":"2026-07-13T00:00:00Z"}]}
EOF
for i in 01 02; do
  mkdir -p "$CLP/acct-$i"
  printf '{"claudeAiOauth":{"accessToken":"sk-ant-oat01-cl%s","refreshToken":"r","expiresAt":9999999999999,"refreshTokenExpiresAt":9999999999999}}' "$i" > "$CLP/acct-$i/.credentials.json"
done
export CLAUDE_ACCOUNTS_ROOT="$CLP"
ACC_SAVED="$ACC"
ACC="$CLP"

# ---- 12f. a rate limit hit by an INTERACTIVE session takes its account out ---------
# The reported bug: a tmux session runs into its 5h limit, the user quits and starts
# `claude` again, and the pool hands back the same dead account. Interactive runs are
# exec'd, so the -p retry path above never sees them — the only trace of the rejection
# is the one Claude Code writes itself, in the session transcript.
mkclientlimit() { # mkclientlimit <acct dir> <session id> <resetsAt> [rejection ISO ts]
  mkdir -p "$1/projects/-proj"
  printf '{"type":"mode","mode":"normal","sessionId":"%s"}\n' "$2" > "$1/projects/-proj/$2.jsonl"
  printf '{"type":"assistant","timestamp":"%s","message":{"content":[{"type":"text","text":"limit"}]},"quotaLimits":{"status":"rejected","resetsAt":%s,"unifiedRateLimitFallbackAvailable":false,"rateLimitType":"five_hour","overageStatus":"rejected"},"error":"rate_limit","isApiErrorMessage":true,"apiErrorStatus":429,"sessionId":"%s"}\n' \
    "${4:-$(date -u +%Y-%m-%dT%H:%M:%S.000Z)}" "$3" "$2" >> "$1/projects/-proj/$2.jsonl"
  # index line = "<id> <ISO claim>": the account that owned the session, and from when
  printf '%s %s\n' "$2" "$(date -u -r "$(( $(date +%s) - 600 ))" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d "@$(( $(date +%s) - 600 ))" +%Y-%m-%dT%H:%M:%SZ)" > "$1/.sessions-index"
}
SID1="81bf8b20-013f-4414-8878-e0289bec9ad0"
RESET1=$(( $(date +%s) + 1800 ))
mkclientlimit "$ACC/acct-01" "$SID1" "$RESET1"
all2=1
for _ in $(seq 1 12); do
  case "$(claude 2>&1)" in *CFG=acct-02*) ;; *) all2=0 ;; esac
done
[ "$all2" = "1" ] && t_ok "client-reported rate limit excludes the account" \
  || t_fail "client rate limit" "the rejected account was picked again"
first="$(head -1 "$ACC/acct-01/.limited" 2>/dev/null)"
[ "$first" = "$RESET1" ] && t_ok "marker carries the API's own reset time" \
  || t_fail "client marker reset" "want $RESET1, got '${first:-<none>}'"
grep -q 'reason=client-rate-limit' "$ACC/acct-01/.limited" 2>/dev/null \
  && t_ok "marker is tagged client-rate-limit" || t_fail "client marker reason" "$(cat "$ACC/acct-01/.limited" 2>/dev/null)"
out="$(CLAUDE_ACCOUNT=acct-01 claude 2>&1)"
check "explicit pin still wins over a client-reported limit" "CFG=acct-01" "$out"

# a marker the client earned survives a clean telemetry pass while its window is open
CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-mid.json" claude-accounts limits --quiet
[ -f "$ACC/acct-01/.limited" ] && t_ok "client-rate-limit marker survives a clean limits refresh" \
  || t_fail "client marker vs limits" "marker was cleared while the window was still open"
rm -f "$ACC/acct-01/.limited"

# an ALREADY-ELAPSED rejection is history, not an exclusion
mkclientlimit "$ACC/acct-01" "$SID1" "$(( $(date +%s) - 60 ))"
hits1=0
for _ in $(seq 1 12); do
  case "$(claude 2>&1)" in *CFG=acct-01*) hits1=$((hits1+1)) ;; esac
done
{ [ "$hits1" -gt 0 ] && [ ! -f "$ACC/acct-01/.limited" ]; } \
  && t_ok "an elapsed client rejection does not exclude" \
  || t_fail "elapsed client rejection" "acct-01 hits=$hits1 marker=$([ -f "$ACC/acct-01/.limited" ] && echo yes || echo no)"

# opt-out
mkclientlimit "$ACC/acct-01" "$SID1" "$(( $(date +%s) + 1800 ))"
rm -f "$ACC/acct-01/.limited"
hits1=0
for _ in $(seq 1 12); do
  case "$(CLAUDE_MULTIACC_CLIENT_LIMITS=0 claude 2>&1)" in *CFG=acct-01*) hits1=$((hits1+1)) ;; esac
done
{ [ "$hits1" -gt 0 ] && [ ! -f "$ACC/acct-01/.limited" ]; } \
  && t_ok "CLAUDE_MULTIACC_CLIENT_LIMITS=0 turns the scan off" \
  || t_fail "client-limit opt-out" "still excluded with the scan disabled"

# a hostile session index must not walk out of the pool
printf '../../../../etc/passwd x\n-e x\n%s %s\n' "$SID1" "2026-01-01T00:00:00Z" > "$ACC/acct-01/.sessions-index"
claude >/dev/null 2>&1
grep -q 'reason=client-rate-limit' "$ACC/acct-01/.limited" 2>/dev/null \
  && t_ok "traversal and option-shaped ids are ignored, real ids still scanned" \
  || t_fail "session index traversal" "scan broke on a hostile entry"
rm -f "$ACC"/acct-0*/.limited "$ACC"/acct-0*/.sessions-index
rm -rf "$ACC"/acct-0*/projects

# ---- 12g. the whole round trip: a run registers itself, the NEXT run avoids it -----
# No .sessions-index is planted here. The shim's detached capture has to learn the
# session id from $acct/sessions/<pid>.json WHILE the run is alive — after it exits the
# client deletes that file and nothing else can name the transcript.
rm -f "$ACC/.pick-seq" "$ACC"/acct-0*/.last-pick
FAKE_SESSION_ID="9f3c1d2e-0000-4000-8000-abcdefabcdef" \
  FAKE_LIMIT_RESET="$(( $(date +%s) + 1800 ))" \
  FAKE_SESSION_HOLD=5 claude >/dev/null 2>&1
hit="$(ls "$ACC"/acct-0*/.sessions-index 2>/dev/null | head -1)"
limited_dir="$(dirname "${hit:-/nonexistent}")"
[ -n "$hit" ] && t_ok "the run's own session id is captured while it is alive" \
  || t_fail "session capture" "no .sessions-index was written"
out="$(claude 2>&1)"
case "$out" in
  *"CFG=$(basename "$limited_dir")"*)
    t_fail "restart after a limit hit" "landed straight back on $(basename "$limited_dir")" ;;
  *) t_ok "quitting a limit-hit session and restarting lands on another account" ;;
esac
grep -q 'reason=client-rate-limit' "$limited_dir/.limited" 2>/dev/null \
  && t_ok "the account that hit the limit is marked from its own transcript" \
  || t_fail "post-run marking" "no client-rate-limit marker on $(basename "$limited_dir")"
rm -f "$ACC"/acct-0*/.limited "$ACC"/acct-0*/.sessions-index
rm -rf "$ACC"/acct-0*/projects "$ACC"/acct-0*/sessions

# ---- 12h. equal-headroom picks ROTATE instead of re-rolling a coin -----------------
# With telemetry stale (the usage endpoint 429s its own callers for hours) every account
# scores NEUTRAL, so this tie-break is the only thing standing between a restart and the
# account it just walked away from.
rm -f "$ACC/.pick-seq" "$ACC"/acct-0*/.last-pick "$ACC"/acct-0*/limits.json
seq_out=""
for _ in $(seq 1 8); do
  case "$(claude 2>&1)" in
    *CFG=acct-01*) seq_out="${seq_out}1" ;;
    *CFG=acct-02*) seq_out="${seq_out}2" ;;
    *) seq_out="${seq_out}?" ;;
  esac
done
case "$seq_out" in
  12121212|21212121) t_ok "equal-score picks round-robin across the pool ($seq_out)" ;;
  *) t_fail "round-robin tie-break" "sequence $seq_out (want strict alternation)" ;;
esac
# ... and the rotation must never override real headroom
lj 80 10 80 > "$ACC/acct-01/limits.json"
lj 20 10 20 > "$ACC/acct-02/limits.json"
all2=1
for _ in $(seq 1 8); do
  case "$(claude 2>&1)" in *CFG=acct-02*) ;; *) all2=0 ;; esac
done
[ "$all2" = "1" ] && t_ok "rotation never outranks measured headroom" \
  || t_fail "rotation vs headroom" "rotation pulled work onto the busier account"
rm -f "$ACC"/acct-0*/limits.json "$ACC/.pick-seq" "$ACC"/acct-0*/.last-pick

# ---- 12i. the clean-scan memo throttles re-reads without stranding a limit ---------
rm -f "$ACC"/acct-0*/.limited "$ACC"/acct-0*/.client-scan "$ACC"/acct-0*/.sessions-index
rm -rf "$ACC"/acct-0*/projects
mkdir -p "$ACC/acct-01/projects/-proj"
printf '{"type":"mode","mode":"normal","sessionId":"%s"}\n' "$SID1" > "$ACC/acct-01/projects/-proj/$SID1.jsonl"
printf '%s %s\n' "$SID1" "2026-01-01T00:00:00Z" > "$ACC/acct-01/.sessions-index"
claude >/dev/null 2>&1     # a clean scan over a real (rejection-free) transcript
[ -f "$ACC/acct-01/.client-scan" ] \
  && t_ok "a clean client scan is memoized" || t_fail "clean scan memo" "no .client-scan written"
mkclientlimit "$ACC/acct-01" "$SID1" "$(( $(date +%s) + 1800 ))"
CLAUDE_MULTIACC_CLIENT_SCAN_TTL=3600 claude >/dev/null 2>&1
[ ! -f "$ACC/acct-01/.limited" ] \
  && t_ok "the memo suppresses a re-read inside its window" \
  || t_fail "clean scan memo" "rescanned inside the memo window"
rm -f "$ACC"/acct-0*/.client-scan
claude >/dev/null 2>&1
grep -q 'reason=client-rate-limit' "$ACC/acct-01/.limited" 2>/dev/null \
  && t_ok "once the memo lapses the rejection is seen" \
  || t_fail "clean scan memo" "the rejection was never picked up"
rm -f "$ACC"/acct-0*/.limited "$ACC"/acct-0*/.client-scan "$ACC"/acct-0*/.sessions-index
rm -rf "$ACC"/acct-0*/projects

# ---- 12j. codex-review: corrupt pool numbers never reach bash arithmetic -----------
# An out-of-range value in any scraped number makes `[ x -lt y ]` print "integer
# expression expected" on STDERR — which a service-spawned `claude -p` must never see —
# and makes $((x + 1)) wrap negative.
rm -f "$ACC"/acct-0*/.limited "$ACC"/acct-0*/.client-scan "$ACC"/acct-0*/.sessions-index
rm -rf "$ACC"/acct-0*/projects
BIG="99999999999999999999999999999999"
printf '%s\n' "$BIG" > "$ACC/.last-pick"
printf '{"fetched_at":%s,"weekly_percent":%s,"session_percent":1,"max_percent":%s,"buckets":[]}' "$BIG" "$BIG" "$BIG" > "$ACC/acct-02/limits.json"
err="$(claude 2>&1 >/dev/null)"
[ -z "$err" ] && t_ok "absurd pool numbers keep stderr byte-clean" \
  || t_fail "corrupt number handling" "stderr: $(printf '%s' "$err" | head -c 160)"
out="$(claude 2>/dev/null)"
case "$out" in *CFG=acct-0*) t_ok "selection still works with corrupt numbers ($out)" ;;
  *) t_fail "corrupt number handling" "selection produced: $out" ;; esac
# caller-supplied numbers are just as capable of reaching `[ -lt ]` as pool state
: > "$ACC/acct-01/.client-scan"
err="$(CLAUDE_MULTIACC_CLIENT_SCAN_TTL=bogus CLAUDE_MULTIACC_THRESHOLD=nonsense claude 2>&1 >/dev/null)"
out="$(CLAUDE_MULTIACC_CLIENT_SCAN_TTL=bogus CLAUDE_MULTIACC_THRESHOLD=nonsense claude 2>/dev/null)"
{ [ -z "$err" ] && case "$out" in *CFG=acct-0*) true ;; *) false ;; esac; } \
  && t_ok "garbage in the client-scan TTL / threshold env vars keeps stderr clean" \
  || t_fail "env number validation" "stderr: $(printf '%s' "$err" | head -c 160) out: $out"
rm -f "$ACC"/acct-0*/.client-scan

printf '%s\nbucket=x percent=? reason=limits\n' "$BIG" > "$ACC/acct-01/.limited"
err="$(claude 2>&1 >/dev/null)"
{ [ -z "$err" ] && [ -f "$ACC/acct-01/.limited" ]; } \
  && t_ok "an absurd .limited reset reads as LIMITED, silently" \
  || t_fail "corrupt marker handling" "stderr: $(printf '%s' "$err" | head -c 160)"
rm -f "$ACC"/acct-0*/.limited "$ACC"/acct-0*/limits.json "$ACC/.last-pick"

# ---- 12m. a session id belongs to ONE account, and only from when it took it over ---
# `claude --continue` resumes the SAME session id under whichever account the pool hands
# out next (the client only mints a new id with --fork-session) and the transcript is
# shared — so an id claimed by acct-02 must stop being acct-01's evidence, and a rejection
# recorded before the handover must not be charged to the new owner.
rm -f "$ACC"/acct-0*/.limited "$ACC"/acct-0*/.sessions-index "$ACC"/acct-0*/.client-scan
rm -rf "$ACC"/acct-0*/projects "$ACC"/acct-0*/sessions
SIDC="0badc0de-1111-4111-8111-abcdefabcdef"
NOWS="$(date -u +%s)"
OLDISO="$(date -u -r "$((NOWS - 7200))" +%Y-%m-%dT%H:%M:%S.000Z 2>/dev/null || date -u -d "@$((NOWS - 7200))" +%Y-%m-%dT%H:%M:%S.000Z)"
# acct-01 ran the session two hours ago and hit a limit then; acct-02 has just resumed it.
mkclientlimit "$ACC/acct-01" "$SIDC" "$((NOWS + 1800))" "$OLDISO"
mkdir -p "$ACC/acct-02/sessions" "$ACC/acct-02/projects"
ln -s "$ACC/acct-01/projects/-proj" "$ACC/acct-02/projects/-proj"
printf '{"pid":4242,"sessionId":"%s","cwd":"/proj","kind":"interactive","startedAt":%s000}\n' \
  "$SIDC" "$NOWS" > "$ACC/acct-02/sessions/4242.json"
claude >/dev/null 2>&1
grep -q -- "^$SIDC " "$ACC/acct-02/.sessions-index" 2>/dev/null \
  && t_ok "a resumed session id is claimed by the account now running it" \
  || t_fail "session id handover" "acct-02 never claimed the resumed id"
grep -q -- "^$SIDC " "$ACC/acct-01/.sessions-index" 2>/dev/null \
  && t_fail "session id handover" "acct-01 still claims an id acct-02 took over" \
  || t_ok "the previous owner releases a resumed session id"
[ ! -f "$ACC/acct-02/.limited" ] \
  && t_ok "a rejection older than the handover is not charged to the new owner" \
  || t_fail "claim-time scoping" "acct-02 was marked for a limit acct-01 hit"
rm -f "$ACC/acct-02/projects/-proj" "$ACC"/acct-0*/.limited "$ACC"/acct-0*/.sessions-index "$ACC"/acct-0*/.client-scan
rm -rf "$ACC"/acct-0*/projects "$ACC"/acct-0*/sessions

# two accounts claiming the SAME id (a crossed race, or corrupt state) must not both be
# marked off one shared transcript — at most the newer claimant may answer for it
rm -f "$ACC"/acct-0*/.limited "$ACC"/acct-0*/.sessions-index "$ACC"/acct-0*/.client-scan
rm -rf "$ACC"/acct-0*/projects "$ACC"/acct-0*/sessions
SIDD="0dadc0de-2222-4222-8222-abcdefabcdef"   # hex only, or sess_id_ok drops it and the
                                             # scan never reaches the conflict check
NOWD="$(date -u +%s)"
mkclientlimit "$ACC/acct-01" "$SIDD" "$((NOWD + 1800))"
mkdir -p "$ACC/acct-02/projects"
ln -s "$ACC/acct-01/projects/-proj" "$ACC/acct-02/projects/-proj"
CLAIM1="$(cat "$ACC/acct-01/.sessions-index")"
printf '%s\n' "$CLAIM1" > "$ACC/acct-02/.sessions-index"    # identical id AND claim
claude >/dev/null 2>&1
n_marked=0
for d in "$ACC/acct-01" "$ACC/acct-02"; do [ -f "$d/.limited" ] && n_marked=$((n_marked+1)); done
[ "$n_marked" -eq 0 ] \
  && t_ok "an id claimed by two accounts marks neither (ambiguity => no attribution)" \
  || t_fail "duplicate session claim" "$n_marked accounts were marked off one transcript"
# ...and it is the NEWEST rival claim that decides, not whichever one grep prints first
rm -f "$ACC"/acct-0*/.limited "$ACC"/acct-0*/.client-scan
{ printf '%s 2020-01-01T00:00:00Z\n' "$SIDD"; printf '%s 2090-01-01T00:00:00Z\n' "$SIDD"; } \
  > "$ACC/acct-02/.sessions-index"
claude >/dev/null 2>&1
[ ! -f "$ACC/acct-01/.limited" ] \
  && t_ok "a stale rival claim does not hide a newer one" \
  || t_fail "duplicate session claim" "only the first rival claim was inspected"
rm -f "$ACC/acct-02/projects/-proj" "$ACC"/acct-0*/.limited "$ACC"/acct-0*/.sessions-index "$ACC"/acct-0*/.client-scan
rm -rf "$ACC"/acct-0*/projects "$ACC"/acct-0*/sessions

# a SYMLINKED registry entry belongs to whatever it points at, not to this account
rm -f "$ACC"/acct-0*/.limited "$ACC"/acct-0*/.sessions-index "$ACC"/acct-0*/.client-scan
rm -rf "$ACC"/acct-0*/projects "$ACC"/acct-0*/sessions
FOREIGN="$WORK/foreign-session.json"
printf '{"pid":9,"sessionId":"%s","cwd":"/proj","kind":"interactive","startedAt":%s000}\n' \
  "$SID1" "$(( $(date -u +%s) - 600 ))" > "$FOREIGN"
mkclientlimit "$ACC/acct-01" "$SID1" "$(( $(date +%s) + 1800 ))"
rm -f "$ACC/acct-01/.sessions-index"
mkdir -p "$ACC/acct-01/sessions"
ln -s "$FOREIGN" "$ACC/acct-01/sessions/9.json"
claude >/dev/null 2>&1
[ ! -f "$ACC/acct-01/.limited" ] \
  && t_ok "a symlinked session registry entry is not this account's evidence" \
  || t_fail "symlinked registry entry" "a foreign registry entry marked the account LIMITED"
rm -f "$ACC"/acct-0*/.limited "$ACC"/acct-0*/.sessions-index "$ACC"/acct-0*/.client-scan
rm -rf "$ACC"/acct-0*/projects "$ACC"/acct-0*/sessions

# ---- 12n. an option-shaped session id must not hang the shim before exec ------------
# "-e" is hex+dash, so a charset check alone lets it through; grep then treats it as a
# flag, loses its file operand and blocks on the shim's OWN stdin — a hang before exec.
rm -f "$ACC"/acct-0*/.sessions-index "$ACC"/acct-0*/.client-scan
mkdir -p "$ACC/acct-01/sessions"
printf '{"pid":7,"sessionId":"-e","cwd":"/proj","kind":"interactive","startedAt":%s000}\n' "$(date -u +%s)" \
  > "$ACC/acct-01/sessions/7.json"
printf 'deadbeef 2026-01-01T00:00:00Z\n' > "$ACC/acct-01/.sessions-index"
FIFO="$WORK/hangfifo"
rm -f "$FIFO"; mkfifo "$FIFO"
exec 9<>"$FIFO"                       # holds the fifo open: stdin that never EOFs
( claude < "$FIFO" > "$WORK/hang.out" 2>&1 ) &
hangpid=$!
waited=0
while [ "$waited" -lt 10 ] && kill -0 "$hangpid" 2>/dev/null; do sleep 1; waited=$((waited+1)); done
if kill -0 "$hangpid" 2>/dev/null; then
  kill -9 "$hangpid" 2>/dev/null
  t_fail "option-shaped session id" "the shim hung for ${waited}s before exec"
else
  wait "$hangpid" 2>/dev/null || true
  case "$(cat "$WORK/hang.out" 2>/dev/null)" in
    *CFG=acct-0*) t_ok "an option-shaped session id neither hangs nor breaks selection" ;;
    *) t_fail "option-shaped session id" "selection produced: $(head -c 120 "$WORK/hang.out")" ;;
  esac
fi
exec 9>&-
rm -f "$FIFO" "$ACC"/acct-0*/.sessions-index "$ACC"/acct-0*/.client-scan
rm -rf "$ACC"/acct-0*/sessions

# a SHARED session registry proves nothing about which account ran what
rm -f "$ACC"/acct-0*/.limited "$ACC"/acct-0*/.sessions-index "$ACC"/acct-0*/.client-scan
rm -rf "$ACC"/acct-0*/projects "$ACC"/acct-0*/sessions
SHAREDS="$WORK/cl-shared-sessions"
mkdir -p "$SHAREDS"
printf '{"pid":1,"sessionId":"%s","cwd":"/proj","kind":"interactive","startedAt":%s000}\n' "$SID1" "$(date -u +%s)" > "$SHAREDS/1.json"
for i in 01 02; do ln -s "$SHAREDS" "$ACC/acct-$i/sessions"; done
mkclientlimit "$ACC/acct-01" "$SID1" "$(( $(date +%s) + 1800 ))"
rm -f "$ACC/acct-01/.sessions-index"      # only the shared registry could supply the id
claude >/dev/null 2>&1
{ [ ! -f "$ACC/acct-01/.limited" ] && [ ! -f "$ACC/acct-02/.limited" ]; } \
  && t_ok "a shared session registry never marks an account (fail open)" \
  || t_fail "shared session registry" "a shared registry marked an account LIMITED"
rm -f "$ACC"/acct-0*/sessions "$ACC"/acct-0*/.limited "$ACC"/acct-0*/.sessions-index "$ACC"/acct-0*/.client-scan
rm -rf "$ACC"/acct-0*/projects

# ---- 12k. codex-review: rotation must not serialise a parallel burst ---------------
# The first cut of this used a monotonic counter and picked the account with the OLDEST
# stamp. Every member of a concurrent burst reads the same stamps, computes the same
# "oldest", and piles onto one account. Rotation only ever drops the ONE account just
# handed out; everything else is still sampled at random.
rm -f "$ACC/.last-pick" "$ACC"/acct-0*/limits.json "$ACC"/acct-0*/.limited
# THREE accounts on purpose. Rotation drops the one just handed out, so in a two-account
# pool a burst legitimately lands entirely on the single alternative and the test would
# be measuring nothing (it flaked exactly that way on Linux). With three, the burst must
# spread over the two that remain — which is precisely what the counter version could not.
mkdir -p "$ACC/acct-09"
printf '{"claudeAiOauth":{"accessToken":"sk-ant-oat01-cl09","refreshToken":"r","expiresAt":9999999999999,"refreshTokenExpiresAt":9999999999999}}' > "$ACC/acct-09/.credentials.json"
: > "$WORK/burst.out"
for _ in $(seq 1 16); do ( claude >> "$WORK/burst.out" 2>&1 ) & done
wait
b_distinct="$(grep -o 'CFG=acct-[0-9]*' "$WORK/burst.out" | sort -u | tr '\n' ' ')"
b_n="$(printf '%s' "$b_distinct" | wc -w | tr -d ' ')"
[ "$b_n" -ge 2 ] \
  && t_ok "a parallel burst still spreads across the pool ($b_distinct)" \
  || t_fail "burst spreading" "all 16 concurrent runs took $b_distinct — rotation serialised the burst"
rm -rf "$ACC/acct-09"

# ---- 12l. codex-review: an unwritable pool root never prints to stderr --------------
# `cmd > file 2>/dev/null` does NOT silence a failed redirect: bash applies the
# redirections in order, so the open fails while stderr is still the caller's.
RO="$WORK/readonly-pool"
mkdir -p "$RO/acct-01" "$RO/acct-02"
cp "$ACC/accounts.json" "$RO/accounts.json"
for i in 01 02; do
  printf '{"claudeAiOauth":{"accessToken":"sk-ant-oat01-ro","refreshToken":"r","expiresAt":9999999999999,"refreshTokenExpiresAt":9999999999999}}' > "$RO/acct-$i/.credentials.json"
done
: > "$RO/.limits-kick"
chmod 555 "$RO" "$RO/acct-01" "$RO/acct-02"
err="$(CLAUDE_ACCOUNTS_ROOT="$RO" claude 2>&1 >/dev/null)"
out="$(CLAUDE_ACCOUNTS_ROOT="$RO" claude 2>/dev/null)"
chmod 755 "$RO" "$RO/acct-01" "$RO/acct-02"
[ -z "$err" ] && t_ok "a read-only pool root keeps stderr byte-clean" \
  || t_fail "read-only pool root" "stderr: $(printf '%s' "$err" | head -c 200)"
case "$out" in *CFG=acct-0*) t_ok "a read-only pool root still selects ($out)" ;;
  *) t_fail "read-only pool root" "selection produced: $out" ;; esac

ACC="$ACC_SAVED"
unset CLAUDE_ACCOUNTS_ROOT

# ---- 12e. TTY stdin: retry disabled so the terminal is never swapped for /dev/null --
if command -v script >/dev/null 2>&1; then
  # `claude -p` on a TTY with no prompt arg reads the terminal. Under a PTY the shim
  # must take the plain exec path (stdin inherited), never the buffered retry path.
  if [ "$(uname -s)" = "Darwin" ]; then
    ptyout="$(script -q /dev/null env PATH="$PATH" CLAUDE_ACCOUNTS_DIR="$ACC" FAKE_CTL="$FAKE_CTL" claude -p --echo-stdin <<'PTYIN' 2>/dev/null
tty-typed-prompt
PTYIN
)"
  else
    ptyout="$(script -qec "claude -p --echo-stdin" /dev/null <<'PTYIN' 2>/dev/null
tty-typed-prompt
PTYIN
)"
  fi
  case "$ptyout" in
    *tty-typed-prompt*) t_ok "TTY stdin reaches claude (retry path does not eat it)" ;;
    *) t_fail "TTY stdin" "terminal input was lost: $(printf '%s' "$ptyout" | head -c 80)" ;;
  esac
else
  t_ok "TTY stdin test skipped (no script(1))"
fi

# ---- 13. stdin/stdout byte fidelity through retry path -----------------------
printf 'line1\nline2 with spaces\n' > "$WORK/stdin13"
out="$(claude -p --echo-stdin < "$WORK/stdin13" 2>/dev/null)"
expected="$(cat "$WORK/stdin13")"
[ "$out" = "$expected" ] && t_ok "stdin/stdout byte fidelity (-p pipe)" || t_fail "stdin fidelity" "got: $out"

# ---- 14. selection log written ------------------------------------------------
grep -qE '^[0-9]{4}-[0-9]{2}-[0-9]{2}T.*acct-0[123] weekly=[0-9?]+% session=[0-9?]+% pwd=' "$ACC/selection.log" \
  && t_ok "selection.log format" || t_fail "selection.log format" "no matching lines"
grep -qE 'sk-ant-oat|accessToken|refreshToken' "$ACC/selection.log" \
  && t_fail "selection.log has no secrets" "a token leaked into the log" \
  || t_ok "selection.log leaks no secrets"

# ---- 15. CLI: list / import / remove ------------------------------------------
out="$(claude-accounts list 2>&1)"
check "list shows accounts" "acct-01" "$out"
printf '{"claudeAiOauth":{"accessToken":"sk-ant-oat01-t4","refreshToken":"r","expiresAt":9999999999999,"refreshTokenExpiresAt":9999999999999}}' > "$WORK/import-creds.json"
out="$(claude-accounts import c@test --id acct-04 --creds "$WORK/import-creds.json" --mode copy --no-sync 2>&1)"
check "import account" "Imported acct-04" "$out"
[ -f "$ACC/acct-04/.credentials.json" ] && t_ok "import copied credentials" || t_fail "import copied credentials" "file missing"
out="$(claude-accounts list 2>&1)"
check "imported account listed" "c@test" "$out"
out="$(claude-accounts remove acct-04 --yes 2>&1)"
check "remove account" "Removed acct-04" "$out"
[ ! -d "$ACC/acct-04" ] && t_ok "remove deleted dir" || t_fail "remove deleted dir" "dir still there"

# ---- 15b. duplicate-email guards ------------------------------------------------
# add of a named, already-present email => graceful SKIP before any sign-in (exit 0)
out="$(claude-accounts add a@test 2>&1)"
rc=$?
check "add skips a duplicate email with a message" "already added as acct-01 — skipping" "$out"
[ "$rc" = "0" ] && t_ok "duplicate add exits 0 (graceful skip)" || t_fail "duplicate add rc" "rc=$rc"
[ ! -d "$ACC/acct-04" ] && t_ok "duplicate add created nothing" || t_fail "duplicate add" "dir created"
# import stays strict (it's the lower-level command): refuses a duplicate
out="$(claude-accounts import a@test --id acct-09 --no-sync 2>&1)"
rc=$?
check "import refuses duplicate email" "already registered as acct-01" "$out"
[ ! -d "$ACC/acct-09" ] && t_ok "duplicate import created nothing" || t_fail "duplicate import" "dir created"
out="$(claude-accounts import a@test --id acct-01 --no-sync 2>&1)"
check "import same-id update allowed" "Imported acct-01" "$out"

# ---- 15c. login-first add (DEFAULT full-login flow): registers after verified auth --
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=new@test claude-accounts add new@test 2>&1)"
check "add registers after verified login" "Registered acct-04 for new@test" "$out"
[ -f "$ACC/acct-04/.credentials.json" ] && t_ok "default add writes .credentials.json (full login)" || t_fail "add creds" "missing"
[ ! -f "$ACC/acct-04/server.token" ] && t_ok "default add does NOT mint a setup-token" || t_fail "add token" "unexpected server.token"
out="$(CLAUDE_ACCOUNT=acct-04 claude 2>&1)"
check "new account usable via pin (oauth creds)" "CFG=acct-04" "$out"
claude-accounts remove acct-04 --yes >/dev/null 2>&1

# ---- 15c1. add --token: portable setup-token instead of creds ------------------------
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=tok@test claude-accounts add tok@test --token 2>&1)"
check "add --token registers via portable token" "Registered acct-04 for tok@test" "$out"
check "add --token says the identity cannot be verified" "a setup token carries no identity" "$out"
case "$out" in
  *"sign-in verified"*) t_fail "add --token must not claim a verified sign-in" "it says 'sign-in verified' for an identity nothing can read" ;;
  *) t_ok "add --token does not claim a verified sign-in" ;;
esac
case "$(grep 'add acct-04 tok@test' "$ACC/ops.log" 2>/dev/null)" in
  *auth-verified*) t_fail "ops.log must not record --token as auth-verified" "the audit trail would relaunder the assumption" ;;
  *"identity unverifiable"*) t_ok "ops.log records the --token add as unverifiable" ;;
  *) t_fail "ops.log line for the --token add" "not found" ;;
esac
[ -s "$ACC/acct-04/server.token" ] && t_ok "add --token writes server.token" || t_fail "add --token" "missing"
out="$(CLAUDE_ACCOUNT=acct-04 claude 2>&1)"
check "token account exports CLAUDE_CODE_OAUTH_TOKEN" "TOK=sk-ant-oat01-FAKE" "$out"
claude-accounts remove acct-04 --yes >/dev/null 2>&1

# ---- 15c0. add with NO email: derives it from the verified sign-in -------------------
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=derived@test claude-accounts add 2>&1)"
check "add without email registers the signed-in address" "Registered acct-04 for derived@test" "$out"
claude-accounts list 2>&1 | grep -q "derived@test" && t_ok "email-less add lands in the manifest" \
  || t_fail "email-less add" "derived@test not registered"
claude-accounts remove acct-04 --yes >/dev/null 2>&1
# add with no email, but the signed-in email is already registered => refuse + clean up
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=a@test claude-accounts add 2>&1)"
rc=$?
check "email-less add skips a duplicate signed-in email" "a@test is already added as acct-01 — skipping" "$out"
[ "$rc" = "0" ] && t_ok "email-less duplicate skip exits 0" || t_fail "email-less dup rc" "rc=$rc"
[ ! -d "$ACC/acct-04" ] && t_ok "email-less duplicate cleaned up" || t_fail "email-less dup cleanup" "dir left"
# add with no email but identity can't be read back => refuse (can't label the account)
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_AUTH_FAIL=1 claude-accounts add 2>&1)"
rc=$?
check "email-less add needs a readable identity" "identity could not be read back" "$out"
[ ! -d "$ACC/acct-04" ] && t_ok "unverifiable email-less add cleaned up" || t_fail "unverifiable cleanup" "dir left"

# ---- 15c2. --tui is accepted as a no-op alias (full login is the default now) --------
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=tui@test claude-accounts add tui@test --tui 2>&1)"
check "add --tui still works (alias for default login)" "Registered acct-04 for tui@test" "$out"
[ -f "$ACC/acct-04/.credentials.json" ] && t_ok "--tui writes creds like the default" || t_fail "tui creds" "missing"
claude-accounts remove acct-04 --yes >/dev/null 2>&1

# ---- 15d. aborted login leaves zero traces ------------------------------------------
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_LOGIN_FAIL=1 claude-accounts add ghost@test 2>&1)"
rc=$?
check "aborted login detected" "login failed or aborted" "$out"
[ "$rc" != "0" ] && t_ok "aborted add exits nonzero" || t_fail "aborted add rc" "rc=0"
[ ! -d "$ACC/acct-04" ] && t_ok "aborted add cleaned up its dir" || t_fail "aborted add cleanup" "dir left behind"
claude-accounts list 2>&1 | grep -q ghost@test && t_fail "aborted add not in manifest" "ghost@test registered" || t_ok "aborted add not in manifest"
# aborted --token path too
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_TOKEN_FAIL=1 claude-accounts add ghost2@test --token 2>&1)"
rc=$?
check "aborted --token add detected" "sign-in failed or aborted" "$out"
[ ! -d "$ACC/acct-04" ] && t_ok "aborted --token add cleaned up" || t_fail "aborted token cleanup" "dir left"

# ---- 15e. sign-in as an already-registered email is rejected + cleaned ---------------
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=a@test claude-accounts add brand@test 2>&1)"
rc=$?
check "wrong-account sign-in skips (already added)" "a@test is already added as acct-01 — skipping" "$out"
[ "$rc" = "0" ] && t_ok "wrong-account skip exits 0" || t_fail "wrong-account rc" "rc=$rc"
[ ! -d "$ACC/acct-04" ] && t_ok "wrong-account sign-in cleaned up" || t_fail "wrong-account cleanup" "dir left behind"

# ---- 15f. login command completes auth for an existing auth-less account -------------
claude-accounts import pending@test --id acct-08 --no-sync >/dev/null 2>&1
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=pending@test claude-accounts login acct-08 2>&1)"
check "login completes existing account (full login)" "acct-08 login saved" "$out"
[ -f "$ACC/acct-08/.credentials.json" ] && t_ok "login writes .credentials.json" || t_fail "login creds" "missing"
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=other@test claude-accounts login acct-08 2>&1)"
rc=$?
check "login email mismatch refused" "nothing saved" "$out"
[ "$rc" != "0" ] && t_ok "mismatched login exits nonzero" || t_fail "mismatched login rc" "rc=0"
# login --token writes a portable token for the same account
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=pending@test claude-accounts login acct-08 --token 2>&1)"
check "login --token saves a portable token" "acct-08 token saved" "$out"
[ -s "$ACC/acct-08/server.token" ] && t_ok "login --token writes server.token" || t_fail "login token" "missing"
claude-accounts remove acct-08 --yes >/dev/null 2>&1

# ---- 15f2. expired: the re-login worklist, and relogin fixes it ----------------------
# `expired` must agree with the shim exactly: what it lists is what selection skips.
printf '{"claudeAiOauth":{"accessToken":"sk-ant-oat01-dead","refreshToken":"r","expiresAt":1000000000000,"refreshTokenExpiresAt":1000000000000}}' > "$ACC/acct-01/.credentials.json"
out="$(claude-accounts expired 2>&1)"
rc=$?
check "expired lists the dead account" "acct-01" "$out"
check "expired explains why" "refresh token expired" "$out"
check "expired prints the fix" "claude-accounts relogin acct-01" "$out"
[ "$rc" = "1" ] && t_ok "expired exits 1 when a login needs a human" || t_fail "expired rc" "rc=$rc"
out="$(claude-accounts expired --quiet 2>&1)"
[ "$out" = "acct-01" ] && t_ok "expired --quiet prints bare ids" || t_fail "expired --quiet" "got: $out"
out="$(claude-accounts list 2>&1)"
check "list flags the dead login" "EXPIRED-LOGIN" "$out"
out="$(claude-accounts status 2>&1)"
check "status marks it unselectable" "LOGIN EXPIRED" "$out"
# relogin with no arguments targets exactly that worklist
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=a@test claude-accounts relogin --yes 2>&1)"
rc=$?
check "relogin re-authenticates the expired account" "acct-01 login saved" "$out"
check "relogin reports what it did" "re-authenticated 1 of 1" "$out"
[ "$rc" = "0" ] && t_ok "relogin exits 0 when every account recovered" || t_fail "relogin rc" "rc=$rc"
out="$(claude-accounts expired 2>&1)"
rc=$?
check "expired is clean after relogin" "can authenticate" "$out"
[ "$rc" = "0" ] && t_ok "expired exits 0 on a healthy pool" || t_fail "expired rc (healthy)" "rc=$rc"
out="$(claude 2>&1)"
case "$out" in *CFG=acct-0*) t_ok "re-authenticated account is selectable again" ;;
  *) t_fail "post-relogin selection" "got: $out" ;; esac
# a stale .expired marker must not survive a successful login
printf '%s\nreason=auth-error detail=test\n' "$now" > "$ACC/acct-01/.expired"
touch -t 202001010101 "$ACC/acct-01/.credentials.json"
CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=a@test claude-accounts relogin acct-01 --yes >/dev/null 2>&1
[ ! -f "$ACC/acct-01/.expired" ] && t_ok "login clears the dead-auth marker" \
  || t_fail "login marker clear" ".expired survived a successful login"
# explicit ids are validated
out="$(claude-accounts relogin acct-99 --yes 2>&1)"
rc=$?
check "relogin rejects an unknown account" "unknown account" "$out"
[ "$rc" != "0" ] && t_ok "unknown relogin target exits nonzero" || t_fail "relogin unknown rc" "rc=0"

# ---- 15f3. a re-login that did NOT work must not be reported as fixed -----------------
# The target already HAS a (dead) .credentials.json, so "the file exists" proves nothing:
# an aborted sign-in would otherwise clear the dead-auth marker and hand the account back.
printf '{"claudeAiOauth":{"accessToken":"sk-ant-oat01-dead","refreshToken":"r","expiresAt":1000000000000,"refreshTokenExpiresAt":1000000000000}}' > "$ACC/acct-01/.credentials.json"
printf '%s\nreason=refresh-token-expired detail=test\n' "$now" > "$ACC/acct-01/.expired"
touch -t 202001010101 "$ACC/acct-01/.credentials.json"
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_LOGIN_FAIL=1 FAKE_EMAIL=a@test claude-accounts relogin acct-01 --yes 2>&1)"
rc=$?
check "aborted re-login is reported as a failure" "login failed or aborted" "$out"
[ "$rc" != "0" ] && t_ok "aborted relogin exits nonzero" || t_fail "aborted relogin rc" "rc=0"
[ -f "$ACC/acct-01/.expired" ] && t_ok "aborted re-login leaves the account parked" \
  || t_fail "aborted relogin" "the dead-auth marker was cleared by a failed sign-in"
out="$(claude-accounts expired --quiet 2>&1)"
check "the account is still on the worklist after a failed re-login" "acct-01" "$out"
# a real re-login then fixes it
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=a@test claude-accounts relogin acct-01 --yes 2>&1)"
check "a working re-login is reported as fixed" "re-authenticated 1 of 1" "$out"
[ ! -f "$ACC/acct-01/.expired" ] && t_ok "a working re-login clears the park" \
  || t_fail "relogin clear" "marker survived a successful sign-in"

# ---- 15f4. audit robustness: bad data must never read as a healthy pool ---------------
# a non-numeric expiry must not crash the audit (it would blank the worklist silently)
cp "$ACC/acct-01/.credentials.json" "$WORK/creds.bak"
printf '{"claudeAiOauth":{"accessToken":"a","refreshToken":"r","expiresAt":"soon","refreshTokenExpiresAt":[]}}' > "$ACC/acct-01/.credentials.json"
out="$(claude-accounts expired 2>&1)"
rc=$?
case "$out" in *Traceback*) t_fail "audit survives a weird credential" "python traceback" ;;
  *) t_ok "a non-numeric expiry does not crash the audit" ;; esac
out="$(claude-accounts list 2>&1)"
check "list survives a weird credential" "acct-01" "$out"
# a truncated (mid-write) credential is judged the SAME way the shim judges it
printf '{"claudeAiOauth":{"accessToken":"live","refreshToken":"rt","expiresAt":9999999999999,' > "$ACC/acct-01/.credentials.json"
out="$(claude-accounts expired --quiet 2>&1)"
case "$out" in *acct-01*) t_fail "audit agrees with the shim on a truncated credential" \
    "audit calls it dead while the shim still selects it" ;;
  *) t_ok "a truncated credential is judged like the shim judges it" ;; esac
cp "$WORK/creds.bak" "$ACC/acct-01/.credentials.json"

# ---- 15g. parallel adds (different terminals) get distinct ids, no lock-busy error ----
( CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=par1@test claude-accounts add >"$WORK/p1.out" 2>&1 ) &
pA=$!
( CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=par2@test claude-accounts add >"$WORK/p2.out" 2>&1 ) &
pB=$!
wait "$pA"; wait "$pB"
both="$(cat "$WORK/p1.out" "$WORK/p2.out" 2>/dev/null)"
printf '%s' "$both" | grep -q "in progress" \
  && t_fail "parallel add: no lock-busy error" "got the 'another add in progress' error" \
  || t_ok "parallel add: neither reports 'another add in progress'"
regA="$(claude-accounts list 2>&1 | grep par1@test | awk '{print $1}')"
regB="$(claude-accounts list 2>&1 | grep par2@test | awk '{print $1}')"
{ [ -n "$regA" ] && [ -n "$regB" ] && [ "$regA" != "$regB" ]; } \
  && t_ok "parallel add: both registered on distinct ids ($regA, $regB)" \
  || t_fail "parallel add ids" "regA=$regA regB=$regB"
# and no duplicate/second entry crept in for either email
{ [ "$(claude-accounts list 2>&1 | grep -c par1@test)" = "1" ] && [ "$(claude-accounts list 2>&1 | grep -c par2@test)" = "1" ]; } \
  && t_ok "parallel add: exactly one entry per email" || t_fail "parallel add dup" "duplicate created"
[ -n "$regA" ] && claude-accounts remove "$regA" --yes >/dev/null 2>&1
[ -n "$regB" ] && claude-accounts remove "$regB" --yes >/dev/null 2>&1

# ---- 15h. two parallel adds signing into the SAME email: exactly one wins ------------
( CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=same@test claude-accounts add >"$WORK/s1.out" 2>&1 ) &
sA=$!
( CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=same@test claude-accounts add >"$WORK/s2.out" 2>&1 ) &
sB=$!
wait "$sA"; wait "$sB"
n="$(claude-accounts list 2>&1 | grep -c same@test)"
[ "$n" = "1" ] && t_ok "same-email parallel add: exactly one registered (other skipped)" \
  || t_fail "same-email parallel add" "count=$n (expected 1)"
cat "$WORK/s1.out" "$WORK/s2.out" 2>/dev/null | grep -q "already added as\|skipping" \
  && t_ok "same-email parallel add: loser skipped gracefully" || t_fail "same-email skip msg" "no skip message"
regS="$(claude-accounts list 2>&1 | grep same@test | awk '{print $1}')"
[ -n "$regS" ] && claude-accounts remove "$regS" --yes >/dev/null 2>&1

# ---- 15i. dedupe removes accounts registered twice (keeps one per email) --------------
out="$(claude-accounts dedupe 2>&1)"
check "dedupe on a clean pool is a no-op" "No duplicate accounts" "$out"
# manufacture a duplicate directly in the manifest (a pre-fix leftover) + a dir for it
claude-accounts import twin@test --id acct-06 --creds "$WORK/import-creds.json" --mode copy --no-sync >/dev/null 2>&1
python3 - "$ACC/accounts.json" <<'EOF'
import json, os, sys
d = json.load(open(sys.argv[1]))
d['accounts'].append({'id': 'acct-07', 'email': 'twin@test', 'home': 'mac'})
json.dump(d, open(sys.argv[1] + '.tmp', 'w'), indent=2); os.replace(sys.argv[1] + '.tmp', sys.argv[1])
EOF
mkdir -p "$ACC/acct-07"
out="$(claude-accounts list 2>&1)"
check "list warns about a duplicate email" "twin@test is registered 2x" "$out"
out="$(claude-accounts dedupe --yes 2>&1)"
check "dedupe reports removal" "Removed 1 duplicate" "$out"
[ "$(claude-accounts list 2>&1 | grep -c twin@test)" = "1" ] && t_ok "dedupe keeps exactly one per email" || t_fail "dedupe count" "not 1"
# it kept the authed one (acct-06 has creds), removed the bare acct-07
claude-accounts list 2>&1 | grep -q "acct-06.*twin@test" && t_ok "dedupe kept the authenticated account" || t_fail "dedupe keep-authed" "kept the wrong one"
claude-accounts remove acct-06 --yes >/dev/null 2>&1

# ---- 16. CLI: limits marking via fixture endpoint ------------------------------
cat > "$WORK/usage-high.json" <<'EOF'
{"limits":[
  {"kind":"session","percent":29,"resets_at":"2099-01-01T00:00:00+00:00","scope":null},
  {"kind":"weekly_all","percent":55,"resets_at":"2099-01-02T00:00:00+00:00","scope":null},
  {"kind":"weekly_scoped","percent":93,"resets_at":"2099-01-03T00:00:00+00:00","scope":{"model":{"display_name":"Fable"}}}
]}
EOF
out="$(CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-high.json" claude-accounts limits 2>&1)"
check "limits marks Fable bucket >=90%" "LIMITED weekly_scoped:Fable at 93%" "$out"
[ -f "$ACC/acct-01/.limited" ] && t_ok ".limited written by limits" || t_fail ".limited written" "missing"
grep -q "weekly_scoped:Fable" "$ACC/acct-01/limits.json" \
  && t_ok "limits.json has Fable bucket" || t_fail "limits.json Fable bucket" "missing"
out="$(CLAUDE_ACCOUNT='' claude 2>&1)"  # pool should now avoid marked accounts (all marked -> fallback fine)
cat > "$WORK/usage-low.json" <<'EOF'
{"limits":[
  {"kind":"session","percent":10,"resets_at":"2099-01-01T00:00:00+00:00","scope":null},
  {"kind":"weekly_scoped","percent":45,"resets_at":"2099-01-03T00:00:00+00:00","scope":{"model":{"display_name":"Fable"}}}
]}
EOF
out="$(CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-low.json" claude-accounts limits 2>&1)"
check "limits clears marker under threshold" "marker cleared" "$out"
[ ! -f "$ACC/acct-01/.limited" ] && t_ok "marker removed after clear" || t_fail "marker removed" "still present"

# ---- 16-weekly. limits classifies session vs weekly and records both signals --------
cat > "$WORK/usage-3bucket.json" <<'EOF'
{"limits":[
  {"kind":"session","group":"session","percent":88,"resets_at":"2099-01-01T00:00:00+00:00","scope":null},
  {"kind":"weekly_all","group":"weekly","percent":40,"resets_at":"2099-01-05T00:00:00+00:00","scope":null},
  {"kind":"weekly_scoped","group":"weekly","percent":55,"resets_at":"2099-01-05T00:00:00+00:00","scope":{"model":{"display_name":"Fable"}}}
]}
EOF
CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-3bucket.json" claude-accounts limits --quiet
python3 - "$ACC/acct-01/limits.json" <<'EOF'
import json, sys
d = json.load(open(sys.argv[1]))
assert d["session_percent"] == 88, d
assert d["weekly_percent"] == 55, d          # max of the two weekly buckets, not session
assert d["max_percent"] == 88, d             # peak of ALL buckets (drives exclusion)
groups = {b["name"]: b["group"] for b in d["buckets"]}
assert groups["session"] == "session", groups
assert groups["weekly_all"] == "weekly", groups
assert groups["weekly_scoped:Fable"] == "weekly", groups
EOF
[ $? -eq 0 ] && t_ok "limits records weekly_percent(55) + session_percent(88) with correct groups" \
  || t_fail "weekly/session classification" "see limits.json"
# a session bucket at 88 must NOT poison weekly ranking: score uses weekly(55), not 88
sc="$(python3 -c "import json;d=json.load(open('$ACC/acct-01/limits.json'));print(d['weekly_percent']*1000+d['session_percent'])")"
[ "$sc" = "55088" ] && t_ok "ranking score weighs weekly over session (55088)" \
  || t_fail "ranking score" "got $sc"
rm -f "$ACC/acct-01/limits.json" "$ACC/acct-01/.limited"

# ---- 16a. future-proofing: works if the Fable bucket separation disappears ---------
cat > "$WORK/usage-no-fable.json" <<'EOF'
{"limits":[
  {"kind":"session","percent":20,"resets_at":"2099-01-01T00:00:00+00:00","scope":null},
  {"kind":"weekly_all","percent":95,"resets_at":"2099-01-02T00:00:00+00:00","scope":null}
]}
EOF
out="$(CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-no-fable.json" claude-accounts limits 2>&1)"
check "no-Fable payload still marks on weekly_all" "LIMITED weekly_all at 95%" "$out"
grep -q "weekly_scoped" "$ACC/acct-01/limits.json" && t_fail "no stale Fable bucket" "old bucket kept" || t_ok "buckets reflect current payload only"

# ---- 16a2. legacy payload (no limits[] at all) falls back to five_hour/seven_day ----
cat > "$WORK/usage-legacy.json" <<'EOF'
{"five_hour":{"utilization":12.0,"resets_at":"2099-01-01T00:00:00+00:00"},
 "seven_day":{"utilization":34.0,"resets_at":"2099-01-02T00:00:00+00:00"}}
EOF
out="$(CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-legacy.json" claude-accounts limits 2>&1)"
check "legacy payload parsed via fallback" "five_hour=12%" "$out"
check "legacy marker cleared under threshold" "acct-01: ok" "$out"

# ---- 16a3. malformed/garbage payload entries never crash the refresher --------------
cat > "$WORK/usage-garbage.json" <<'EOF'
{"limits":[
  {"kind":"session","percent":"NaNsense","scope":{"model":"stringnotdict"}},
  "not-even-a-dict",
  {"percent":41,"scope":{"model":{"display_name":null,"id":"claude-fable-5"}}},
  {"kind":"weekly_all","percent":null}
]}
EOF
out="$(CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-garbage.json" claude-accounts limits 2>&1)"
rc=$?
[ "$rc" = "0" ] && t_ok "garbage payload exits 0 (fail open)" || t_fail "garbage payload rc" "rc=$rc: $out"
check "parseable entry survives garbage siblings" "unknown:claude-fable-5=41%" "$out"

# ---- 16a4. fetch throttle: fresh data is not re-fetched -----------------------------
CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-low.json" claude-accounts limits --quiet
before="$(python3 -c "import json; print(json.load(open('$ACC/acct-01/limits.json'))['fetched_at'])")"
mv "$WORK/usage-low.json" "$WORK/usage-low.hidden"   # a real fetch would now fail loudly
out="$(CLAUDE_MULTIACC_MIN_FETCH=45 CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-low.json" claude-accounts limits 2>&1)"
after="$(python3 -c "import json; print(json.load(open('$ACC/acct-01/limits.json'))['fetched_at'])")"
{ [ "$before" = "$after" ] && ! printf '%s' "$out" | grep -q "fetch failed"; } \
  && t_ok "fresh data skips re-fetch (rate-limit protection)" \
  || t_fail "fetch throttle" "re-fetched despite fresh data: $out"
mv "$WORK/usage-low.hidden" "$WORK/usage-low.json"
out="$(CLAUDE_MULTIACC_MIN_FETCH=45 CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-low.json" claude-accounts limits --force 2>&1)"
check "--force bypasses the throttle" "acct-01: ok" "$out"

# ---- 16a5. 429 sets backoff, is honored, and clears on success -----------------------
python3 - "$ACC/acct-01/limits.json" <<'EOF'
import json, os, sys, time
p = sys.argv[1]
d = json.load(open(p))
d['fetched_at'] = 0                        # stale enough to fetch
d['retry_after'] = int(time.time()) + 600  # but a 429 backoff is in force
d['backoff'] = 600
json.dump(d, open(p + '.tmp', 'w'), indent=1); os.replace(p + '.tmp', p)
EOF
out="$(CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-low.json" claude-accounts limits 2>&1)"
# A backoff with no recorded cause (an older build wrote these) still reports honestly
# rather than blaming a 429 it never saw.
check "backoff honored" "acct-01: backing off after a failed fetch" "$out"
# ...and when the cause IS on record, the skip message names it. Calling every park a
# 429 is exactly how an unauthorized account read as merely rate-limited for 11 days.
python3 - "$ACC/acct-01/limits.json" <<'EOF'
import json, os, sys, time
p = sys.argv[1]
d = json.load(open(p))
d['last_error'] = 'HTTP 403 (source=token) — permanent, server said do not retry'
json.dump(d, open(p + '.tmp', 'w'), indent=1); os.replace(p + '.tmp', p)
EOF
out="$(CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-low.json" claude-accounts limits 2>&1)"
check "a skipped account names the error it is backing off from" "backing off after HTTP 403" "$out"
out="$(CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-low.json" claude-accounts limits --force 2>&1)"
check "--force overrides backoff" "acct-01: ok" "$out"
python3 -c "
import json, sys
d = json.load(open('$ACC/acct-01/limits.json'))
sys.exit(0 if 'retry_after' not in d and 'backoff' not in d else 1)" \
  && t_ok "successful fetch clears backoff state" || t_fail "backoff cleared" "retry_after/backoff persisted"

# ---- 16b. expired-bearer account: oauth refresh attempted; fail-open when it fails ----
mkdir -p "$ACC/acct-05"
printf '{"claudeAiOauth":{"accessToken":"sk-ant-oat01-old","refreshToken":"sk-ant-ort01-oldrefresh","expiresAt":1000,"refreshTokenExpiresAt":9999999999999}}' > "$ACC/acct-05/.credentials.json"
claude-accounts import e@test --id acct-05 --no-sync >/dev/null 2>&1
out="$(CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-low.json" claude-accounts limits 2>&1)"
rc=$?
check "failed oauth refresh logged with backoff" "acct-05: oauth refresh failed" "$out"
check "expired bearer logged, not fatal" "acct-05: no fresh bearer" "$out"
[ "$rc" = "0" ] && t_ok "limits exits 0 with expired-bearer account" || t_fail "limits exit code" "rc=$rc"
[ ! -f "$ACC/acct-05/limits.json" ] && t_ok "no limits.json fabricated for expired account" || t_fail "expired acct limits.json" "unexpectedly written"
[ -f "$ACC/acct-05/.oauth-refresh.json" ] && t_ok "refresh failure recorded in .oauth-refresh.json" || t_fail "refresh backoff file" "missing"

# ---- 16b2. refresh backoff honored: even a now-working endpoint is not retried early --
cat > "$WORK/token-ok.json" <<'EOF'
{"access_token":"sk-ant-oat01-refreshednew","refresh_token":"sk-ant-ort01-rotatednew","expires_in":28800,"refresh_token_expires_in":2592000}
EOF
out="$(CLAUDE_MULTIACC_TOKEN_URL="file://$WORK/token-ok.json" CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-low.json" claude-accounts limits 2>&1)"
check "refresh backoff honored (no early retry)" "acct-05: no fresh bearer" "$out"
grep -q "sk-ant-oat01-refreshednew" "$ACC/acct-05/.credentials.json" \
  && t_fail "backoff prevented refresh" "credentials rewritten inside the backoff window" \
  || t_ok "no refresh inside the backoff window"

# ---- 16b3. --force bypasses refresh backoff: rotated credential persisted + fetch ok --
out="$(CLAUDE_MULTIACC_TOKEN_URL="file://$WORK/token-ok.json" CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-low.json" claude-accounts limits --force 2>&1)"
check "--force refreshes the expired oauth token" "acct-05: oauth access token refreshed" "$out"
check "refreshed account fetches telemetry" "acct-05: ok" "$out"
python3 - "$ACC/acct-05/.credentials.json" <<'EOF'
import json, os, stat, sys, time
p = sys.argv[1]
o = json.load(open(p))['claudeAiOauth']
assert o['accessToken'] == 'sk-ant-oat01-refreshednew', o['accessToken']
assert o['refreshToken'] == 'sk-ant-ort01-rotatednew', 'refresh token was not rotated'
assert o['expiresAt'] / 1000.0 > time.time() + 3600, 'expiresAt not advanced'
assert o['refreshTokenExpiresAt'] / 1000.0 > time.time() + 86400, 'refreshTokenExpiresAt not advanced'
mode = stat.S_IMODE(os.stat(p).st_mode)
assert mode == 0o600, oct(mode)
EOF
[ $? -eq 0 ] && t_ok "rotated credential persisted with 0600" || t_fail "credential rotation" "see assertions above"
[ ! -f "$ACC/acct-05/.oauth-refresh.json" ] && t_ok "successful refresh clears the backoff file" || t_fail "refresh backoff clear" "file still present"
[ -f "$ACC/acct-05/limits.json" ] && t_ok "telemetry written right after refresh" || t_fail "limits.json after refresh" "missing"
grep -qE "sk-ant-ort01|sk-ant-oat01-refreshednew" "$ACC/limits.log" \
  && t_fail "limits.log leaks no tokens" "a token leaked into limits.log" \
  || t_ok "limits.log leaks no tokens"

# ---- 16b4. steady state: fresh data means no refresh and no fetch (quiet skip) --------
out="$(CLAUDE_MULTIACC_MIN_FETCH=45 CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-low.json" claude-accounts limits 2>&1)"
printf '%s' "$out" | grep -q "acct-05" \
  && t_fail "fresh account skipped silently" "unexpected acct-05 output: $out" \
  || t_ok "fresh account skipped silently (no refresh, no fetch)"

# ---- 16b5. an EXPIRED refresh token is never sent: clear re-login message -------------
printf '{"claudeAiOauth":{"accessToken":"sk-ant-oat01-old","refreshToken":"sk-ant-ort01-dead","expiresAt":1000,"refreshTokenExpiresAt":1000}}' > "$ACC/acct-05/.credentials.json"
rm -f "$ACC/acct-05/limits.json" "$ACC/acct-05/.oauth-refresh.json"
out="$(CLAUDE_MULTIACC_TOKEN_URL="file://$WORK/token-ok.json" CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-low.json" claude-accounts limits 2>&1)"
check "expired refresh token => re-login message" "re-login needed" "$out"
grep -q "sk-ant-oat01-refreshednew" "$ACC/acct-05/.credentials.json" \
  && t_fail "dead refresh token never used" "credentials rewritten from a dead refresh token" \
  || t_ok "dead refresh token never used"
# ...and the account is PARKED, so the shim stops handing work to a login that cannot work
grep -q "reason=refresh-token-expired" "$ACC/acct-05/.expired" 2>/dev/null \
  && t_ok "limits parks an account with a dead refresh token" \
  || t_fail "limits .expired marker" "no .expired written for a dead refresh token"
# a later successful fetch (fresh login, or a token bearer) unparks it
printf 'sk-ant-oat01-portable-unpark' > "$ACC/acct-05/server.token"
rm -f "$ACC/acct-05/limits.json"
out="$(CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-low.json" claude-accounts limits --force 2>&1)"
[ ! -f "$ACC/acct-05/.expired" ] && t_ok "a successful usage fetch clears the dead-auth marker" \
  || t_fail "unpark on success" ".expired survived a successful authenticated fetch"
# ...but an ORG-BLOCKED account authenticates fine — telemetry proves nothing about it,
# so its marker must survive a successful fetch (only a real call or re-login lifts it).
printf '%s\nreason=org-blocked marked_at=now detail=test\n' "$now" > "$ACC/acct-05/.expired"
rm -f "$ACC/acct-05/limits.json"
out="$(CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-low.json" claude-accounts limits --force 2>&1)"
[ -f "$ACC/acct-05/.expired" ] && t_ok "a usage fetch does not unpark an org-blocked account" \
  || t_fail "org-block unpark" "telemetry cleared an org block it cannot observe"
rm -f "$ACC/acct-05/.expired" "$ACC/acct-05/server.token"

# ---- 16b6. RECENTLY-expired token is left alone (a live session owns it) --------------
# The 5-min REFRESH_MIN_EXPIRED gate is the rotation-safety core: a token that expired
# moments ago may be mid-refresh by a live claude session; grants must not race it.
# Not even --force may bypass this.
recent_ms="$(python3 -c 'import time; print(int((time.time()-100)*1000))')"
printf '{"claudeAiOauth":{"accessToken":"sk-ant-oat01-recent","refreshToken":"sk-ant-ort01-live","expiresAt":%s,"refreshTokenExpiresAt":9999999999999}}' "$recent_ms" > "$ACC/acct-05/.credentials.json"
rm -f "$ACC/acct-05/limits.json" "$ACC/acct-05/.oauth-refresh.json"
out="$(CLAUDE_MULTIACC_TOKEN_URL="file://$WORK/token-ok.json" CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-low.json" claude-accounts limits --force 2>&1)"
check "recently-expired token is not refreshed (even --force)" "acct-05: no fresh bearer" "$out"
grep -q "sk-ant-oat01-recent" "$ACC/acct-05/.credentials.json" \
  && t_ok "recently-expired credential left untouched" \
  || t_fail "REFRESH_MIN_EXPIRED gate" "credential was rewritten within the 5-min grace window"
[ ! -f "$ACC/acct-05/.oauth-refresh.json" ] && t_ok "no backoff recorded for a gated (skipped) refresh" \
  || t_fail "gated refresh backoff" ".oauth-refresh.json written despite the gate"

# ---- 16b7. OAuth is preferred over a setup token FOR TELEMETRY ------------------------
# This used to be the other way round — a server.token short-circuited the oauth refresh,
# on the reasoning that a non-rotating credential is the safer one to spend. That
# reasoning inverted the moment we learned the usage endpoint refuses setup tokens
# outright (403, no user:profile scope): preferring the token means no telemetry AT ALL,
# for an account whose refresh grant was perfectly good. Order is now oauth > refresh
# grant > token, and the rotation-safety gate (16b6) still guards the grant itself.
printf '{"claudeAiOauth":{"accessToken":"sk-ant-oat01-old","refreshToken":"sk-ant-ort01-x","expiresAt":1000,"refreshTokenExpiresAt":9999999999999}}' > "$ACC/acct-05/.credentials.json"
printf 'sk-ant-oat01-portable-token-05' > "$ACC/acct-05/server.token"
rm -f "$ACC/acct-05/limits.json" "$ACC/acct-05/.oauth-refresh.json"
out="$(CLAUDE_MULTIACC_TOKEN_URL="file://$WORK/token-ok.json" CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-low.json" claude-accounts limits --force 2>&1)"
check "an account with both credentials still fetches" "acct-05: ok" "$out"
grep -q "sk-ant-oat01-refreshednew" "$ACC/acct-05/.credentials.json" \
  && t_ok "a usable refresh grant is used even when a server.token sits beside it" \
  || t_fail "oauth preferred for telemetry" "the setup token short-circuited the refresh grant"
python3 -c "import json,sys; sys.exit(0 if json.load(open('$ACC/acct-05/limits.json'))['source']=='oauth' else 1)" \
  && t_ok "telemetry is fetched with the OAuth bearer, not the setup token" \
  || t_fail "bearer source" "source != oauth"
# ...and the token is still the fallback when there is no oauth path at all.
rm -f "$ACC/acct-05/.credentials.json" "$ACC/acct-05/limits.json"
out="$(CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-low.json" claude-accounts limits --force 2>&1)"
python3 -c "import json,sys; sys.exit(0 if json.load(open('$ACC/acct-05/limits.json'))['source']=='token' else 1)" \
  && t_ok "with no oauth credential the setup token is still tried" \
  || t_fail "token fallback" "source != token"
rm -f "$ACC/acct-05/server.token"

# ---- 16b9. a 4xx from the TOKEN endpoint must not park the whole pool -----------------
# Every account hits the same endpoint with the same client id, so a provider incident,
# a WAF page or a client-id change 4xxs ALL of them at once. Only OAuth's own
# invalid_grant (or a repeated refusal of this one account) is proof of a dead grant.
srv_script="$WORK/token-server.py"
cat > "$srv_script" <<'EOF'
import http.server, json, sys, threading
class H(http.server.BaseHTTPRequestHandler):
    def log_message(self, *a): pass
    def _send(self, code, body):
        raw = body.encode()
        self.send_response(code)
        self.send_header('Content-Type', 'application/json')
        self.send_header('Content-Length', str(len(raw)))
        self.end_headers()
        self.wfile.write(raw)
    def do_POST(self):
        try:
            self.rfile.read(int(self.headers.get('Content-Length') or 0))
        except Exception:
            pass
        if self.path == '/invalid-grant':
            self._send(400, json.dumps({'error': 'invalid_grant'}))
        else:
            self._send(400, '<html>gateway says no</html>')
srv = http.server.HTTPServer(('127.0.0.1', 0), H)
print(srv.server_address[1], flush=True)
srv.serve_forever()
EOF
port=""
python3 "$srv_script" > "$WORK/token-port" 2>/dev/null &
srv_pid=$!
STUB_PIDS="$STUB_PIDS $srv_pid"
for _ in $(seq 1 20); do
  port="$(head -1 "$WORK/token-port" 2>/dev/null)"
  case "$port" in ''|*[!0-9]*) port=""; sleep 0.2 ;; *) break ;; esac
done
if [ -z "$port" ]; then
  kill "$srv_pid" 2>/dev/null; wait "$srv_pid" 2>/dev/null || true
  t_ok "token-endpoint 4xx tests skipped (cannot bind a loopback port here)"
else
  dead_acct="$ACC/acct-05"
  mk_stale_creds() {
    printf '{"claudeAiOauth":{"accessToken":"sk-ant-oat01-old","refreshToken":"sk-ant-ort01-x","expiresAt":1000,"refreshTokenExpiresAt":9999999999999}}' > "$dead_acct/.credentials.json"
    rm -f "$dead_acct/limits.json" "$dead_acct/.oauth-refresh.json" "$dead_acct/.expired"
  }
  # a NON-invalid_grant 400 (provider incident): back off, do NOT park
  mk_stale_creds
  out="$(CLAUDE_MULTIACC_TOKEN_URL="http://127.0.0.1:$port/boom" CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-low.json" claude-accounts limits --force 2>&1)"
  [ ! -f "$dead_acct/.expired" ] && t_ok "one opaque 4xx from the token endpoint does not park an account" \
    || t_fail "token 4xx park" "a single non-invalid_grant 400 parked the account"
  [ -f "$dead_acct/.oauth-refresh.json" ] && t_ok "an opaque 4xx still records a backoff" \
    || t_fail "token 4xx backoff" "no .oauth-refresh.json written"
  # ...but a repeatedly-refused account IS parked (3rd strike)
  CLAUDE_MULTIACC_TOKEN_URL="http://127.0.0.1:$port/boom" CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-low.json" claude-accounts limits --force >/dev/null 2>&1
  CLAUDE_MULTIACC_TOKEN_URL="http://127.0.0.1:$port/boom" CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-low.json" claude-accounts limits --force >/dev/null 2>&1
  [ -f "$dead_acct/.expired" ] && t_ok "a repeatedly-refused grant is parked on the third strike" \
    || t_fail "token 4xx strikes" "still not parked after three refusals"
  # invalid_grant is proof on the FIRST refusal
  mk_stale_creds
  out="$(CLAUDE_MULTIACC_TOKEN_URL="http://127.0.0.1:$port/invalid-grant" CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-low.json" claude-accounts limits --force 2>&1)"
  grep -q "invalid_grant" "$dead_acct/.expired" 2>/dev/null \
    && t_ok "invalid_grant parks the account immediately" \
    || t_fail "invalid_grant park" "no marker for an explicit invalid_grant"
  kill "$srv_pid" 2>/dev/null; wait "$srv_pid" 2>/dev/null || true
  rm -f "$dead_acct/.expired" "$dead_acct/.oauth-refresh.json"
fi

# ---- 16b9b. the usage endpoint's PERMANENT refusals ----------------------------------
# 2026-08-22: every account in the fleet had ranked NEUTRAL for eleven days, so `claude`
# was picking at random and a fresh session landed on the account already at 80% of its
# weekly limit. The chain: the pool's OAuth grants lapsed, the fetcher fell back to the
# portable setup token, and the usage endpoint refuses THAT with
#   403 {"type":"permission_error","message":"OAuth token does not meet scope
#        requirement user:profile"}      x-should-retry: false
# because a setup token is minted without user:profile. Only a 429 used to record a
# backoff, so the refusal was re-issued every scheduled pass from every machine — and
# those retries are what earned the 429s that made an UNAUTHORIZED account look merely
# RATE LIMITED, hiding the real cause behind a plausible one for eleven days.
usrv="$WORK/usage-server.py"
cat > "$usrv" <<'EOF'
import http.server, json, sys
LOG = sys.argv[1]
class H(http.server.BaseHTTPRequestHandler):
    def log_message(self, *a): pass
    def do_GET(self):
        with open(LOG, 'a') as f:
            f.write(self.path + '\n')
        if self.path == '/scope-denied':
            raw = json.dumps({'type': 'error', 'error': {
                'type': 'permission_error',
                'message': 'OAuth token does not meet scope requirement user:profile'}}).encode()
            self.send_response(403)
            self.send_header('x-should-retry', 'false')
        elif self.path == '/boom':
            raw = b'{"error":"server"}'
            self.send_response(500)
        elif self.path == '/multibucket':
            # weekly_percent is the MAX durable bucket (80, five days out). The cheap
            # monthly bucket resets in an hour and says nothing about it.
            import time as _t
            def iso(dt):
                return _t.strftime('%Y-%m-%dT%H:%M:%S+00:00', _t.gmtime(_t.time() + dt))
            raw = json.dumps({'limits': [
                {'kind': 'session', 'percent': 1, 'resets_at': iso(3600), 'scope': None},
                {'kind': 'weekly_all', 'percent': 80, 'resets_at': iso(432000), 'scope': None},
                {'kind': 'monthly_all', 'percent': 10, 'resets_at': iso(3600), 'scope': None}]}).encode()
            self.send_response(200)
        else:
            raw = json.dumps({'limits': [
                {'kind': 'session', 'percent': 3, 'resets_at': '2099-01-01T00:00:00+00:00', 'scope': None},
                {'kind': 'weekly_all', 'percent': 7, 'resets_at': '2099-01-01T00:00:00+00:00', 'scope': None}]}).encode()
            self.send_response(200)
        self.send_header('Content-Type', 'application/json')
        self.send_header('Content-Length', str(len(raw)))
        self.end_headers()
        self.wfile.write(raw)
srv = http.server.HTTPServer(('127.0.0.1', 0), H)
print(srv.server_address[1], flush=True)
srv.serve_forever()
EOF
uhits="$WORK/usage-hits"
: > "$uhits"
uport=""
python3 "$usrv" "$uhits" > "$WORK/usage-port" 2>/dev/null &
usrv_pid=$!
STUB_PIDS="$STUB_PIDS $usrv_pid"
for _ in $(seq 1 20); do
  uport="$(head -1 "$WORK/usage-port" 2>/dev/null)"
  case "$uport" in ''|*[!0-9]*) uport=""; sleep 0.2 ;; *) break ;; esac
done
if [ -z "$uport" ]; then
  kill "$usrv_pid" 2>/dev/null; wait "$usrv_pid" 2>/dev/null || true
  t_ok "usage-endpoint refusal tests skipped (cannot bind a loopback port here)"
else
  # A pool in exactly the incident's shape: a portable setup token and NO OAuth grant.
  SD="$WORK/scope-denied-pool"
  mkdir -p "$SD/acct-01" "$SD/acct-02" "$SD/tmp"
  : > "$SD/.limits-kick"
  cat > "$SD/accounts.json" <<'EOF'
{"version":1,"server":"none","threshold":90,"accounts":[
  {"id":"acct-01","email":"sd1@test","home":"mac","added_at":"2026-07-13T00:00:00Z"},
  {"id":"acct-02","email":"sd2@test","home":"mac","added_at":"2026-07-13T00:00:00Z"}]}
EOF
  for i in 01 02; do
    printf 'sk-ant-oat01-SETUPTOKEN%s\n' "$i" > "$SD/acct-$i/server.token"
    chmod 600 "$SD/acct-$i/server.token"
    # Telemetry frozen eleven days ago — exactly what the incident left on disk.
    printf '{"fetched_at":%s,"source":"oauth","max_percent":2,"weekly_percent":2,"session_percent":0,"buckets":[]}' \
      "$((now - 950000))" > "$SD/acct-$i/limits.json"
  done

  out="$(CLAUDE_ACCOUNTS_ROOT="$SD" CLAUDE_MULTIACC_USAGE_URL="http://127.0.0.1:$uport/scope-denied" \
         claude-accounts limits --force 2>&1)"
  check "a scope-denied 403 names the missing scope" "user:profile" "$out"
  check "a scope-denied 403 names the ceremony that fixes it" "claude-accounts login acct-01" "$out"
  check "a scope-denied 403 backs off instead of retrying" "Backing off" "$out"
  python3 - "$SD/acct-01/limits.json" "$now" <<'EOF'
import json, sys
lim = json.load(open(sys.argv[1]))
now = int(sys.argv[2])
assert lim['retry_after'] > now + 3600, lim          # parked for hours, not minutes
assert lim['fetched_at'] == now - 950000, lim        # a FAILURE never invents freshness
assert 'HTTP 403' in lim['last_error'], lim
EOF
  [ $? -eq 0 ] && t_ok "a refused fetch records a long backoff and keeps its stale fetched_at" \
    || t_fail "403 backoff state" "see $SD/acct-01/limits.json"

  # The whole point: the next scheduled pass must NOT spend another request. Before the
  # fix this retried every five minutes, from every machine, forever.
  before="$(wc -l < "$uhits")"
  out="$(CLAUDE_ACCOUNTS_ROOT="$SD" CLAUDE_MULTIACC_USAGE_URL="http://127.0.0.1:$uport/scope-denied" \
         claude-accounts limits 2>&1)"
  after="$(wc -l < "$uhits")"
  [ "$before" = "$after" ] && t_ok "a parked account is not re-fetched on the next pass" \
    || t_fail "403 retry storm" "endpoint hit again ($before -> $after requests)"
  check "the parked account says why it is waiting" "backing off after HTTP 403" "$out"

  # Any other non-2xx backs off too — a 5xx retried every pass is the same storm.
  rm -f "$SD/acct-01/limits.json"
  out="$(CLAUDE_ACCOUNTS_ROOT="$SD" CLAUDE_MULTIACC_USAGE_URL="http://127.0.0.1:$uport/boom" \
         claude-accounts limits --force 2>&1)"
  check "a 500 backs off as well" "backing off" "$out"
  python3 - "$SD/acct-01/limits.json" <<'EOF'
import json, sys
lim = json.load(open(sys.argv[1]))
assert lim.get('retry_after', 0) > 0 and 'HTTP 500' in lim.get('last_error', ''), lim
assert 'fetched_at' not in lim or not lim['fetched_at'], lim   # never fetched != fresh
EOF
  [ $? -eq 0 ] && t_ok "a 5xx records a backoff without faking a fetch" \
    || t_fail "500 backoff state" "see $SD/acct-01/limits.json"

  # ---- the blind-ranking guard -------------------------------------------------
  # Stale telemetry scores every account the same NEUTRAL value, so pick_best sees one
  # pool-wide tie and selection silently becomes uniform random. It must say so.
  for i in 01 02; do
    printf '{"fetched_at":%s,"source":"oauth","max_percent":2,"weekly_percent":2,"session_percent":0,"buckets":[]}' \
      "$((now - 950000))" > "$SD/acct-$i/limits.json"
  done
  : > "$SD/selection.log"
  CLAUDE_ACCOUNTS_ROOT="$SD" claude >/dev/null 2>&1
  grep -q "ranking=BLIND" "$SD/selection.log" \
    && t_ok "selection.log records that ranking ran blind" \
    || t_fail "blind ranking log" "no ranking=BLIND line: $(tail -1 "$SD/selection.log")"
  grep -q "telemetry-age=9[0-9]\{5\}s" "$SD/selection.log" \
    && t_ok "the blind line carries the age of the outage" \
    || t_fail "blind ranking age" "$(tail -1 "$SD/selection.log")"
  out="$(CLAUDE_ACCOUNTS_ROOT="$SD" claude-accounts status 2>&1)"
  check "status calls a blind pool blind" "RANKING IS BLIND" "$out"
  check "status names the scope that is missing" "user:profile" "$out"
  check "status flags the stale reading itself" "<< STALE" "$out"
  # A panel drives off --json, so the outage has to be a FIELD, not just prose.
  CLAUDE_ACCOUNTS_ROOT="$SD" claude-accounts list --json > "$SD/blind.json" 2>/dev/null
  python3 - "$SD/blind.json" <<'EOF'
import json, sys
d = json.load(open(sys.argv[1]))
assert d['summary']['ranking_blind'] is True, d['summary']
assert all(a['usage']['stale'] is True for a in d['accounts']), d['accounts']
EOF
  [ $? -eq 0 ] && t_ok "--json reports the pool-wide blindness and per-account staleness" \
    || t_fail "json blindness" "see $SD/blind.json"

  # ...and telemetry INSIDE the window must still rank. 900s used to be the window,
  # which is below the ~3600s floor the endpoint itself enforces (Retry-After: 3600),
  # so a healthy pool spent most of every hour ranking neutral for no reason.
  for i in 01 02; do
    printf '{"fetched_at":%s,"source":"oauth","max_percent":%s,"weekly_percent":%s,"session_percent":1,"buckets":[]}' \
      "$((now - 1200))" "$((i + 3))" "$((i + 3))" > "$SD/acct-$i/limits.json"
  done
  : > "$SD/selection.log"
  CLAUDE_ACCOUNTS_ROOT="$SD" claude >/dev/null 2>&1
  ! grep -q "ranking=BLIND" "$SD/selection.log" \
    && t_ok "20-minute-old telemetry still ranks (window matches the endpoint's own floor)" \
    || t_fail "stale window" "20-minute-old data was treated as blind"
  grep -q "acct-01 weekly=4%" "$SD/selection.log" \
    && t_ok "the pool ranks on real numbers and picks the account with more headroom" \
    || t_fail "headroom ranking" "$(tail -1 "$SD/selection.log")"

  # ---- blind does not mean neutral --------------------------------------------
  # Ranking everything NEUTRAL when nothing is fresh throws away information that is
  # still TRUE: a weekly bucket only rises until its reset, so before that moment an
  # old weekly reading remains a valid lower bound. Neutral is only the right answer
  # while some other account has fresh data to be neutral against.
  printf '{"fetched_at":%s,"weekly_percent":81,"session_percent":0,"max_percent":81,"weekly_resets_epoch":%s,"buckets":[]}' \
    "$((now - 950000))" "$((now + 200000))" > "$SD/acct-01/limits.json"
  printf '{"fetched_at":%s,"weekly_percent":4,"session_percent":0,"max_percent":4,"weekly_resets_epoch":%s,"buckets":[]}' \
    "$((now - 950000))" "$((now + 200000))" > "$SD/acct-02/limits.json"
  : > "$SD/selection.log"
  rm -f "$SD/.last-pick"
  for _ in 1 2 3 4 5 6; do CLAUDE_ACCOUNTS_ROOT="$SD" claude >/dev/null 2>&1; done
  if grep -q "acct-01 " "$SD/selection.log"; then
    t_fail "blind ranking still avoids a nearly-exhausted account" \
      "the account stale-reported at 81% weekly was picked: $(grep -c 'acct-01 ' "$SD/selection.log")/6 runs"
  else
    t_ok "blind ranking still avoids a nearly-exhausted account"
  fi
  # DEGRADED, not BLIND: the pool IS still ranking, on readings that remain true. An
  # operator told "random" would go hunting a bug that is not there.
  grep -q "ranking=DEGRADED" "$SD/selection.log" \
    && t_ok "a degraded pick is logged as degraded, not as blind" \
    || t_fail "degraded log" "$(tail -1 "$SD/selection.log")"
  grep -q "acct-02 weekly=4% .*ranking=DEGRADED" "$SD/selection.log" \
    && t_ok "the degraded line reports the stale reading it actually ranked on" \
    || t_fail "degraded weekly" "$(tail -1 "$SD/selection.log")"

  # ...but a reading whose week has ALREADY reset describes a week that is over. It is
  # worth nothing, and must not be mistaken for a low-usage account.
  printf '{"fetched_at":%s,"weekly_percent":81,"session_percent":0,"max_percent":81,"weekly_resets_epoch":%s,"buckets":[]}' \
    "$((now - 950000))" "$((now + 200000))" > "$SD/acct-01/limits.json"
  printf '{"fetched_at":%s,"weekly_percent":4,"session_percent":0,"max_percent":4,"weekly_resets_epoch":%s,"buckets":[]}' \
    "$((now - 950000))" "$((now - 100))" > "$SD/acct-02/limits.json"
  : > "$SD/selection.log"
  rm -f "$SD/.last-pick"
  for _ in 1 2 3 4 5 6; do CLAUDE_ACCOUNTS_ROOT="$SD" claude >/dev/null 2>&1; done
  grep -q "acct-02 " "$SD/selection.log" \
    && t_ok "an expired weekly reading falls back to neutral instead of reading as 4%" \
    || t_fail "expired weekly reading" "acct-02 never picked, so 81% still outranked an unknown"

  # ---- the recorded horizon belongs to the bucket weekly_percent came from -----
  # Taking the earliest reset across ALL durable buckets would let a 10% monthly bucket
  # resetting in an hour throw away an 80% weekly reading that is good for five days —
  # and that account would then score neutral 50 and beat a neighbour honestly at 60%.
  rm -f "$SD/acct-01/limits.json"
  CLAUDE_ACCOUNTS_ROOT="$SD" CLAUDE_MULTIACC_USAGE_URL="http://127.0.0.1:$uport/multibucket" \
    claude-accounts limits --force >/dev/null 2>&1
  python3 - "$SD/acct-01/limits.json" <<'EOF'
import json, sys, time
lim = json.load(open(sys.argv[1]))
assert lim['weekly_percent'] == 80, lim
horizon = lim['weekly_resets_epoch'] - time.time()
assert horizon > 86400, lim   # the 80% bucket's five days, not the monthly bucket's hour
EOF
  [ $? -eq 0 ] && t_ok "the stale-reading horizon tracks the bucket weekly_percent came from" \
    || t_fail "weekly horizon" "see $SD/acct-01/limits.json"

  # ---- an unknown horizon is not comparable, so nobody gets degraded ranking ----
  # A limits.json written before weekly_resets_epoch existed scores NEUTRAL 50 — which
  # would beat a neighbour's true-but-worse 70 and make the degraded path actively
  # wrong. Degraded ranking is therefore all-or-nothing across the candidates.
  printf '{"fetched_at":%s,"weekly_percent":70,"session_percent":0,"max_percent":70,"weekly_resets_epoch":%s,"buckets":[]}' \
    "$((now - 950000))" "$((now + 200000))" > "$SD/acct-01/limits.json"
  printf '{"fetched_at":%s,"weekly_percent":85,"session_percent":0,"max_percent":85,"buckets":[]}' \
    "$((now - 950000))" > "$SD/acct-02/limits.json"     # legacy file: no horizon
  : > "$SD/selection.log"
  rm -f "$SD/.last-pick"
  for _ in 1 2 3 4 5 6; do CLAUDE_ACCOUNTS_ROOT="$SD" claude >/dev/null 2>&1; done
  grep -q "ranking=BLIND" "$SD/selection.log" && ! grep -q "ranking=DEGRADED" "$SD/selection.log" \
    && t_ok "one horizon-less candidate turns degraded ranking off for the whole pool" \
    || t_fail "mixed degraded ranking" "$(tail -1 "$SD/selection.log")"
  grep -q "acct-02 " "$SD/selection.log" \
    && t_ok "with degraded ranking off, the legacy account is still reachable" \
    || t_fail "legacy starvation" "acct-02 never picked in 6 runs"

  # ---- status/--json must agree with the shim, not just with each other --------
  # A status that says "picking at RANDOM" while the shim is ranking on valid stale
  # readings sends an operator after a bug that is not there; a status that says
  # "fine" while the shim is blind is how eleven days went by.
  for i in 01 02; do
    printf '{"fetched_at":%s,"weekly_percent":%s,"session_percent":0,"max_percent":%s,"weekly_resets_epoch":%s,"buckets":[]}' \
      "$((now - 950000))" "$((i + 3))" "$((i + 3))" "$((now + 200000))" > "$SD/acct-$i/limits.json"
  done
  out="$(CLAUDE_ACCOUNTS_ROOT="$SD" claude-accounts status 2>&1)"
  check "status reports DEGRADED when the shim is degraded" "RANKING IS DEGRADED" "$out"
  CLAUDE_ACCOUNTS_ROOT="$SD" claude-accounts list --json > "$SD/degraded.json" 2>/dev/null
  python3 - "$SD/degraded.json" <<'EOF'
import json, sys
d = json.load(open(sys.argv[1]))
assert d['summary']['telemetry'] == 'degraded', d['summary']
assert d['summary']['ranking_blind'] is False, d['summary']
EOF
  [ $? -eq 0 ] && t_ok "--json reports degraded, and ranking_blind stays false" \
    || t_fail "json degraded" "see $SD/degraded.json"

  # ---- a refused setup token is never spent on this endpoint again -------------
  # The 6h park expires; the refusal does not. Asking again can only 403 and only
  # burns the account's ~1-per-hour budget, which is what made an authorization
  # problem look like a rate limit.
  rm -f "$SD/acct-02/limits.json"
  CLAUDE_ACCOUNTS_ROOT="$SD" CLAUDE_MULTIACC_USAGE_URL="http://127.0.0.1:$uport/scope-denied" \
    claude-accounts limits --force >/dev/null 2>&1
  python3 - "$SD/acct-01/limits.json" <<'EOF'
import json, os, sys
p = sys.argv[1]
d = json.load(open(p))
assert d.get('token_scope_denied'), d             # WHICH token was refused (digest)
d['retry_after'] = 0                             # the park has since expired
json.dump(d, open(p + '.tmp', 'w')); os.replace(p + '.tmp', p)
EOF
  before="$(wc -l < "$uhits")"
  out="$(CLAUDE_ACCOUNTS_ROOT="$SD" CLAUDE_MULTIACC_USAGE_URL="http://127.0.0.1:$uport/scope-denied" \
         claude-accounts limits 2>&1)"
  after="$(wc -l < "$uhits")"
  [ "$before" = "$after" ] && t_ok "a token already refused for scope is not offered again" \
    || t_fail "token re-offered" "endpoint hit again ($before -> $after)"
  check "and the message says what would fix it" "claude-accounts login acct-01" "$out"
  # Re-minting the token is a new credential, so it earns a fresh try.
  printf 'sk-ant-oat01-REMINTED01\n' > "$SD/acct-01/server.token"
  before="$(wc -l < "$uhits")"
  CLAUDE_ACCOUNTS_ROOT="$SD" CLAUDE_MULTIACC_USAGE_URL="http://127.0.0.1:$uport/ok" \
    claude-accounts limits >/dev/null 2>&1
  after="$(wc -l < "$uhits")"
  [ "$before" != "$after" ] && t_ok "a newly minted token is tried again" \
    || t_fail "remint not retried" "the new token was never offered"

  # ---- a dead OAuth grant must not park an account whose TOKEN still works -----
  # This is the whole pool's shape after the incident: a working setup token beside a
  # lapsed grant. The shim's auth_dead() reads .expired BEFORE server.token, so parking
  # here would take every working account out of the pool at once — over a credential
  # the pool needs only for telemetry, never for work.
  printf '{"claudeAiOauth":{"accessToken":"sk-ant-oat01-old","refreshToken":"sk-ant-ort01-x","expiresAt":1000,"refreshTokenExpiresAt":1000}}' \
    > "$SD/acct-01/.credentials.json"
  rm -f "$SD/acct-01/limits.json" "$SD/acct-01/.expired" "$SD/acct-01/.oauth-refresh.json"
  out="$(CLAUDE_ACCOUNTS_ROOT="$SD" CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-endpoint-missing.json" \
         claude-accounts limits --force 2>&1)"
  [ ! -f "$SD/acct-01/.expired" ] \
    && t_ok "a dead grant never parks an account that still has a working setup token" \
    || t_fail "portable account parked" "$(tail -1 "$SD/acct-01/.expired")"
  check "...and it says telemetry is what is broken, not the account" "TELEMETRY is dead" "$out"
  out="$(CLAUDE_ACCOUNTS_ROOT="$SD" claude-accounts status 2>&1)"
  check "status keeps it selectable" "selectable  : yes" "$out"
  # ...but with NO token, the same dead grant DOES park it: then nothing can authenticate.
  mv "$SD/acct-01/server.token" "$SD/acct-01/server.token.bak"
  rm -f "$SD/acct-01/limits.json" "$SD/acct-01/.oauth-refresh.json"
  CLAUDE_ACCOUNTS_ROOT="$SD" CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-endpoint-missing.json" \
    claude-accounts limits --force >/dev/null 2>&1
  grep -q "reason=refresh-token-expired" "$SD/acct-01/.expired" 2>/dev/null \
    && t_ok "with no token to fall back on, a dead grant still parks the account" \
    || t_fail "dead grant not parked" "no .expired for an account with nothing that authenticates"
  mv "$SD/acct-01/server.token.bak" "$SD/acct-01/server.token"
  rm -f "$SD/acct-01/.expired" "$SD/acct-01/.credentials.json" "$SD/acct-01/.oauth-refresh.json"

  # ---- a live refresh token recovers even from a husk credential ---------------
  # A credential whose ACCESS token was cleared but whose REFRESH token is alive is
  # exactly what a grant exists to recover from. Requiring the dead half to be present
  # meant such an account could never come back — and with a setup token beside it, it
  # went dark for telemetry permanently.
  printf '{"claudeAiOauth":{"accessToken":"","refreshToken":"sk-ant-ort01-live","expiresAt":0,"refreshTokenExpiresAt":9999999999999}}' \
    > "$SD/acct-01/.credentials.json"
  rm -f "$SD/acct-01/limits.json" "$SD/acct-01/.oauth-refresh.json" "$SD/acct-01/.expired"
  out="$(CLAUDE_ACCOUNTS_ROOT="$SD" CLAUDE_MULTIACC_TOKEN_URL="file://$WORK/token-ok.json" \
         CLAUDE_MULTIACC_USAGE_URL="http://127.0.0.1:$uport/ok" claude-accounts limits --force 2>&1)"
  check "a husk credential with a live refresh token is refreshed" "refreshed via refresh-token grant" "$out"
  python3 -c "import json,sys; sys.exit(0 if json.load(open('$SD/acct-01/limits.json'))['source']=='oauth' else 1)" \
    && t_ok "...and telemetry comes back on the OAuth bearer" \
    || t_fail "husk recovery" "source != oauth"

  # ---- a credential rotated mid-flight by someone else is never overwritten -----
  # The grant rotates; a live claude session refreshes the same file. Losing that race
  # by overwriting destroys the session's newer credential and strands the account.
  printf '{"claudeAiOauth":{"accessToken":"sk-ant-oat01-old","refreshToken":"sk-ant-ort01-MINE","expiresAt":1000,"refreshTokenExpiresAt":9999999999999}}' \
    > "$SD/acct-01/.credentials.json"
  rm -f "$SD/acct-01/limits.json" "$SD/acct-01/.oauth-refresh.json"
  # token-ok.json is a file:// fixture, so the "other writer" can land while the grant
  # is in flight simply by writing a different refresh token first.
  cat > "$SD/racer.sh" <<'RACER'
#!/usr/bin/env bash
printf '{"claudeAiOauth":{"accessToken":"sk-ant-oat01-SESSION","refreshToken":"sk-ant-ort01-THEIRS","expiresAt":9999999999999,"refreshTokenExpiresAt":9999999999999}}' > "$1"
RACER
  chmod +x "$SD/racer.sh"
  "$SD/racer.sh" "$SD/acct-01/.credentials.json.race"
  # simulate: the grant was issued against MINE, but THEIRS is what is on disk now
  printf '{"claudeAiOauth":{"accessToken":"sk-ant-oat01-old","refreshToken":"sk-ant-ort01-MINE","expiresAt":1000,"refreshTokenExpiresAt":9999999999999}}' \
    > "$SD/acct-01/.credentials.json"
  ( sleep 0.1; cp "$SD/acct-01/.credentials.json.race" "$SD/acct-01/.credentials.json" ) &
  racer_pid=$!
  CLAUDE_ACCOUNTS_ROOT="$SD" CLAUDE_MULTIACC_TOKEN_URL="file://$WORK/token-ok.json" \
    CLAUDE_MULTIACC_USAGE_URL="http://127.0.0.1:$uport/ok" claude-accounts limits --force >/dev/null 2>&1
  wait "$racer_pid" 2>/dev/null || true
  grep -q "sk-ant-ort01-THEIRS" "$SD/acct-01/.credentials.json" \
    && t_ok "a credential rotated by another writer survives our refresh" \
    || t_ok "refresh committed before the other writer landed (race not exercised)"
  rm -f "$SD/acct-01/.credentials.json" "$SD/acct-01/.credentials.json.race" "$SD/racer.sh" \
        "$SD/acct-01/.oauth-refresh.json" "$SD/acct-01/.expired"

  # ---- an org block is never downgraded by a weaker reason ---------------------
  # clear_expired refuses to lift an org block, but nothing stopped mark_expired from
  # REWRITING its reason — after which the next successful fetch lifts it happily.
  printf '%s\nreason=org-blocked marked_at=now detail=test\n' "$now" > "$SD/acct-01/.expired"
  printf '{"claudeAiOauth":{"accessToken":"sk-ant-oat01-x","refreshToken":"sk-ant-ort01-x","expiresAt":1000,"refreshTokenExpiresAt":1000}}' \
    > "$SD/acct-01/.credentials.json"
  rm -f "$SD/acct-01/limits.json"
  CLAUDE_ACCOUNTS_ROOT="$SD" CLAUDE_MULTIACC_USAGE_URL="http://127.0.0.1:$uport/ok" \
    claude-accounts limits --force >/dev/null 2>&1
  grep -q "reason=org-blocked" "$SD/acct-01/.expired" 2>/dev/null \
    && t_ok "a dead refresh grant never overwrites an org-blocked marker" \
    || t_fail "org block downgraded" "marker is now: $(cat "$SD/acct-01/.expired" 2>/dev/null | tail -1)"
  rm -f "$SD/acct-01/.expired" "$SD/acct-01/.credentials.json"

  # ---- the >=90% cutoff keeps the TIGHT window --------------------------------
  # Ranking may trust an hour-old number; declaring an account UNUSABLE may not. The
  # cutoff window is EXCLUDE_STALE_AFTER (900s), so this is tested on both sides of it.
  # acct-01 is deliberately the BEST-RANKING account (weekly 1%) while being over the
  # threshold on its session bucket (max 91%). So it is picked whenever it is eligible,
  # and skipped only when the cutoff actually fires — which isolates the cutoff window
  # from the ranking window instead of conflating "excluded" with "outranked".
  mk_cutoff_pool() { # $1 = age of both readings, in seconds
    printf '{"fetched_at":%s,"weekly_percent":1,"session_percent":91,"max_percent":91,"weekly_resets_epoch":%s,"buckets":[]}' \
      "$((now - $1))" "$((now + 200000))" > "$SD/acct-01/limits.json"
    printf '{"fetched_at":%s,"weekly_percent":50,"session_percent":5,"max_percent":50,"weekly_resets_epoch":%s,"buckets":[]}' \
      "$((now - $1))" "$((now + 200000))" > "$SD/acct-02/limits.json"
    : > "$SD/selection.log"
    rm -f "$SD/.last-pick"
    local _i
    for _i in 1 2 3 4; do CLAUDE_ACCOUNTS_ROOT="$SD" claude >/dev/null 2>&1; done
  }
  mk_cutoff_pool 800     # inside the 900s cutoff window
  ! grep -q "acct-01 " "$SD/selection.log" \
    && t_ok "a 91% reading inside the cutoff window excludes the account" \
    || t_fail "threshold exclusion" "a 91% account was selected on 800s-old data"
  mk_cutoff_pool 1000    # past the cutoff window, still inside the RANKING window
  grep -q "acct-01 " "$SD/selection.log" \
    && t_ok "past the cutoff window a 91% reading no longer excludes (fail open)" \
    || t_fail "cutoff fail-open" "a 1000s-old 91% reading still excluded the account"
  ! grep -qE "ranking=(BLIND|DEGRADED)" "$SD/selection.log" \
    && t_ok "...but it is still fresh enough to RANK on (the two windows differ)" \
    || t_fail "ranking window" "1000s-old data was treated as unrankable"
  grep -q "acct-01 weekly=1%" "$SD/selection.log" \
    && t_ok "and ranking still prefers the account with more weekly headroom" \
    || t_fail "ranking preference" "$(tail -1 "$SD/selection.log")"

  # ---- one corrupt limits.json costs exactly one account ----------------------
  # `[]` is valid JSON. Every prev.get() in the refresher would raise on it, OUTSIDE
  # the per-account try — starving every account after it, which is the same pool-wide
  # telemetry blackout this whole section is about.
  printf '[]' > "$SD/acct-01/limits.json"
  rm -f "$SD/acct-02/limits.json"
  out="$(CLAUDE_ACCOUNTS_ROOT="$SD" CLAUDE_MULTIACC_USAGE_URL="http://127.0.0.1:$uport/ok" \
         claude-accounts limits --force 2>&1)"
  rc=$?
  [ "$rc" = "0" ] && t_ok "a limits.json that is not an object exits 0" || t_fail "corrupt limits rc" "rc=$rc: $out"
  [ -s "$SD/acct-02/limits.json" ] \
    && t_ok "accounts after a corrupt limits.json still refresh" \
    || t_fail "corrupt limits starves the loop" "acct-02 was never fetched"

  # status must survive the same file — it is the one command that reports the outage.
  printf '{"fetched_at":"yesterday"}' > "$SD/acct-01/limits.json"
  out="$(CLAUDE_ACCOUNTS_ROOT="$SD" claude-accounts status 2>&1)"
  rc=$?
  [ "$rc" = "0" ] && t_ok "status survives a limits.json with a non-numeric fetched_at" \
    || t_fail "status crash" "rc=$rc: $(printf '%s' "$out" | tail -3)"
  check "status still reaches the accounts after the corrupt one" "acct-02" "$out"

  # A successful fetch must clear the whole backoff record, or one bad hour would keep
  # an account parked long after the endpoint came back.
  printf '{"fetched_at":%s,"weekly_percent":2,"session_percent":0,"max_percent":2,"buckets":[]}' \
    "$((now - 950000))" > "$SD/acct-01/limits.json"
  out="$(CLAUDE_ACCOUNTS_ROOT="$SD" CLAUDE_MULTIACC_USAGE_URL="http://127.0.0.1:$uport/scope-denied" \
         claude-accounts limits --force 2>&1)"
  out="$(CLAUDE_ACCOUNTS_ROOT="$SD" CLAUDE_MULTIACC_USAGE_URL="http://127.0.0.1:$uport/ok" \
         claude-accounts limits --force 2>&1)"
  python3 - "$SD/acct-01/limits.json" <<'EOF'
import json, sys
lim = json.load(open(sys.argv[1]))
assert 'retry_after' not in lim and 'last_error' not in lim, lim
assert lim['weekly_percent'] == 7 and lim['session_percent'] == 3, lim
EOF
  [ $? -eq 0 ] && t_ok "a successful fetch drops every trace of the backoff" \
    || t_fail "backoff cleared" "see $SD/acct-01/limits.json"
  kill "$usrv_pid" 2>/dev/null; wait "$usrv_pid" 2>/dev/null || true
fi

# ---- 16b8. malformed claudeAiOauth (null) degrades that account ONLY (fail open) -------
# {"claudeAiOauth": null} is valid JSON from an interrupted/reset credential write; it
# must not abort the refresher — accounts AFTER it in the manifest must still be fetched.
# acct-01 is first in the manifest, so corrupting it exercises the loop guarantee.
cp "$ACC/acct-01/.credentials.json" "$WORK/acct01-creds.bak"
printf '{"claudeAiOauth": null}' > "$ACC/acct-01/.credentials.json"
rm -f "$ACC/acct-01/limits.json" "$ACC/acct-01/.oauth-refresh.json" "$ACC/acct-02/limits.json"
out="$(CLAUDE_MULTIACC_TOKEN_URL="file://$WORK/token-ok.json" CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-low.json" claude-accounts limits --force 2>&1)"
rc=$?
[ "$rc" = "0" ] && t_ok "null claudeAiOauth exits 0 (fail open)" || t_fail "null claudeAiOauth rc" "rc=$rc: $out"
check "null claudeAiOauth degrades only that account" "acct-01: no fresh bearer" "$out"
[ -f "$ACC/acct-02/limits.json" ] && t_ok "accounts after a malformed one still refresh" \
  || t_fail "fail-open loop guarantee" "acct-02 was starved by acct-01's malformed creds"
cp "$WORK/acct01-creds.bak" "$ACC/acct-01/.credentials.json"
claude-accounts remove acct-05 --yes >/dev/null 2>&1

# ---- 16c. codex-review: security hardening -----------------------------------------
# path traversal via a hand-edited manifest id must never touch the filesystem
mkdir -p "$WORK/canary" && : > "$WORK/canary/DO_NOT_DELETE"
cp "$ACC/accounts.json" "$WORK/manifest.bak"
python3 - "$ACC/accounts.json" <<'EOF'
import json, os, sys
d = json.load(open(sys.argv[1]))
d['accounts'].append({'id': '../canary', 'email': 'evil@test', 'home': 'mac'})
json.dump(d, open(sys.argv[1] + '.tmp', 'w'), indent=2); os.replace(sys.argv[1] + '.tmp', sys.argv[1])
EOF
out="$(claude-accounts remove ../canary --yes 2>&1)"
rc=$?
[ -f "$WORK/canary/DO_NOT_DELETE" ] && t_ok "remove refuses path-traversal id (no deletion)" \
  || t_fail "path traversal" "remove ../canary DELETED files outside the pool"
[ "$rc" != "0" ] && t_ok "traversal id rejected nonzero" || t_fail "traversal rc" "rc=0"
claude-accounts list 2>&1 | grep -q "\.\./canary" \
  && t_fail "invalid ids filtered from listings" "traversal id surfaced" \
  || t_ok "invalid manifest ids are filtered out"
cp "$WORK/manifest.bak" "$ACC/accounts.json"

# sync is Mac-only (source of truth); its input-validation guards can only be exercised
# on Darwin. On Linux `sync` refuses up front, so skip with a note rather than fail.
if [ "$(uname -s)" = "Darwin" ]; then
  # remote command injection via manifest server_root
  python3 - "$ACC/accounts.json" <<'EOF'
import json, os, sys
d = json.load(open(sys.argv[1]))
d['server_root'] = "/tmp/x'; touch /tmp/multiacc_PWNED; #"
json.dump(d, open(sys.argv[1] + '.tmp', 'w'), indent=2); os.replace(sys.argv[1] + '.tmp', sys.argv[1])
EOF
  rm -f /tmp/multiacc_PWNED
  out="$(CLAUDE_MULTIACC_NO_SYNC=0 claude-accounts sync 2>&1)"
  [ ! -f /tmp/multiacc_PWNED ] && t_ok "sync rejects injected server_root (no command executed)" \
    || { t_fail "command injection" "server_root injection EXECUTED"; rm -f /tmp/multiacc_PWNED; }
  check "injected server_root refused" "not a plain absolute path" "$out"
  cp "$WORK/manifest.bak" "$ACC/accounts.json"

  # accountless manifest must not blank the server pool
  python3 - "$ACC/accounts.json" <<'EOF'
import json, os, sys
d = json.load(open(sys.argv[1])); d['accounts'] = []
json.dump(d, open(sys.argv[1] + '.tmp', 'w'), indent=2); os.replace(sys.argv[1] + '.tmp', sys.argv[1])
EOF
  out="$(CLAUDE_MULTIACC_NO_SYNC=0 claude-accounts sync 2>&1)"
  rc=$?
  check "empty manifest refuses to sync" "refusing to blank the target pools" "$out"
  [ "$rc" != "0" ] && t_ok "empty-manifest sync exits nonzero" || t_fail "empty sync rc" "rc=0"
  cp "$WORK/manifest.bak" "$ACC/accounts.json"

  # a REPLICA pool never pushes (side file, not manifest — the manifest is what
  # gets pushed TO replicas)
  printf 'replica\n' > "$ACC/sync-role"
  out="$(CLAUDE_MULTIACC_NO_SYNC=0 claude-accounts sync 2>&1)"
  rc=$?
  check "replica pool refuses to push" "sync replica" "$out"
  [ "$rc" = "0" ] && t_ok "replica sync exits 0 (informational, not an error)" || t_fail "replica sync rc" "rc=$rc"
  # ...and auto_sync (after a mutation) is silent about it
  out="$(CLAUDE_MULTIACC_NO_SYNC=0 claude-accounts import replica-test@x --id acct-31 --no-sync 2>&1
         CLAUDE_MULTIACC_NO_SYNC=0 claude-accounts remove acct-31 --yes 2>&1)"
  case "$out" in *"sync failed"*) t_fail "replica auto_sync" "a replica mutation warned about sync: $out" ;;
    *) t_ok "replica auto_sync is silent (no push, no warning)" ;; esac
  rm -f "$ACC/sync-role"

  # peer targets are validated with the same injection guards as the primary.
  # A fake ssh/rsync harness (prepended to PATH only for these calls) records every
  # remote invocation, so "nothing pushed" and "both targets pushed, in order" are
  # verified from the actual command stream, not inferred from silence.
  SSHFAKE="$WORK/sshfake"
  SSHLOG="$WORK/sshfake.log"
  mkdir -p "$SSHFAKE"
  cat > "$SSHFAKE/ssh" <<'EOF'
#!/usr/bin/env bash
printf 'ssh %s\n' "$*" >> "${SSHLOG:?}"
case "$*" in *"${SSH_FAIL_HOST:-@@none@@}"*) exit 1 ;; esac
exit 0
EOF
  cat > "$SSHFAKE/rsync" <<'EOF'
#!/usr/bin/env bash
printf 'rsync %s\n' "$*" >> "${SSHLOG:?}"
exit 0
EOF
  chmod +x "$SSHFAKE/ssh" "$SSHFAKE/rsync"
  export SSHLOG

  python3 - "$ACC/accounts.json" <<'EOF'
import json, os, sys
d = json.load(open(sys.argv[1]))
d['peers'] = [{'target': "gas@peer; touch /tmp/multiacc_PEER_PWNED", 'root': '/tmp/x', 'repo': '/tmp/y'}]
json.dump(d, open(sys.argv[1] + '.tmp', 'w'), indent=2); os.replace(sys.argv[1] + '.tmp', sys.argv[1])
EOF
  rm -f /tmp/multiacc_PEER_PWNED
  : > "$SSHLOG"
  out="$(PATH="$SSHFAKE:$PATH" CLAUDE_MULTIACC_NO_SYNC=0 claude-accounts sync 2>&1)"
  rc=$?
  check "injected peer target refused" "peer target is not a plain user@host" "$out"
  [ "$rc" != "0" ] && t_ok "bad peer exits nonzero" || t_fail "peer validation rc" "rc=0"
  [ ! -s "$SSHLOG" ] && t_ok "bad peer: nothing pushed anywhere (no ssh/rsync ran)" \
    || t_fail "peer pre-validation" "remote commands ran before peer validation: $(head -2 "$SSHLOG")"
  [ ! -f /tmp/multiacc_PEER_PWNED ] && t_ok "peer injection never executed" \
    || { t_fail "peer injection" "peer target injection EXECUTED"; rm -f /tmp/multiacc_PEER_PWNED; }
  cp "$WORK/manifest.bak" "$ACC/accounts.json"

  # a MALFORMED peer entry (missing fields / not an object) is fatal, never skipped
  python3 - "$ACC/accounts.json" <<'EOF'
import json, os, sys
d = json.load(open(sys.argv[1]))
d['peers'] = [{'root': '/tmp/x', 'repo': '/tmp/y'}]   # no target
json.dump(d, open(sys.argv[1] + '.tmp', 'w'), indent=2); os.replace(sys.argv[1] + '.tmp', sys.argv[1])
EOF
  out="$(PATH="$SSHFAKE:$PATH" CLAUDE_MULTIACC_NO_SYNC=0 claude-accounts sync 2>&1)"
  rc=$?
  check "peer with missing field is fatal, not skipped" "incomplete" "$out"
  [ "$rc" != "0" ] && t_ok "incomplete peer exits nonzero" || t_fail "incomplete peer rc" "rc=0"
  python3 - "$ACC/accounts.json" <<'EOF'
import json, os, sys
d = json.load(open(sys.argv[1]))
d['peers'] = ["gas@peer"]   # not an object
json.dump(d, open(sys.argv[1] + '.tmp', 'w'), indent=2); os.replace(sys.argv[1] + '.tmp', sys.argv[1])
EOF
  out="$(PATH="$SSHFAKE:$PATH" CLAUDE_MULTIACC_NO_SYNC=0 claude-accounts sync 2>&1)"
  check "non-object peer entry is fatal" "malformed" "$out"
  cp "$WORK/manifest.bak" "$ACC/accounts.json"

  # a SUCCESSFUL multi-target sync pushes primary first, then the peer — verified
  # from the recorded command stream
  python3 - "$ACC/accounts.json" <<'EOF'
import json, os, sys
d = json.load(open(sys.argv[1]))
d['peers'] = [{'target': 'gas@peer1', 'root': '/Users/gas/.claude-accounts', 'repo': '/Users/gas/claude-multiacc'}]
json.dump(d, open(sys.argv[1] + '.tmp', 'w'), indent=2); os.replace(sys.argv[1] + '.tmp', sys.argv[1])
EOF
  : > "$SSHLOG"
  out="$(PATH="$SSHFAKE:$PATH" CLAUDE_MULTIACC_NO_SYNC=0 claude-accounts sync 2>&1)"
  rc=$?
  [ "$rc" = "0" ] && t_ok "multi-target sync succeeds" || t_fail "multi-target sync" "rc=$rc: $out"
  check "multi-target sync reports the peer" "+ 1 peer(s)" "$out"
  grep -q "rsync.*root@203.0.113.1:/root/.claude-accounts/accounts.json" "$SSHLOG" \
    && grep -q "rsync.*gas@peer1:/Users/gas/.claude-accounts/accounts.json" "$SSHLOG" \
    && t_ok "manifest pushed to BOTH targets" \
    || t_fail "multi-target pushes" "missing a manifest push: $(grep accounts.json "$SSHLOG")"
  first_primary="$(grep -n "root@203.0.113.1" "$SSHLOG" | head -1 | cut -d: -f1)"
  first_peer="$(grep -n "gas@peer1" "$SSHLOG" | head -1 | cut -d: -f1)"
  [ -n "$first_primary" ] && [ -n "$first_peer" ] && [ "$first_primary" -lt "$first_peer" ] \
    && t_ok "primary target pushed before the peer" \
    || t_fail "target order" "primary=$first_primary peer=$first_peer"
  n_remov="$(grep -c "for dd in acct-" "$SSHLOG")"
  [ "$n_remov" = "2" ] && t_ok "removal propagation ran on each target" \
    || t_fail "per-target removal" "expected 2 removal sweeps, saw $n_remov"
  grep -q "post-sync" "$SSHLOG" && t_ok "post-sync hooks invoked" || t_fail "post-sync hooks" "none recorded"
  # a primary failure aborts the WHOLE sync: the peer is never contacted
  : > "$SSHLOG"
  out="$(PATH="$SSHFAKE:$PATH" SSH_FAIL_HOST="root@203.0.113.1" CLAUDE_MULTIACC_NO_SYNC=0 claude-accounts sync 2>&1)"
  rc=$?
  [ "$rc" != "0" ] && t_ok "primary failure fails the sync" || t_fail "fail-fast rc" "rc=0"
  check "primary failure is loud" "cannot reach" "$out"
  grep -q "gas@peer1" "$SSHLOG" \
    && t_fail "fail-fast" "the peer was contacted after the primary failed" \
    || t_ok "peer not contacted after a primary failure"
  cp "$WORK/manifest.bak" "$ACC/accounts.json"

  # only an EXACT 'replica' value suppresses sync ('not-replica' must still push)
  printf 'not-replica\n' > "$ACC/sync-role"
  out="$(PATH="$SSHFAKE:$PATH" CLAUDE_MULTIACC_NO_SYNC=0 claude-accounts sync 2>&1)"
  case "$out" in *"sync replica"*) t_fail "replica anchor" "'not-replica' suppressed sync" ;;
    *"sync ok"*) t_ok "only an exact 'replica' value suppresses sync" ;;
    *) t_fail "replica anchor" "unexpected: $out" ;; esac
  rm -f "$ACC/sync-role"
else
  t_ok "sync validation tests skipped (Mac-only feature; server refuses sync by design)"
fi

# API keys are never accepted as credentials (subscription-only requirement)
printf 'sk-ant-api03-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' > "$WORK/apikey.txt"
out="$(claude-accounts import apikey@test --id acct-11 --token-file "$WORK/apikey.txt" --no-sync 2>&1)"
rc=$?
check "import rejects an API key as token" "not a subscription setup-token" "$out"
[ ! -d "$ACC/acct-11" ] && t_ok "API-key import created nothing" || t_fail "apikey import" "acct-11 created"
out="$(printf 'sk-ant-api03-ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ' | claude-accounts mint acct-01 --paste 2>&1)"
check "mint --paste rejects an API key" "not a subscription setup-token" "$out"

# ---- 17b. verify authenticates the way the SHIM would ---------------------------------
# A dead credential next to a live portable token: the shim runs that account with the
# TOKEN, so verify must too — testing it with the dead credential would fail a healthy
# account and park it.
printf '{"claudeAiOauth":{"accessToken":"sk-ant-oat01-dead","refreshToken":"r","expiresAt":1000000000000,"refreshTokenExpiresAt":1000000000000}}' > "$ACC/acct-01/.credentials.json"
printf 'sk-ant-oat01-token-for-01' > "$ACC/acct-01/server.token"
rm -f "$ACC/acct-01/.expired"
out="$(claude-accounts verify 2>&1)"
case "$out" in *"acct-01 a@test: PASS"*) t_ok "verify uses the portable token when the credential is dead" ;;
  *) t_fail "verify token fallback" "expected acct-01 PASS, got: $(printf '%s' "$out" | grep acct-01)" ;; esac
[ ! -f "$ACC/acct-01/.expired" ] && t_ok "verify does not park an account its token can run" \
  || t_fail "verify token fallback" "healthy token-auth account was parked"
rm -f "$ACC/acct-01/server.token"
printf '{"claudeAiOauth":{"accessToken":"sk-ant-oat01-test01","refreshToken":"r","expiresAt":9999999999999,"refreshTokenExpiresAt":9999999999999}}' > "$ACC/acct-01/.credentials.json"

# ---- 17c. audit: machine vocabulary (home=server vs machine_kind=linux) ---------------
# `import --home` says mac|server; machine_kind() says mac|linux. If those are compared
# raw, an un-authenticated account ON the server reads as "the grant lives elsewhere"
# and silently drops off the re-login worklist.
mkdir -p "$ACC/acct-06"
python3 - "$ACC/accounts.json" <<'EOF'
import json, sys
doc = json.load(open(sys.argv[1]))
doc['accounts'] = [a for a in doc['accounts'] if a['id'] != 'acct-06']
doc['accounts'].append({'id': 'acct-06', 'email': 'srv@test', 'home': 'server'})
doc['accounts'].sort(key=lambda a: a['id'])
json.dump(doc, open(sys.argv[1], 'w'), indent=2)
EOF
out="$(python3 "$REPO_DIR/lib/audit.py" "$ACC" linux | awk -F'\t' '$1=="acct-06"{print $4}')"
[ "$out" = "missing" ] && t_ok "home=server on the server means NO LOGIN, not 'elsewhere'" \
  || t_fail "home vocabulary" "expected missing on linux, got: $out"
out="$(python3 "$REPO_DIR/lib/audit.py" "$ACC" mac | awk -F'\t' '$1=="acct-06"{print $4}')"
[ "$out" = "remote" ] && t_ok "home=server on the Mac is correctly 'elsewhere'" \
  || t_fail "home vocabulary" "expected remote on mac, got: $out"
python3 - "$ACC/accounts.json" <<'EOF'
import json, sys
doc = json.load(open(sys.argv[1]))
doc['accounts'] = [a for a in doc['accounts'] if a['id'] != 'acct-06']
json.dump(doc, open(sys.argv[1], 'w'), indent=2)
EOF
rm -rf "$ACC/acct-06"

# ---- 17d. an empty pool is never rendered as "all clear" ------------------------------
emptypool="$WORK/emptypool"
mkdir -p "$emptypool/tmp"
printf '{"version":1,"threshold":90,"accounts":[]}' > "$emptypool/accounts.json"
out="$(CLAUDE_ACCOUNTS_DIR="$emptypool" claude-accounts expired 2>&1)"
rc=$?
check "expired on an empty pool says so" "No accounts registered yet" "$out"
[ "$rc" = "0" ] && t_ok "empty pool exits 0" || t_fail "empty pool rc" "rc=$rc"
case "$out" in *"NO LOGIN"*) t_fail "empty pool phantom row" "invented an account from a blank line" ;;
  *) t_ok "empty pool invents no phantom account" ;; esac

out="$(claude-accounts verify --quick 2>&1)"
check "verify --quick passes oauth accounts" "acct-01 a@test: OK" "$out"
check "verify --quick counts" "0 failure(s)" "$out"

# ---- 18. CLI: status renders ------------------------------------------------------
out="$(claude-accounts status 2>&1)"
check "status shows threshold" "90%" "$out"
check "status shows account" "a@test" "$out"

# ---- 19. npm layer: cli.mjs dispatch + self-update + postinstall guards -------------
if command -v node >/dev/null 2>&1; then
  CLI="$REPO_DIR/bin/cli.mjs"
  pkgver="$(node -e "console.log(require('$REPO_DIR/package.json').version)")"
  out="$(node "$CLI" --version 2>&1)"
  check "cli --version matches package.json" "$pkgver" "$out"
  out="$(node "$CLI" --help 2>&1)"
  check "cli --help documents install" "install or update the addon" "$out"
  # passthrough to claude-accounts
  out="$(node "$CLI" list 2>&1)"
  check "cli passes through to claude-accounts (list)" "acct-01" "$out"
  out="$(node "$CLI" status 2>&1)"
  check "cli doctor/status passthrough" "threshold" "$out"
  # self-update on a non-npm, non-git tree is a logged no-op (never errors)
  out="$(claude-accounts self-update 2>&1)"
  rc=$?
  # A git checkout that cannot fast-forward (feature branch, detached CI checkout) is
  # SUPPOSED to fail loudly; what must never happen is a crash with no explanation.
  case "$rc:$out" in
    0:*) t_ok "self-update no-op exits 0 on a plain checkout" ;;
    *:*"git pull FAILED"*) t_ok "self-update reports a git checkout it cannot fast-forward" ;;
    *) t_fail "self-update rc" "rc=$rc: $out" ;;
  esac
  case "$out" in *"update manually"*|*"already latest"*|*"git pull"*) t_ok "self-update reports its path" ;;
    *) t_fail "self-update message" "unexpected: $out" ;; esac
  # postinstall must SKIP for a non-global install and never fail
  out="$(node "$REPO_DIR/scripts/postinstall.mjs" 2>&1)"
  rc=$?
  { [ "$rc" = "0" ] && printf '%s' "$out" | grep -q "skipping auto-setup"; } \
    && t_ok "postinstall skips (and exits 0) for a non-global install" \
    || t_fail "postinstall guard" "rc=$rc out=$out"
  out="$(CI=1 npm_config_global=true node "$REPO_DIR/scripts/postinstall.mjs" 2>&1)"
  printf '%s' "$out" | grep -q "CI environment" \
    && t_ok "postinstall skips under CI even when global" || t_fail "postinstall CI guard" "$out"
  # package.json is valid and ships the essential files list
  node -e "
    const p=require('$REPO_DIR/package.json');
    const need=['bin/','lib/','install.sh','scripts/postinstall.mjs'];
    if(!/^(\.\/)?bin\/cli\.mjs$/.test(p.bin['claude-multiacc'])) { console.error('bad bin'); process.exit(1); }
    for(const f of need) if(!p.files.includes(f)) { console.error('missing file entry: '+f); process.exit(1); }
    if(p.scripts.postinstall!=='node scripts/postinstall.mjs'){ console.error('bad postinstall'); process.exit(1); }
  " && t_ok "package.json bin/files/postinstall wired correctly" || t_fail "package.json" "see errors above"
else
  t_ok "npm-layer tests skipped (node not installed)"
fi

# ============================ CODEX PROVIDER =====================================
# The codex pool (bin/codex + bin/codex-accounts + lib/codex_audit.py) is separate
# code over separate state (~/.codex-accounts), so it gets its own sandboxed pass:
# fake `codex` binary, JWT-shaped auth fixtures, file:// usage/token endpoints.

export CODEX_ACCOUNTS_DIR="$WORK/codex-accounts"
CX="$CODEX_ACCOUNTS_DIR"
mkdir -p "$CX/tmp"
unset CODEX_HOME CODEX_ACCOUNT CODEX_SHIM_ACTIVE 2>/dev/null || true
export CODEX_MULTIACC_NO_SYNC=1
export CODEX_MULTIACC_MIN_FETCH=0
export CODEX_MULTIACC_CLIENT_SCAN_TTL=0
export CODEX_MULTIACC_TOKEN_URL="file://$WORK/cx-token-endpoint-missing.json"
# Default usage URL is an offline missing fixture: the SHIM's opportunistic
# background `limits --quiet` kick must never reach a real endpoint from tests
# (each limits test overrides the URL inline). The pre-armed .limits-kick throttle
# below keeps those background kicks from racing the explicit limits runs at all.
export CODEX_MULTIACC_USAGE_URL="file://$WORK/cx-usage-endpoint-missing.json"
export CODEX_MULTIACC_FORCE_TTY=1
export FAKE_CTL2="$WORK/ctl2"
: > "$CX/.limits-kick"

# Codex auth fixtures are real-shaped: auth.json carrying JWTs whose exp/email
# claims the audit decodes offline (exactly what the CLI does).
MKAUTH="$WORK/mk_cx_auth.py"
cat > "$MKAUTH" <<'EOF'
import base64, json, sys
def jwt(claims):
    enc = lambda o: base64.urlsafe_b64encode(json.dumps(o).encode()).rstrip(b'=').decode()
    return f"{enc({'alg':'RS256'})}.{enc(claims)}.sig"
path, email, exp, mode = sys.argv[1], sys.argv[2], float(sys.argv[3]), (sys.argv[4] if len(sys.argv) > 4 else '')
if mode == 'apikey':
    doc = {"auth_mode": "apikey", "OPENAI_API_KEY": "sk-test-api-key", "tokens": None, "last_refresh": None}
else:
    auth_claim = {"chatgpt_plan_type": "pro", "chatgpt_account_id": "acct-uuid"}
    tokens = {
        "id_token": jwt({"email": email, "exp": exp, "https://api.openai.com/auth": auth_claim}),
        "access_token": jwt({"exp": exp, "https://api.openai.com/auth": auth_claim}),
        "refresh_token": "rt-" + email,
        "account_id": "acct-uuid",
    }
    if mode == 'norefresh':
        tokens.pop('refresh_token')
    doc = {"auth_mode": "chatgpt", "OPENAI_API_KEY": None, "tokens": tokens,
           "last_refresh": "2026-01-01T00:00:00Z"}
json.dump(doc, open(path, 'w'))
EOF
export MKAUTH
mk_cx_auth() { python3 "$MKAUTH" "$@"; }
FUTURE_EXP=$((now + 864000))

# Fake "real" codex: prints which CODEX_HOME it ran under; scriptable login +
# failures via a control file. (Note: must not contain the hyphenated shim marker.)
cat > "$FAKEBIN/codex" <<EOF
#!/usr/bin/env bash
# fake real codex for tests (not a shim)
if [ "\${1:-}" = "login" ]; then
  shift
  # record the flags the CLI chose (device-auth default vs --browser opt-out)
  printf '%s\n' "\$*" > "\${FAKE_LOGIN_ARGS:-/dev/null}" 2>/dev/null || true
  [ -n "\${FAKE_LOGIN_FAIL:-}" ] && { echo "login aborted" >&2; exit 1; }
  # simulate a completed ChatGPT sign-in: write a JWT-shaped auth.json
  python3 "\$MKAUTH" "\${CODEX_HOME:-/dev/null}/auth.json" "\${FAKE_EMAIL:-fake@test}" "\$(( \$(date +%s) + 864000 ))"
  echo "Successfully logged in"
  exit 0
fi
ctl="\${FAKE_CTL2:-/nonexistent}"
acct="\$(basename "\${CODEX_HOME:-none}")"
if [ -f "\$ctl" ] && grep -qx "fail:\$acct" "\$ctl" 2>/dev/null; then
  echo "ERROR: 429 Too Many Requests — you have hit your usage limit" >&2
  exit 1
fi
if [ -f "\$ctl" ] && grep -qx "authfail:\$acct" "\$ctl" 2>/dev/null; then
  echo "Error: token expired. Please run codex login again" >&2
  exit 1
fi
if [ -f "\$ctl" ] && grep -qx "orgfail:\$acct" "\$ctl" 2>/dev/null; then
  echo "Codex has been disabled by your workspace admin"
  exit 1
fi
outfile=""
prev=""
for a in "\$@"; do
  case "\$prev" in -o|--output-last-message) outfile="\$a" ;; esac
  case "\$a" in
    --exit7) echo "ordinary failure, not auth related" >&2; exit 7 ;;
    --echo-stdin) cat; exit 0 ;;
  esac
  prev="\$a"
done
[ -n "\$outfile" ] && printf 'OK' > "\$outfile"
echo "CFG=\$acct"
EOF
chmod +x "$FAKEBIN/codex"

# ---- C1. passthrough: no manifest yet ---------------------------------------------
out="$(codex 2>&1)"
check "codex: passthrough without manifest" "CFG=none" "$out"

# ---- codex manifest + two chatgpt accounts ----------------------------------------
cat > "$CX/accounts.json" <<EOF
{
  "version": 1,
  "server": "root@203.0.113.1",
  "server_root": "/root/.codex-accounts",
  "server_repo": "/root/claude-multiacc",
  "threshold": 90,
  "accounts": [
    {"id": "acct-01", "email": "a@cx", "home": "mac", "added_at": "2026-08-21T00:00:00Z"},
    {"id": "acct-02", "email": "b@cx", "home": "mac", "added_at": "2026-08-21T00:00:00Z"}
  ]
}
EOF
for i in 01 02; do
  mkdir -p "$CX/acct-$i"
done
mk_cx_auth "$CX/acct-01/auth.json" a@cx "$FUTURE_EXP"
mk_cx_auth "$CX/acct-02/auth.json" b@cx "$FUTURE_EXP"

# ---- C2. passthrough guards --------------------------------------------------------
out="$(CODEX_HOME=/tmp/other codex 2>&1)"
check "codex: passthrough with CODEX_HOME" "CFG=other" "$out"
out="$(CODEX_MULTIACC_DISABLE=1 codex 2>&1)"
check "codex: passthrough when disabled" "CFG=none" "$out"

# ---- C3. headroom selection --------------------------------------------------------
cxlj() { printf '{"fetched_at":%s,"weekly_percent":%s,"session_percent":%s,"max_percent":%s,"buckets":[]}' "$now" "$1" "$2" "$3"; }
cxlj 80 10 80 > "$CX/acct-01/limits.json"
cxlj 20 10 20 > "$CX/acct-02/limits.json"
all2=1
for _ in $(seq 1 15); do
  case "$(codex 2>&1)" in *CFG=acct-02*) ;; *) all2=0 ;; esac
done
[ "$all2" = "1" ] && t_ok "codex: picks the highest weekly-headroom account" \
  || t_fail "codex headroom selection" "picked the more-utilized account"
# high session must NOT beat better weekly headroom
cxlj 10 85 85 > "$CX/acct-01/limits.json"
cxlj 70 20 70 > "$CX/acct-02/limits.json"
all1=1
for _ in $(seq 1 15); do
  case "$(codex 2>&1)" in *CFG=acct-01*) ;; *) all1=0 ;; esac
done
[ "$all1" = "1" ] && t_ok "codex: high session does NOT beat better weekly headroom" \
  || t_fail "codex weekly-over-session" "ranked the account with less weekly headroom higher"
# equal scores spread load
cxlj 10 10 10 > "$CX/acct-01/limits.json"
cxlj 10 10 10 > "$CX/acct-02/limits.json"
hits1=0; hits2=0
for _ in $(seq 1 40); do
  case "$(codex 2>&1)" in
    *CFG=acct-01*) hits1=$((hits1+1)) ;;
    *CFG=acct-02*) hits2=$((hits2+1)) ;;
  esac
done
{ [ "$hits1" -gt 0 ] && [ "$hits2" -gt 0 ] && [ $((hits1+hits2)) -eq 40 ]; } \
  && t_ok "codex: equal scores spread randomly (acct-01=$hits1 acct-02=$hits2)" \
  || t_fail "codex tie spreading" "acct-01=$hits1 acct-02=$hits2 (want both >0, total 40)"
# Unknown telemetry ranks behind every truthful reading, never as neutral or free.
printf '{"fetched_at":1,"weekly_percent":1,"session_percent":1,"max_percent":1,"buckets":[]}' > "$CX/acct-01/limits.json"
cxlj 30 30 30 > "$CX/acct-02/limits.json"
out="$(codex 2>&1)"
check "codex: stale 1% loses to fresh 30%" "CFG=acct-02" "$out"
cxlj 88 88 88 > "$CX/acct-01/limits.json"
rm -f "$CX/acct-02/limits.json" "$CX/.last-pick"
all1=1
for _ in $(seq 1 15); do
  case "$(codex 2>&1)" in *CFG=acct-01*) ;; *) all1=0 ;; esac
done
[ "$all1" = "1" ] && t_ok "codex: unknown telemetry never beats known 88%" \
  || t_fail "codex unknown telemetry ranking" "the unknown account beat truthful 88% usage"
rm -f "$CX/acct-01/limits.json" "$CX/.last-pick"
hits1=0; hits2=0
for _ in $(seq 1 40); do
  case "$(codex 2>&1)" in
    *CFG=acct-01*) hits1=$((hits1+1)) ;;
    *CFG=acct-02*) hits2=$((hits2+1)) ;;
  esac
done
{ [ "$hits1" -gt 0 ] && [ "$hits2" -gt 0 ]; } \
  && t_ok "codex: an entirely unknown pool still fails open" \
  || t_fail "codex unknown pool fail-open" "acct-01=$hits1 acct-02=$hits2"
rm -f "$CX"/acct-*/limits.json

# ---- C4. pin -----------------------------------------------------------------------
out="$(CODEX_ACCOUNT=acct-02 codex 2>&1)"
check "codex: CODEX_ACCOUNT pin" "CFG=acct-02" "$out"
mkdir -p "$CX/acct-07"
out="$(CODEX_ACCOUNT=acct-07 codex 2>&1)"
check "codex: pin to auth-less dir (ceremony)" "CFG=acct-07" "$out"
rmdir "$CX/acct-07"

# ---- C5. limited marker excludes; pin overrides; expired marker self-clears ---------
printf '%s\nbucket=GPT-5.3-Codex-Spark:7d percent=95 reason=limits\n' "$((now+3600))" > "$CX/acct-01/.limited"
all2=1
for _ in $(seq 1 15); do
  case "$(codex 2>&1)" in *CFG=acct-02*) ;; *) all2=0 ;; esac
done
[ "$all2" = "1" ] && t_ok "codex: limited account excluded from pool" || t_fail "codex limited exclusion" "acct-01 was still picked"
out="$(CODEX_ACCOUNT=acct-01 codex 2>&1)"
check "codex: explicit pin wins over marker" "CFG=acct-01" "$out"
printf '%s\nbucket=5h percent=95 reason=limits\n' "$((now-10))" > "$CX/acct-01/.limited"
codex >/dev/null 2>&1
[ ! -f "$CX/acct-01/.limited" ] && t_ok "codex: expired .limited marker self-clears" \
  || t_fail "codex marker expiry" ".limited survived its reset time"

# ---- C5b. client-reported rate limits (rollouts) + rotation -------------------------
# Parity with the claude shim: never depend on the usage endpoint to notice an account
# ran dry. The codex CLI writes the windows the server reported into every run's
# rollout, and rollouts live inside the account dir — so no session index is needed.
rm -f "$CX"/acct-0*/.limited "$CX"/acct-0*/limits.json "$CX/.pick-seq" "$CX"/acct-0*/.last-pick
rm -rf "$CX"/acct-0*/sessions
mkrollout() { # mkrollout <acct dir> <used_percent> <resets_at>
  local day="$1/sessions/2026/08/20"
  mkdir -p "$day"
  {
    printf '{"timestamp":"2026-08-19T22:43:21.299Z","type":"session_meta","payload":{"session_id":"01a01c31","cwd":"/proj"}}\n'
    printf '{"timestamp":"2026-08-19T22:49:54.120Z","type":"event_msg","payload":{"type":"token_count","info":{"model_context_window":258400},"rate_limits":{"limit_id":"codex","limit_name":null,"primary":{"used_percent":%s,"window_minutes":10080,"resets_at":%s},"secondary":null,"credits":{"has_credits":false,"unlimited":false,"balance":"0"}}}}\n' "$2" "$3"
  } > "$day/rollout-2026-08-20T01-43-21-01a01c31-7fc3-7291-a0fc-7b4e2b035f1a.jsonl"
}
mkrollout "$CX/acct-01" "97.4" "$((now + 3600))"
all2=1
for _ in $(seq 1 12); do
  case "$(codex 2>&1)" in *CFG=acct-02*) ;; *) all2=0 ;; esac
done
[ "$all2" = "1" ] && t_ok "codex: a spent window in the account's own rollout excludes it" \
  || t_fail "codex client rate limit" "the spent account was picked again"
first="$(head -1 "$CX/acct-01/.limited" 2>/dev/null)"
[ "$first" = "$((now + 3600))" ] && t_ok "codex: marker carries the reported reset time" \
  || t_fail "codex client marker reset" "want $((now + 3600)), got '${first:-<none>}'"
grep -q 'reason=client-rate-limit' "$CX/acct-01/.limited" 2>/dev/null \
  && t_ok "codex: marker is tagged client-rate-limit" \
  || t_fail "codex client marker reason" "$(cat "$CX/acct-01/.limited" 2>/dev/null)"
rm -f "$CX/acct-01/.limited"

# under the threshold is just usage, not an exclusion
mkrollout "$CX/acct-01" "40.0" "$((now + 3600))"
hits1=0
for _ in $(seq 1 12); do
  case "$(codex 2>&1)" in *CFG=acct-01*) hits1=$((hits1+1)) ;; esac
done
{ [ "$hits1" -gt 0 ] && [ ! -f "$CX/acct-01/.limited" ]; } \
  && t_ok "codex: a sub-threshold window does not exclude" \
  || t_fail "codex sub-threshold rollout" "acct-01 hits=$hits1"

# a window that has already reset is history
mkrollout "$CX/acct-01" "99.0" "$((now - 60))"
hits1=0
for _ in $(seq 1 12); do
  case "$(codex 2>&1)" in *CFG=acct-01*) hits1=$((hits1+1)) ;; esac
done
{ [ "$hits1" -gt 0 ] && [ ! -f "$CX/acct-01/.limited" ]; } \
  && t_ok "codex: an elapsed window does not exclude" \
  || t_fail "codex elapsed rollout window" "acct-01 hits=$hits1"

# opt-out
mkrollout "$CX/acct-01" "99.0" "$((now + 3600))"
hits1=0
for _ in $(seq 1 12); do
  case "$(CODEX_MULTIACC_CLIENT_LIMITS=0 codex 2>&1)" in *CFG=acct-01*) hits1=$((hits1+1)) ;; esac
done
{ [ "$hits1" -gt 0 ] && [ ! -f "$CX/acct-01/.limited" ]; } \
  && t_ok "codex: CODEX_MULTIACC_CLIENT_LIMITS=0 turns the scan off" \
  || t_fail "codex client-limit opt-out" "still excluded with the scan disabled"
rm -rf "$CX"/acct-0*/sessions
rm -f "$CX"/acct-0*/.limited

# equal-headroom picks rotate instead of re-rolling a coin
rm -f "$CX/.pick-seq" "$CX"/acct-0*/.last-pick "$CX"/acct-0*/limits.json
seq_out=""
for _ in $(seq 1 8); do
  case "$(codex 2>&1)" in
    *CFG=acct-01*) seq_out="${seq_out}1" ;;
    *CFG=acct-02*) seq_out="${seq_out}2" ;;
    *) seq_out="${seq_out}?" ;;
  esac
done
case "$seq_out" in
  12121212|21212121) t_ok "codex: equal-score picks round-robin across the pool ($seq_out)" ;;
  *) t_fail "codex round-robin tie-break" "sequence $seq_out (want strict alternation)" ;;
esac
rm -f "$CX/.pick-seq" "$CX"/acct-0*/.last-pick


# codex-review finding: the bounded rollout scan must start at the NEWEST file, or a
# busy account whose only over-threshold report is its latest run stays eligible.
rm -f "$CX"/acct-0*/.limited "$CX"/acct-0*/.client-scan
rm -rf "$CX"/acct-0*/sessions
mkdir -p "$CX/acct-01/sessions/2026/08/20"
i=1
while [ "$i" -le 12 ]; do
  printf '{"type":"event_msg","payload":{"rate_limits":{"primary":{"used_percent":10.0,"window_minutes":10080,"resets_at":%s},"secondary":null}}}\n' \
    "$((now + 3600))" > "$CX/acct-01/sessions/2026/08/20/rollout-2026-08-20T0$(printf '%01d' $((i % 10)))-0$i-old$i.jsonl"
  i=$((i + 1))
done
printf '{"type":"event_msg","payload":{"rate_limits":{"primary":{"used_percent":100.0,"window_minutes":10080,"resets_at":%s},"secondary":null}}}\n' \
  "$((now + 3600))" > "$CX/acct-01/sessions/2026/08/20/rollout-2026-08-20T99-99-newest.jsonl"
codex >/dev/null 2>&1
grep -q 'reason=client-rate-limit' "$CX/acct-01/.limited" 2>/dev/null \
  && t_ok "codex: the newest rollout is read even past the file cap" \
  || t_fail "codex rollout scan order" "13 rollouts, only the newest over threshold — missed it"
rm -rf "$CX"/acct-0*/sessions
rm -f "$CX"/acct-0*/.limited "$CX"/acct-0*/.client-scan

# A SHARED sessions tree (the layout lib/common.sh actually installs: acct/sessions is a
# symlink to ~/.codex/sessions so `codex resume` finds every session) proves nothing about
# who spent the quota. One spent window there must not mark the whole pool.
SHARED="$WORK/cx-shared-sessions"
mkdir -p "$SHARED/2026/08/20"
printf '{"type":"event_msg","payload":{"rate_limits":{"primary":{"used_percent":100.0,"window_minutes":10080,"resets_at":%s},"secondary":null}}}\n' \
  "$((now + 3600))" > "$SHARED/2026/08/20/rollout-2026-08-20T01-00-00-shared.jsonl"
for i in 01 02; do ln -s "$SHARED" "$CX/acct-$i/sessions"; done
codex >/dev/null 2>&1
{ [ ! -f "$CX/acct-01/.limited" ] && [ ! -f "$CX/acct-02/.limited" ]; } \
  && t_ok "codex: a shared sessions symlink never marks an account (fail open)" \
  || t_fail "codex shared sessions" "a shared rollout tree marked the pool LIMITED"
out="$(codex 2>&1)"
case "$out" in *CFG=acct-0*) t_ok "codex: the pool still selects with a shared sessions tree" ;;
  *) t_fail "codex shared sessions" "selection produced: $out" ;; esac
rm -f "$CX"/acct-0*/sessions "$CX"/acct-0*/.limited "$CX"/acct-0*/.client-scan

# second codex-review pass: a NESTED symlink inside a real sessions/ dir must not smuggle
# another tree's rollouts in, and a huge stale rollout directory must not be walked.
rm -f "$CX"/acct-0*/.limited "$CX"/acct-0*/.client-scan
rm -rf "$CX"/acct-0*/sessions
OUTSIDE="$WORK/cx-outside"
mkdir -p "$OUTSIDE/08/20" "$CX/acct-01/sessions"
printf '{"type":"event_msg","payload":{"rate_limits":{"primary":{"used_percent":100.0,"window_minutes":10080,"resets_at":%s},"secondary":null}}}\n' \
  "$((now + 3600))" > "$OUTSIDE/08/20/rollout-2026-08-20T01-00-00-outside.jsonl"
ln -s "$OUTSIDE" "$CX/acct-01/sessions/2026"
codex >/dev/null 2>&1
[ ! -f "$CX/acct-01/.limited" ] \
  && t_ok "codex: a nested symlink out of the account tree never marks it" \
  || t_fail "codex nested sessions symlink" "rollouts outside the account tree marked it LIMITED"
rm -f "$CX/acct-01/sessions/2026"

# a day dir stuffed with stale rollouts must cost a bounded amount of work, and the
# newest (over-threshold) file must still be the one that decides
mkdir -p "$CX/acct-01/sessions/2026/08/20"
i=1
while [ "$i" -le 60 ]; do
  printf '{"type":"event_msg","payload":{"rate_limits":{"primary":{"used_percent":5.0,"window_minutes":10080,"resets_at":%s},"secondary":null}}}\n' \
    "$((now + 3600))" > "$CX/acct-01/sessions/2026/08/20/rollout-2026-08-20T00-00-$(printf '%02d' "$i")-bulk.jsonl"
  i=$((i + 1))
done
printf '{"type":"event_msg","payload":{"rate_limits":{"primary":{"used_percent":100.0,"window_minutes":10080,"resets_at":%s},"secondary":null}}}\n' \
  "$((now + 3600))" > "$CX/acct-01/sessions/2026/08/20/rollout-2026-08-20T23-59-59-newest.jsonl"
codex >/dev/null 2>&1
grep -q 'reason=client-rate-limit' "$CX/acct-01/.limited" 2>/dev/null \
  && t_ok "codex: 61 rollouts in one day dir — the newest still decides" \
  || t_fail "codex bulk rollout dir" "the newest rollout was not the one read"
rm -rf "$CX"/acct-0*/sessions
rm -f "$CX"/acct-0*/.limited "$CX"/acct-0*/.client-scan

# third codex-review pass: find(1) output is newline-delimited, so a pool file whose NAME
# contains a newline arrives as two lines and its tail resolves relative to $PWD.
rm -f "$CX"/acct-0*/.limited "$CX"/acct-0*/.client-scan
rm -rf "$CX"/acct-0*/sessions
mkdir -p "$CX/acct-01/sessions/2026/08/20"
ESCDIR="$WORK/cx-escape-dir"
mkdir -p "$ESCDIR"
printf '{"type":"event_msg","payload":{"rate_limits":{"primary":{"used_percent":100.0,"window_minutes":10080,"resets_at":%s},"secondary":null}}}\n' \
  "$((now + 3600))" > "$ESCDIR/escape.jsonl"
# ONE file whose name embeds a newline; find prints it as two lines, and the second
# ("escape.jsonl") would resolve against the caller's cwd — outside the pool entirely.
HOSTILE=$'rollout-a\nescape.jsonl'
: > "$CX/acct-01/sessions/2026/08/20/$HOSTILE"
[ -e "$CX/acct-01/sessions/2026/08/20/$HOSTILE" ] \
  || t_fail "codex newline filename" "fixture not created — the guard would go unexercised"
( cd "$ESCDIR" && codex >/dev/null 2>&1 )
[ ! -f "$CX/acct-01/.limited" ] \
  && t_ok "codex: a newline in a rollout name cannot pull in a file outside the pool" \
  || t_fail "codex newline filename" "a file outside the pool marked the account LIMITED"
rm -rf "$CX"/acct-0*/sessions
rm -f "$CX"/acct-0*/.limited "$CX"/acct-0*/.client-scan

# corrupt pool state must never reach bash arithmetic here either
CXBIG="99999999999999999999999999999999"
printf '%s\n' "$CXBIG" > "$CX/.last-pick"
printf '{"fetched_at":%s,"weekly_percent":%s,"session_percent":1,"max_percent":%s,"buckets":[]}' "$CXBIG" "$CXBIG" "$CXBIG" > "$CX/acct-02/limits.json"
err="$(codex 2>&1 >/dev/null)"
[ -z "$err" ] && t_ok "codex: absurd pool numbers keep stderr byte-clean" \
  || t_fail "codex corrupt number handling" "stderr: $(printf '%s' "$err" | head -c 160)"
rm -f "$CX/.last-pick" "$CX"/acct-0*/limits.json
: > "$CX/acct-01/.client-scan"
err="$(CODEX_MULTIACC_CLIENT_SCAN_TTL=bogus CODEX_MULTIACC_THRESHOLD=nonsense codex 2>&1 >/dev/null)"
out="$(CODEX_MULTIACC_CLIENT_SCAN_TTL=bogus CODEX_MULTIACC_THRESHOLD=nonsense codex 2>/dev/null)"
{ [ -z "$err" ] && case "$out" in *CFG=acct-0*) true ;; *) false ;; esac; } \
  && t_ok "codex: garbage in the TTL / threshold env vars keeps stderr clean" \
  || t_fail "codex env number validation" "stderr: $(printf '%s' "$err" | head -c 160) out: $out"
rm -f "$CX"/acct-0*/.client-scan

# a parallel burst still spreads (rotation drops only the account just handed out).
# Three accounts, for the same reason as the claude side: with two, landing entirely on
# the single alternative is correct behaviour and the assertion would measure nothing.
mkdir -p "$CX/acct-09"
"$MKAUTH" > "$CX/acct-09/auth.json" 2>/dev/null || \
  printf '{"tokens":{"access_token":"%s","account_id":"a9"},"last_refresh":"2026-08-01T00:00:00Z"}' \
    "$(sed -n 's/.*"access_token"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$CX/acct-01/auth.json" | head -1)" \
    > "$CX/acct-09/auth.json"
: > "$WORK/cx-burst.out"
for _ in $(seq 1 16); do ( codex >> "$WORK/cx-burst.out" 2>&1 ) & done
wait
c_distinct="$(grep -o 'CFG=acct-[0-9]*' "$WORK/cx-burst.out" | sort -u | tr '\n' ' ')"
c_n="$(printf '%s' "$c_distinct" | wc -w | tr -d ' ')"
[ "$c_n" -ge 2 ] \
  && t_ok "codex: a parallel burst still spreads across the pool ($c_distinct)" \
  || t_fail "codex burst spreading" "all 16 concurrent runs took $c_distinct"
rm -rf "$CX/acct-09"
rm -f "$CX/.last-pick"

# ---- C6. all limited -> least-utilized fallback -------------------------------------
printf '%s\nbucket=7d percent=95 reason=limits\n' "$((now+3600))" > "$CX/acct-01/.limited"
printf '%s\nbucket=7d percent=99 reason=limits\n' "$((now+3600))" > "$CX/acct-02/.limited"
cxlj 95 10 95 > "$CX/acct-01/limits.json"
cxlj 99 10 99 > "$CX/acct-02/limits.json"
out="$(codex 2>&1)"
check "codex: all-limited falls back to least utilized" "CFG=acct-01" "$out"
grep -q "all-limited fallback=acct-01" "$CX/selection.log" \
  && t_ok "codex: all-limited fallback logged" || t_fail "codex fallback log" "no all-limited line"
rm -f "$CX"/acct-*/.limited "$CX"/acct-*/limits.json

# ---- C7. dead logins: .expired excludes, heals on newer credential ------------------
printf '%s\nreason=refresh-denied-http-400 marked_at=t detail=x\n' "$now" > "$CX/acct-01/.expired"
all2=1
for _ in $(seq 1 10); do
  case "$(codex 2>&1)" in *CFG=acct-02*) ;; *) all2=0 ;; esac
done
[ "$all2" = "1" ] && t_ok "codex: dead login (proven park) is never selected" \
  || t_fail "codex dead login" "a parked account was selected"
# ...not even as the all-limited fallback
printf '%s\nbucket=7d percent=95 reason=limits\n' "$((now+3600))" > "$CX/acct-02/.limited"
out="$(codex 2>&1)"
check "codex: limited-but-alive beats dead in the fallback" "CFG=acct-02" "$out"
rm -f "$CX/acct-02/.limited"
# a NEWER credential heals a credential-scoped park
sleep 1
mk_cx_auth "$CX/acct-01/auth.json" a@cx "$FUTURE_EXP"
out="$(CODEX_SHIM_SELECT=random codex 2>&1)"
[ ! -f "$CX/acct-01/.expired" ] && t_ok "codex: newer auth.json clears a credential park" \
  || t_fail "codex park heal" ".expired survived a newer credential"
# an org-blocked park survives a newer credential (policy, not credential)
printf '%s\nreason=org-blocked marked_at=t detail=x\n' "$now" > "$CX/acct-01/.expired"
sleep 1
mk_cx_auth "$CX/acct-01/auth.json" a@cx "$FUTURE_EXP"
codex >/dev/null 2>&1
[ -f "$CX/acct-01/.expired" ] && t_ok "codex: org-blocked park survives a credential refresh" \
  || t_fail "codex org park" "a token write cleared an org-policy park"
rm -f "$CX/acct-01/.expired"
# a soft-stamped park expires on its own
printf '%s\nreason=auth-error soft_until=%s marked_at=t detail=x\n' "$now" "$((now-5))" > "$CX/acct-02/.expired"
codex >/dev/null 2>&1
[ ! -f "$CX/acct-02/.expired" ] && t_ok "codex: soft_until park expires on its own" \
  || t_fail "codex soft park" "an elapsed soft park still excluded the account"

# ---- C8. exec auto-retry ------------------------------------------------------------
# acct-01 gets the strictly better score so headroom selection deterministically
# picks it FIRST; the scripted failure then forces the retry onto acct-02.
cx_first_01() { cxlj 5 5 5 > "$CX/acct-01/limits.json"; cxlj 20 20 20 > "$CX/acct-02/limits.json"; }
cx_first_01
# rate limit: retry on the other account + self-expiring cooldown for the failed one
echo "fail:acct-01" > "$FAKE_CTL2"
out="$(codex exec "hello" < /dev/null 2>&1)"
case "$out" in *CFG=acct-02*) t_ok "codex: exec retries a rate-limited account on another" ;;
  *) t_fail "codex retry" "did not land on acct-02: $out" ;; esac
if [ -f "$CX/acct-01/.limited" ]; then
  grep -q "error-cooldown" "$CX/acct-01/.limited" \
    && t_ok "codex: rate-limited account got a cooldown, not a park" \
    || t_fail "codex cooldown" "marker is not an error-cooldown"
else
  t_fail "codex cooldown" "no .limited cooldown written"
fi
rm -f "$CX/acct-01/.limited" "$FAKE_CTL2"
# auth failure parks (soft) instead of a cooldown
cx_first_01
echo "authfail:acct-01" > "$FAKE_CTL2"
codex exec "hello" < /dev/null >/dev/null 2>&1
if [ -f "$CX/acct-01/.expired" ]; then
  grep -q "reason=auth-error" "$CX/acct-01/.expired" && grep -q "soft_until=" "$CX/acct-01/.expired" \
    && t_ok "codex: auth failure parks the account with a soft stamp" \
    || t_fail "codex auth park" "marker malformed: $(cat "$CX/acct-01/.expired")"
else
  t_fail "codex auth park" "no .expired written after an auth failure"
fi
rm -f "$CX/acct-01/.expired" "$FAKE_CTL2"
# org-disabled failure parks as org-blocked
cx_first_01
echo "orgfail:acct-01" > "$FAKE_CTL2"
codex exec "hello" < /dev/null >/dev/null 2>&1
grep -q "reason=org-blocked" "$CX/acct-01/.expired" 2>/dev/null \
  && t_ok "codex: workspace-disabled failure parks as org-blocked" \
  || t_fail "codex org park from run" "marker: $(cat "$CX/acct-01/.expired" 2>/dev/null || echo none)"
rm -f "$CX/acct-01/.expired" "$FAKE_CTL2"
# ordinary failure: exit code passes through, no retry, no marker
out="$(codex exec --exit7 < /dev/null 2>&1)"
rc=$?
[ "$rc" = "7" ] && t_ok "codex: non-auth failure exit code passes through" || t_fail "codex rc passthrough" "rc=$rc"
[ ! -f "$CX/acct-01/.limited" ] && [ ! -f "$CX/acct-02/.limited" ] \
  && t_ok "codex: ordinary failure marks nothing" || t_fail "codex ordinary failure" "a marker appeared"
# retry only engages for exec: a plain (interactive-style) run with a failing account
# passes the failure straight through — no retry, no marker classification.
printf 'fail:acct-01\nfail:acct-02\n' > "$FAKE_CTL2"
out="$(codex "prompt" < /dev/null 2>&1)"
rc=$?
[ "$rc" = "1" ] && t_ok "codex: non-exec run is never retried (rc passes through)" \
  || t_fail "codex non-exec retry" "rc=$rc"
[ ! -f "$CX/acct-01/.limited" ] && [ ! -f "$CX/acct-02/.limited" ] \
  && t_ok "codex: non-exec failure writes no markers" \
  || t_fail "codex non-exec markers" "a marker appeared without the retry path"
rm -f "$FAKE_CTL2" "$CX"/acct-*/.limited "$CX"/acct-*/.expired
# stdin/stdout byte fidelity through the retry path
printf 'line1\nline2\n' > "$WORK/cx-stdin.txt"
out="$(codex exec --echo-stdin < "$WORK/cx-stdin.txt" 2>&1)"
[ "$out" = "$(printf 'line1\nline2')" ] && t_ok "codex: stdin passes byte-identically through retry buffering" \
  || t_fail "codex stdin fidelity" "got: $out"
# user args survive selection
out="$(codex exec "two words" --exit7 < /dev/null 2>&1)"
rc=$?
[ "$rc" = "7" ] && t_ok "codex: user args survive selection (pick_best untouched \$@)" \
  || t_fail "codex args" "rc=$rc out=$out"
# HOME unset: fail open into passthrough
out="$(env -u HOME -u CODEX_ACCOUNTS_DIR PATH="$PATH" "$REPO_DIR/bin/codex" 2>&1)"
check "codex: HOME unset fails open into passthrough" "CFG=none" "$out"
rm -f "$CX"/acct-*/limits.json

# ---- C9. selection log --------------------------------------------------------------
grep -qE 'acct-0[12]' "$CX/selection.log" && t_ok "codex: selection.log written" \
  || t_fail "codex selection.log" "no selections logged"

# ---- C10. CLI: list / import / remove / dedupe --------------------------------------
out="$(codex-accounts list 2>&1)"
check "codex-accounts list shows accounts" "a@cx" "$out"
check "codex-accounts list shows auth kind" "auth=chatgpt" "$out"
mk_cx_auth "$WORK/cx-import.json" c@cx "$FUTURE_EXP"
out="$(codex-accounts import c@cx --id acct-03 --auth "$WORK/cx-import.json" --no-sync 2>&1)"
check "codex-accounts import" "Imported acct-03" "$out"
[ -f "$CX/acct-03/auth.json" ] && t_ok "codex: imported auth.json in place" || t_fail "codex import" "auth.json missing"
perm="$(ls -l "$CX/acct-03/auth.json" | cut -c1-10)"
[ "$perm" = "-rw-------" ] && t_ok "codex: imported auth.json is 0600" || t_fail "codex import perms" "$perm"
# duplicate email refused without --force / --id
out="$(codex-accounts import c@cx --no-sync 2>&1)"
rc=$?
check "codex: duplicate import refused" "already registered as acct-03" "$out"
[ "$rc" != "0" ] && t_ok "codex: duplicate import exits nonzero" || t_fail "codex dup import rc" "rc=0"
# dedupe removes a forced duplicate, keeping the authed one
codex-accounts import c@cx --id acct-04 --force --no-sync >/dev/null 2>&1
out="$(codex-accounts dedupe --yes 2>&1)"
check "codex: dedupe removes the duplicate" "Removed 1 duplicate" "$out"
codex-accounts list 2>&1 | grep -q "acct-04" \
  && t_fail "codex dedupe" "acct-04 still listed" || t_ok "codex: dedupe kept one entry per email"
out="$(codex-accounts remove acct-03 --yes 2>&1)"
check "codex-accounts remove" "Removed acct-03" "$out"
[ ! -d "$CX/acct-03" ] && t_ok "codex: removed account dir deleted" || t_fail "codex remove" "dir still present"

# ---- C11. login-first add ceremony --------------------------------------------------
# derived email: no argument, identity read back from the sign-in
out="$(FAKE_EMAIL=new@cx codex-accounts add 2>&1)"
check "codex: add with no email derives it from the sign-in" "Registered acct-03 for new@cx" "$out"
grep -q '"email": "new@cx"' "$CX/accounts.json" && t_ok "codex: derived email registered" \
  || t_fail "codex add derive" "manifest lacks new@cx"
# signed in as a different account than named: register who actually authenticated
out="$(FAKE_EMAIL=other@cx codex-accounts add named@cx 2>&1)"
check "codex: mismatched sign-in registers the real account" "registering the account that actually authenticated" "$out"
grep -q '"email": "other@cx"' "$CX/accounts.json" && t_ok "codex: actual identity registered" \
  || t_fail "codex add mismatch" "manifest lacks other@cx"
codex-accounts remove acct-04 --yes >/dev/null 2>&1
# aborted login leaves zero traces
before="$(ls "$CX" | sort)"
out="$(FAKE_LOGIN_FAIL=1 codex-accounts add gone@cx 2>&1)"
rc=$?
[ "$rc" != "0" ] && t_ok "codex: aborted login exits nonzero" || t_fail "codex abort rc" "rc=0"
after="$(ls "$CX" | sort)"
[ "$before" = "$after" ] && t_ok "codex: aborted login leaves zero traces" \
  || t_fail "codex abort traces" "pool dir changed: $after"
grep -q "gone@cx" "$CX/accounts.json" && t_fail "codex abort manifest" "gone@cx registered" \
  || t_ok "codex: aborted login registered nothing"
# adding an already-registered email skips before sign-in
out="$(codex-accounts add a@cx 2>&1)"
rc=$?
check "codex: add of registered email skips gracefully" "already added as acct-01" "$out"
[ "$rc" = "0" ] && t_ok "codex: duplicate add exits 0 (no-op)" || t_fail "codex dup add rc" "rc=$rc"
# signing in AS an already-registered email (unnamed) also refuses a second entry
out="$(FAKE_EMAIL=a@cx codex-accounts add 2>&1)"
check "codex: sign-in as registered email refused" "already added as acct-01" "$out"
n="$(grep -c '"email": "a@cx"' "$CX/accounts.json")"
[ "$n" = "1" ] && t_ok "codex: no second entry for a re-signed-in email" || t_fail "codex dup signin" "count=$n"

# ---- C12. login command + expired worklist + relogin --------------------------------
# an account with no auth on this machine: login completes it
codex-accounts import d@cx --id acct-04 --no-sync >/dev/null 2>&1
out="$(codex-accounts expired 2>&1)"
rc=$?
check "codex: expired lists the auth-less account" "acct-04" "$out"
[ "$rc" != "0" ] && t_ok "codex: expired exits 1 when a human is needed" || t_fail "codex expired rc" "rc=0"
out="$(FAKE_EMAIL=d@cx codex-accounts login acct-04 2>&1)"
check "codex: login completes an auth-less account" "login saved" "$out"
[ -s "$CX/acct-04/auth.json" ] && t_ok "codex: login wrote auth.json" || t_fail "codex login" "no auth.json"
# wrong sign-in identity is refused AND the prior credential is restored — the wrong
# account's auth.json must never stay installed under this id (codex-review finding)
out="$(FAKE_EMAIL=wrong@cx codex-accounts login acct-04 2>&1)"
rc=$?
check "codex: login refuses a wrong-account sign-in" "you signed in as wrong@cx" "$out"
[ "$rc" != "0" ] && t_ok "codex: wrong-identity login exits nonzero" || t_fail "codex login identity rc" "rc=0"
got_email="$(python3 - "$CX/acct-04/auth.json" <<'EOF'
import base64, json, sys
t = json.load(open(sys.argv[1]))['tokens']['id_token']
p = t.split('.')[1]; p += '=' * (-len(p) % 4)
print(json.loads(base64.urlsafe_b64decode(p)).get('email', ''))
EOF
)"
[ "$got_email" = "d@cx" ] && t_ok "codex: refused login restores the prior credential" \
  || t_fail "codex login restore" "auth.json now belongs to: $got_email"
# signing in as a DIFFERENT-but-REGISTERED account routes the credential to that
# account instead of discarding it (and the target account stays honestly unfixed)
rm -f "$CX/acct-01/auth.json"
out="$(FAKE_EMAIL=a@cx codex-accounts login acct-04 2>&1)"
rc=$?
check "codex: cross-account sign-in is redirected, not discarded" "credential was saved to acct-01" "$out"
check "codex: redirect says the target still needs its sign-in" "acct-04 (d@cx) still needs its own sign-in" "$out"
[ "$rc" != "0" ] && t_ok "codex: redirected login still exits nonzero for the target" \
  || t_fail "codex redirect rc" "rc=0 for an account that was not fixed"
[ -s "$CX/acct-01/auth.json" ] && t_ok "codex: redirected credential landed at its owner" \
  || t_fail "codex redirect landing" "acct-01/auth.json missing"
got_email="$(python3 - "$CX/acct-04/auth.json" <<'EOF'
import base64, json, sys
t = json.load(open(sys.argv[1]))['tokens']['id_token']
p = t.split('.')[1]; p += '=' * (-len(p) % 4)
print(json.loads(base64.urlsafe_b64decode(p)).get('email', ''))
EOF
)"
[ "$got_email" = "d@cx" ] && t_ok "codex: redirect restored the target's prior credential" \
  || t_fail "codex redirect restore" "acct-04 auth.json belongs to: $got_email"
# `add` while signed into an already-registered account also keeps the credential
rm -f "$CX/acct-01/auth.json"
before_dirs="$(ls "$CX" | sort)"
out="$(FAKE_EMAIL=a@cx codex-accounts add 2>&1)"
rc=$?
check "codex: add of a registered account saves the fresh sign-in to it" "fresh sign-in was saved to acct-01" "$out"
[ "$rc" = "0" ] && t_ok "codex: add redirect exits 0 (nothing new to register)" || t_fail "codex add redirect rc" "rc=$rc"
[ -s "$CX/acct-01/auth.json" ] && t_ok "codex: add redirect landed the credential" \
  || t_fail "codex add redirect" "acct-01/auth.json missing"
[ "$before_dirs" = "$(ls "$CX" | sort)" ] && t_ok "codex: add redirect leaves no reserved dir behind" \
  || t_fail "codex add redirect cleanup" "pool dirs changed"
n="$(grep -c '"email": "a@cx"' "$CX/accounts.json")"
[ "$n" = "1" ] && t_ok "codex: add redirect creates no duplicate entry" || t_fail "codex add redirect dup" "count=$n"
# relogin end-to-end: a redirect mid-run fixes the OTHER account, whose own turn is
# then skipped instead of demanding a second sign-in. Both accounts' home must be
# THIS machine's kind, or a credless account reads as 'remote' (grant lives
# elsewhere) on the other platform and never enters the worklist.
cx_mk=mac; [ "$(uname -s)" = "Darwin" ] || cx_mk=linux
python3 - "$CX/accounts.json" "$cx_mk" <<'EOF'
import json, os, sys
d = json.load(open(sys.argv[1]))
for a in d['accounts']:
    if a['id'] in ('acct-01', 'acct-04'):
        a['home'] = sys.argv[2]
json.dump(d, open(sys.argv[1] + '.tmp', 'w'), indent=2); os.replace(sys.argv[1] + '.tmp', sys.argv[1])
EOF
rm -f "$CX/acct-01/auth.json" "$CX/acct-04/auth.json"
out="$(FAKE_EMAIL=d@cx codex-accounts relogin --yes 2>&1)"
rc=$?
check "codex: relogin redirect saves the mis-ordered sign-in" "credential was saved to acct-04" "$out"
check "codex: relogin skips an account fixed mid-run" "already has a working login" "$out"
check "codex: relogin counts the redirect-fixed account" "re-authenticated 1 of 2" "$out"
check "codex: relogin still reports the unfixed account" "still failing: acct-01" "$out"
[ "$rc" != "0" ] && t_ok "codex: relogin with an unfixed account exits nonzero" || t_fail "codex relogin redirect rc" "rc=0"
mk_cx_auth "$CX/acct-01/auth.json" a@cx "$FUTURE_EXP"
mk_cx_auth "$CX/acct-04/auth.json" d@cx "$FUTURE_EXP"
# the sign-in ceremony is DEVICE-CODE by default (the localhost browser callback
# cannot reach a remote/SSH machine); --browser opts into the callback flow
export FAKE_LOGIN_ARGS="$WORK/cx-login-args"
: > "$FAKE_LOGIN_ARGS"
FAKE_EMAIL=d@cx codex-accounts login acct-04 >/dev/null 2>&1
rc=$?
{ [ "$rc" = "0" ] && [ "$(cat "$FAKE_LOGIN_ARGS")" = "--device-auth" ]; } \
  && t_ok "codex: sign-in uses the device-code flow by default" \
  || t_fail "codex device default" "rc=$rc login args: '$(cat "$FAKE_LOGIN_ARGS")'"
: > "$FAKE_LOGIN_ARGS"
FAKE_EMAIL=d@cx codex-accounts login acct-04 --browser >/dev/null 2>&1
rc=$?
{ [ "$rc" = "0" ] && [ "$(cat "$FAKE_LOGIN_ARGS")" = "" ]; } \
  && t_ok "codex: --browser opts into the localhost callback flow" \
  || t_fail "codex --browser opt-out" "rc=$rc login args: '$(cat "$FAKE_LOGIN_ARGS")'"
# lock released after a completed login
[ ! -d "$CX/acct-04/.login-lock" ] && t_ok "codex: login lock released after completion" \
  || { t_fail "codex login lock release" ".login-lock survived a completed login"; rmdir "$CX/acct-04/.login-lock" 2>/dev/null; }
# a SECOND concurrent login for the same account is refused before any ceremony —
# a parallel run snapshotting the old credential could otherwise "restore" it over
# the fresh one (field incident: months-dead token inside a minutes-old auth.json)
mkdir "$CX/acct-04/.login-lock"
export FAKE_LOGIN_ARGS="$WORK/cx-login-args"
: > "$FAKE_LOGIN_ARGS"
cp "$CX/acct-04/auth.json" "$WORK/cx-lock-auth.bak"
out="$(FAKE_EMAIL=d@cx codex-accounts login acct-04 2>&1)"
rc=$?
check "codex: concurrent login for the same account is refused" "already in progress" "$out"
[ "$rc" != "0" ] && t_ok "codex: concurrent login exits nonzero" || t_fail "codex login lock rc" "rc=0"
[ ! -s "$FAKE_LOGIN_ARGS" ] && t_ok "codex: refused concurrent login never ran a ceremony" \
  || t_fail "codex login lock ceremony" "codex login was invoked despite the lock"
cmp -s "$CX/acct-04/auth.json" "$WORK/cx-lock-auth.bak" \
  && t_ok "codex: refused concurrent login left the credential untouched" \
  || t_fail "codex login lock credential" "auth.json changed"
# a STALE lock (holder died >30min ago) is reclaimed, not fatal
python3 - "$CX/acct-04/.login-lock" <<'EOF'
import os, sys, time
t = time.time() - 2000
os.utime(sys.argv[1], (t, t))
EOF
out="$(FAKE_EMAIL=d@cx codex-accounts login acct-04 2>&1)"
rc=$?
[ "$rc" = "0" ] && t_ok "codex: stale login lock is reclaimed" || t_fail "codex stale login lock" "rc=$rc: $out"
[ ! -d "$CX/acct-04/.login-lock" ] && t_ok "codex: reclaimed lock released again" \
  || { t_fail "codex stale lock release" "lock left behind"; rmdir "$CX/acct-04/.login-lock" 2>/dev/null; }
# lock released after a FAILED ceremony too
FAKE_LOGIN_FAIL=1 FAKE_EMAIL=d@cx codex-accounts login acct-04 >/dev/null 2>&1
[ ! -d "$CX/acct-04/.login-lock" ] && t_ok "codex: login lock released after a failed ceremony" \
  || { t_fail "codex login lock on failure" "lock left behind"; rmdir "$CX/acct-04/.login-lock" 2>/dev/null; }
# remove refuses to delete an account under a live ceremony
mkdir "$CX/acct-04/.login-lock"
out="$(codex-accounts remove acct-04 --yes 2>&1)"
rc=$?
check "codex: remove refuses during a live login" "login for acct-04 is in progress" "$out"
[ "$rc" != "0" ] && [ -d "$CX/acct-04" ] && t_ok "codex: account survives a remove during login" \
  || t_fail "codex remove guard" "rc=$rc dir_exists=$([ -d "$CX/acct-04" ] && echo yes || echo no)"
rmdir "$CX/acct-04/.login-lock"
# a redirect never writes under a mid-ceremony login on the OWNER account either:
# it skips the save and falls back to the plain refusal
mkdir "$CX/acct-01/.login-lock"
mv "$CX/acct-01/auth.json" "$WORK/cx-a01.hold"
out="$(FAKE_EMAIL=a@cx codex-accounts login acct-04 2>&1)"
rc=$?
check "codex: redirect skipped while the owner has a login in progress" "login for acct-01 is in progress" "$out"
[ "$rc" != "0" ] && t_ok "codex: skipped redirect still refuses" || t_fail "codex busy-owner redirect rc" "rc=0"
[ ! -f "$CX/acct-01/auth.json" ] && t_ok "codex: no write under the owner's live ceremony" \
  || t_fail "codex busy-owner redirect" "auth.json written under an active login lock"
rmdir "$CX/acct-01/.login-lock"
mv "$WORK/cx-a01.hold" "$CX/acct-01/auth.json"
got_email="$(python3 - "$CX/acct-04/auth.json" <<'EOF'
import base64, json, sys
t = json.load(open(sys.argv[1]))['tokens']['id_token']
p = t.split('.')[1]; p += '=' * (-len(p) % 4)
print(json.loads(base64.urlsafe_b64decode(p)).get('email', ''))
EOF
)"
[ "$got_email" = "d@cx" ] && t_ok "codex: busy-owner refusal restored the target credential" \
  || t_fail "codex busy-owner restore" "acct-04 auth belongs to: $got_email"
unset FAKE_LOGIN_ARGS

# a parked account shows in expired and relogin fixes exactly that
printf '%s\nreason=refresh-denied-http-400 marked_at=t detail=x\n' "$now" > "$CX/acct-04/.expired"
out="$(codex-accounts expired 2>&1)"
check "codex: expired shows the parked account" "acct-04" "$out"
out="$(FAKE_EMAIL=d@cx codex-accounts relogin --yes 2>&1)"
check "codex: relogin re-authenticates the worklist" "re-authenticated 1 of 1" "$out"
[ ! -f "$CX/acct-04/.expired" ] && t_ok "codex: relogin cleared the park" || t_fail "codex relogin" "park survived"
# a relogin whose ceremony fails is NOT reported fixed: the dead credential stays
# on disk, and success is judged by whether the credential can AUTHENTICATE.
mk_cx_auth "$CX/acct-04/auth.json" d@cx 1000 norefresh
out="$(FAKE_LOGIN_FAIL=1 FAKE_EMAIL=d@cx codex-accounts relogin --yes 2>&1)"
rc=$?
check "codex: failed relogin says still failing" "still failing" "$out"
[ "$rc" != "0" ] && t_ok "codex: failed relogin exits nonzero" || t_fail "codex failed relogin rc" "rc=0"
out="$(codex-accounts expired 2>&1)"
check "codex: the un-fixed account is still on the worklist" "acct-04" "$out"
# api-key-only auth is not subscription auth: audited as unusable, never selected
mk_cx_auth "$CX/acct-04/auth.json" d@cx "$FUTURE_EXP" apikey
out="$(codex-accounts expired 2>&1)"
check "codex: api-key auth is called out as unsupported" "API key" "$out"
out="$(CODEX_SHIM_SELECT=random codex 2>&1)"
case "$out" in *CFG=acct-04*) t_fail "codex apikey selection" "api-key account was selected" ;;
  *) t_ok "codex: api-key account is never selected" ;; esac
codex-accounts remove acct-04 --yes >/dev/null 2>&1

# ---- C13. limits: fixture endpoint, marking, classification -------------------------
: > "$CX/.limits-kick"   # re-arm the shim-kick throttle: no background limits racing these
cat > "$WORK/cx-usage-high.json" <<EOF
{"email":"a@cx","plan_type":"pro",
 "rate_limit":{"allowed":true,"limit_reached":false,
   "primary_window":{"used_percent":42,"limit_window_seconds":18000,"reset_at":$((now+3600))},
   "secondary_window":{"used_percent":57,"limit_window_seconds":604800,"reset_at":$((now+90000))}},
 "code_review_rate_limit":null,
 "additional_rate_limits":[
   {"limit_name":"GPT-5.3-Codex-Spark","rate_limit":{"allowed":true,"limit_reached":false,
     "primary_window":{"used_percent":12,"limit_window_seconds":18000,"reset_at":$((now+3600))},
     "secondary_window":{"used_percent":95,"limit_window_seconds":604800,"reset_at":$((now+90000))}}}]}
EOF
out="$(CODEX_MULTIACC_USAGE_URL="file://$WORK/cx-usage-high.json" codex-accounts limits 2>&1)"
check "codex: >=90% model bucket marks the account" "LIMITED GPT-5.3-Codex-Spark:7d at 95%" "$out"
[ -f "$CX/acct-01/.limited" ] && t_ok "codex: .limited marker written" || t_fail "codex limits marker" "missing"
IFS= read -r first < "$CX/acct-01/.limited"
[ "$first" = "$((now+90000))" ] && t_ok "codex: marker carries the offender's reset epoch" \
  || t_fail "codex marker reset" "first line: $first"
python3 - "$CX/acct-01/limits.json" <<'EOF'
import json, sys
d = json.load(open(sys.argv[1]))
assert d['max_percent'] == 95, d['max_percent']
assert d['weekly_percent'] == 95, d['weekly_percent']
assert d['session_percent'] == 42, d['session_percent']
names = {b['name']: (b['group'], b['percent']) for b in d['buckets']}
assert names['5h'] == ('session', 42), names
assert names['7d'] == ('weekly', 57), names
assert names['GPT-5.3-Codex-Spark:5h'] == ('session', 12), names
assert names['GPT-5.3-Codex-Spark:7d'] == ('weekly', 95), names
EOF
[ $? -eq 0 ] && t_ok "codex: buckets classified session/weekly with per-model names" \
  || t_fail "codex bucket classification" "see assertions"
# below-threshold refresh clears the marker
cat > "$WORK/cx-usage-low.json" <<EOF
{"email":"a@cx","plan_type":"pro",
 "rate_limit":{"allowed":true,"limit_reached":false,
   "primary_window":{"used_percent":5,"limit_window_seconds":18000,"reset_at":$((now+3600))},
   "secondary_window":{"used_percent":9,"limit_window_seconds":604800,"reset_at":$((now+90000))}},
 "additional_rate_limits":[]}
EOF
out="$(CODEX_MULTIACC_USAGE_URL="file://$WORK/cx-usage-low.json" codex-accounts limits 2>&1)"
check "codex: below-threshold refresh clears the marker" "marker cleared" "$out"
[ ! -f "$CX/acct-01/.limited" ] && t_ok "codex: marker gone after clean pass" || t_fail "codex marker clear" "still present"
# a hard limit_reached verdict excludes even when no window is >= threshold
cat > "$WORK/cx-usage-blocked.json" <<EOF
{"email":"a@cx","plan_type":"pro",
 "rate_limit":{"allowed":false,"limit_reached":true,
   "primary_window":{"used_percent":50,"limit_window_seconds":18000,"reset_at":$((now+3600))}}}
EOF
out="$(CODEX_MULTIACC_USAGE_URL="file://$WORK/cx-usage-blocked.json" codex-accounts limits 2>&1)"
check "codex: limit_reached without a >=90% window still marks" "LIMITED limit_reached at 100%" "$out"
rm -f "$CX"/acct-*/.limited
# reshaped payload: windows are still found by the recursive fallback
cat > "$WORK/cx-usage-reshaped.json" <<EOF
{"totally":{"new":{"shape":{"used_percent":97,"limit_window_seconds":604800,"reset_at":$((now+90000))}}}}
EOF
out="$(CODEX_MULTIACC_USAGE_URL="file://$WORK/cx-usage-reshaped.json" codex-accounts limits 2>&1)"
check "codex: reshaped payload still tracked (fallback walk)" "LIMITED" "$out"
rm -f "$CX"/acct-*/.limited
# garbage payload degrades that account only, never the run
printf 'this is not json' > "$WORK/cx-usage-garbage.json"
out="$(CODEX_MULTIACC_USAGE_URL="file://$WORK/cx-usage-garbage.json" codex-accounts limits 2>&1)"
rc=$?
[ "$rc" = "0" ] && t_ok "codex: garbage payload fails open (rc=0)" || t_fail "codex garbage rc" "rc=$rc"
check "codex: garbage payload logged as failure" "usage fetch failed" "$out"
# fetch throttle: fresh data is not re-fetched
out="$(CODEX_MULTIACC_MIN_FETCH=9999 CODEX_MULTIACC_USAGE_URL="file://$WORK/cx-usage-low.json" codex-accounts limits 2>&1)"
printf '%s' "$out" | grep -q "acct-01" \
  && t_fail "codex fetch throttle" "fresh account was re-fetched: $out" \
  || t_ok "codex: fresh account skipped by the fetch throttle"
# 429 backoff honored
python3 - "$CX/acct-02/limits.json" "$now" <<'EOF'
import json, sys
json.dump({'fetched_at': 0, 'retry_after': int(sys.argv[2]) + 300, 'backoff': 120},
          open(sys.argv[1], 'w'))
EOF
out="$(CODEX_MULTIACC_USAGE_URL="file://$WORK/cx-usage-low.json" codex-accounts limits 2>&1)"
check "codex: 429 backoff honored" "acct-02: backing off after 429" "$out"
rm -f "$CX"/acct-*/limits.json

# ---- C14. oauth refresh via the token endpoint --------------------------------------
# expired bearer, missing endpoint: fail open with backoff
mk_cx_auth "$CX/acct-01/auth.json" a@cx 1000
out="$(CODEX_MULTIACC_USAGE_URL="file://$WORK/cx-usage-low.json" codex-accounts limits 2>&1)"
rc=$?
check "codex: failed oauth refresh logged with backoff" "acct-01: oauth refresh failed" "$out"
check "codex: expired bearer logged, not fatal" "acct-01: no fresh bearer" "$out"
[ "$rc" = "0" ] && t_ok "codex: limits exits 0 with an expired-bearer account" || t_fail "codex refresh rc" "rc=$rc"
[ -f "$CX/acct-01/.oauth-refresh.json" ] && t_ok "codex: refresh failure recorded for backoff" \
  || t_fail "codex refresh backoff file" "missing"
# a now-working endpoint is not retried inside the backoff window
python3 - "$WORK/cx-token-ok.json" "$((now + 864000))" <<'EOF'
import base64, json, sys
def jwt(claims):
    enc = lambda o: base64.urlsafe_b64encode(json.dumps(o).encode()).rstrip(b'=').decode()
    return f"{enc({'alg':'RS256'})}.{enc(claims)}.sig"
exp = float(sys.argv[2])
auth_claim = {"chatgpt_plan_type": "pro", "chatgpt_account_id": "acct-uuid"}
json.dump({"access_token": jwt({"exp": exp, "https://api.openai.com/auth": auth_claim, "marker": "REFRESHED"}),
           "refresh_token": "rt-rotated-new",
           "id_token": jwt({"email": "a@cx", "exp": exp, "https://api.openai.com/auth": auth_claim})},
          open(sys.argv[1], 'w'))
EOF
out="$(CODEX_MULTIACC_TOKEN_URL="file://$WORK/cx-token-ok.json" CODEX_MULTIACC_USAGE_URL="file://$WORK/cx-usage-low.json" codex-accounts limits 2>&1)"
check "codex: refresh backoff honored (no early retry)" "acct-01: no fresh bearer" "$out"
grep -q "rt-rotated-new" "$CX/acct-01/auth.json" \
  && t_fail "codex backoff" "auth.json rewritten inside the backoff window" \
  || t_ok "codex: no refresh inside the backoff window"
# --force refreshes: rotated tokens persisted 0600, telemetry follows
out="$(CODEX_MULTIACC_TOKEN_URL="file://$WORK/cx-token-ok.json" CODEX_MULTIACC_USAGE_URL="file://$WORK/cx-usage-low.json" codex-accounts limits --force 2>&1)"
check "codex: --force refreshes the expired token" "acct-01: access token refreshed" "$out"
check "codex: refreshed account fetches telemetry" "acct-01: ok" "$out"
python3 - "$CX/acct-01/auth.json" <<'EOF'
import base64, json, os, stat, sys
doc = json.load(open(sys.argv[1]))
t = doc['tokens']
payload = t['access_token'].split('.')[1]
payload += '=' * (-len(payload) % 4)
claims = json.loads(base64.urlsafe_b64decode(payload))
assert claims.get('marker') == 'REFRESHED', 'access token not replaced'
assert t['refresh_token'] == 'rt-rotated-new', 'refresh token not rotated'
assert doc.get('last_refresh', '').startswith('20'), 'last_refresh not stamped'
mode = stat.S_IMODE(os.stat(sys.argv[1]).st_mode)
assert mode == 0o600, oct(mode)
EOF
[ $? -eq 0 ] && t_ok "codex: rotated credential persisted with 0600" || t_fail "codex rotation" "see assertions"
[ ! -f "$CX/acct-01/.oauth-refresh.json" ] && t_ok "codex: successful refresh clears the backoff file" \
  || t_fail "codex refresh clear" "backoff file still present"
grep -q "rt-rotated-new" "$CX/limits.log" \
  && t_fail "codex token leak" "a refresh token leaked into limits.log" \
  || t_ok "codex: limits.log leaks no tokens"
# recently-expired token is left alone (a live codex session may own it) — even --force
mk_cx_auth "$CX/acct-01/auth.json" a@cx "$((now - 100))"
cp "$CX/acct-01/auth.json" "$WORK/cx-recent.bak"
rm -f "$CX/acct-01/limits.json" "$CX/acct-01/.oauth-refresh.json"
out="$(CODEX_MULTIACC_TOKEN_URL="file://$WORK/cx-token-ok.json" CODEX_MULTIACC_USAGE_URL="file://$WORK/cx-usage-low.json" codex-accounts limits --force 2>&1)"
check "codex: recently-expired token not refreshed (even --force)" "acct-01: no fresh bearer" "$out"
cmp -s "$CX/acct-01/auth.json" "$WORK/cx-recent.bak" \
  && t_ok "codex: recently-expired credential left untouched" \
  || t_fail "codex refresh gate" "credential rewritten inside the 5-min grace window"
# no refresh token at all: parked with a clear reason
mk_cx_auth "$CX/acct-01/auth.json" a@cx 1000 norefresh
rm -f "$CX/acct-01/limits.json" "$CX/acct-01/.oauth-refresh.json"
out="$(CODEX_MULTIACC_TOKEN_URL="file://$WORK/cx-token-ok.json" CODEX_MULTIACC_USAGE_URL="file://$WORK/cx-usage-low.json" codex-accounts limits --force 2>&1)"
grep -q "reason=no-refresh-token" "$CX/acct-01/.expired" 2>/dev/null \
  && t_ok "codex: refreshless expired credential is parked" \
  || t_fail "codex no-refresh park" "no marker written"
# a later successful authenticated fetch unparks it
mk_cx_auth "$CX/acct-01/auth.json" a@cx "$FUTURE_EXP"
rm -f "$CX/acct-01/limits.json"
out="$(CODEX_MULTIACC_USAGE_URL="file://$WORK/cx-usage-low.json" codex-accounts limits --force 2>&1)"
[ ! -f "$CX/acct-01/.expired" ] && t_ok "codex: a successful usage fetch clears the dead-auth marker" \
  || t_fail "codex unpark" ".expired survived an authenticated fetch"
# ...but never an org-blocked park (telemetry proves nothing about workspace policy)
printf '%s\nreason=org-blocked marked_at=t detail=x\n' "$now" > "$CX/acct-01/.expired"
rm -f "$CX/acct-01/limits.json"
CODEX_MULTIACC_USAGE_URL="file://$WORK/cx-usage-low.json" codex-accounts limits --force >/dev/null 2>&1
[ -f "$CX/acct-01/.expired" ] && t_ok "codex: a usage fetch does not unpark an org-blocked account" \
  || t_fail "codex org unpark" "telemetry cleared an org block it cannot observe"
rm -f "$CX/acct-01/.expired"
# a 4xx from the token endpoint must not park the pool; invalid_grant is proof
port=""
python3 "$srv_script" > "$WORK/cx-token-port" 2>/dev/null &
cx_srv_pid=$!
for _ in $(seq 1 20); do
  port="$(head -1 "$WORK/cx-token-port" 2>/dev/null)"
  case "$port" in ''|*[!0-9]*) port=""; sleep 0.2 ;; *) break ;; esac
done
if [ -z "$port" ]; then
  kill "$cx_srv_pid" 2>/dev/null; wait "$cx_srv_pid" 2>/dev/null || true
  t_ok "codex: token-endpoint 4xx tests skipped (cannot bind a loopback port here)"
else
  mk_cx_stale() {
    mk_cx_auth "$CX/acct-01/auth.json" a@cx 1000
    rm -f "$CX/acct-01/limits.json" "$CX/acct-01/.oauth-refresh.json" "$CX/acct-01/.expired"
  }
  mk_cx_stale
  CODEX_MULTIACC_TOKEN_URL="http://127.0.0.1:$port/boom" CODEX_MULTIACC_USAGE_URL="file://$WORK/cx-usage-low.json" codex-accounts limits --force >/dev/null 2>&1
  [ ! -f "$CX/acct-01/.expired" ] && t_ok "codex: one opaque 4xx does not park an account" \
    || t_fail "codex 4xx park" "a single non-invalid_grant 400 parked the account"
  CODEX_MULTIACC_TOKEN_URL="http://127.0.0.1:$port/boom" CODEX_MULTIACC_USAGE_URL="file://$WORK/cx-usage-low.json" codex-accounts limits --force >/dev/null 2>&1
  CODEX_MULTIACC_TOKEN_URL="http://127.0.0.1:$port/boom" CODEX_MULTIACC_USAGE_URL="file://$WORK/cx-usage-low.json" codex-accounts limits --force >/dev/null 2>&1
  [ -f "$CX/acct-01/.expired" ] && t_ok "codex: a repeatedly-refused grant is parked on the third strike" \
    || t_fail "codex 4xx strikes" "still not parked after three refusals"
  mk_cx_stale
  CODEX_MULTIACC_TOKEN_URL="http://127.0.0.1:$port/invalid-grant" CODEX_MULTIACC_USAGE_URL="file://$WORK/cx-usage-low.json" codex-accounts limits --force >/dev/null 2>&1
  grep -q "invalid_grant" "$CX/acct-01/.expired" 2>/dev/null \
    && t_ok "codex: invalid_grant parks the account immediately" \
    || t_fail "codex invalid_grant" "no marker for an explicit invalid_grant"
  kill "$cx_srv_pid" 2>/dev/null; wait "$cx_srv_pid" 2>/dev/null || true
fi
mk_cx_auth "$CX/acct-01/auth.json" a@cx "$FUTURE_EXP"
rm -f "$CX/acct-01/.expired" "$CX/acct-01/.oauth-refresh.json" "$CX"/acct-*/limits.json
# malformed tokens object (null) degrades that account only
cp "$CX/acct-01/auth.json" "$WORK/cx-auth01.bak"
printf '{"auth_mode":"chatgpt","tokens": null}' > "$CX/acct-01/auth.json"
out="$(CODEX_MULTIACC_TOKEN_URL="file://$WORK/cx-token-ok.json" CODEX_MULTIACC_USAGE_URL="file://$WORK/cx-usage-low.json" codex-accounts limits --force 2>&1)"
rc=$?
[ "$rc" = "0" ] && t_ok "codex: null tokens object fails open (rc=0)" || t_fail "codex null tokens rc" "rc=$rc"
check "codex: null tokens degrades only that account" "acct-01: no fresh bearer" "$out"
[ -f "$CX/acct-02/limits.json" ] && t_ok "codex: accounts after a malformed one still refresh" \
  || t_fail "codex fail-open loop" "acct-02 starved by acct-01's malformed auth"
cp "$WORK/cx-auth01.bak" "$CX/acct-01/auth.json"
rm -f "$CX"/acct-*/limits.json

# refresh is not attempted at all when the rotated credential could not be persisted
# (the grant rotates the refresh token — consuming it and then failing to write
# auth.json would strand the account; codex-review finding).
# Root ignores directory permission bits, so the unwritable-dir setup only works
# as a non-root user (the server suite runs as root — skip there).
if [ "$(id -u)" = "0" ]; then
  t_ok "codex: refresh persistence preflight test skipped (root ignores directory permissions)"
  t_ok "codex: refresh persistence preflight test skipped (root ignores directory permissions) [2]"
else
  mk_cx_auth "$CX/acct-01/auth.json" a@cx 1000
  rm -f "$CX/acct-01/limits.json" "$CX/acct-01/.oauth-refresh.json"
  chmod 500 "$CX/acct-01"
  out="$(CODEX_MULTIACC_TOKEN_URL="file://$WORK/cx-token-ok.json" CODEX_MULTIACC_USAGE_URL="file://$WORK/cx-usage-low.json" codex-accounts limits --force 2>&1)"
  chmod 700 "$CX/acct-01"
  check "codex: unpersistable credential skips the refresh grant" "refresh not attempted" "$out"
  grep -q "rt-rotated-new" "$CX/acct-01/auth.json" \
    && t_fail "codex refresh preflight" "the grant was consumed despite an unwritable dir" \
    || t_ok "codex: refresh grant not consumed when persistence would fail"
  mk_cx_auth "$CX/acct-01/auth.json" a@cx "$FUTURE_EXP"
  rm -f "$CX/acct-01/.oauth-refresh.json"
fi

# two parallel imports get distinct ids and both land in the manifest (lock coverage;
# codex-review finding)
codex-accounts import p1@cx --no-sync >/dev/null 2>&1 &
imp1=$!
codex-accounts import p2@cx --no-sync >/dev/null 2>&1 &
imp2=$!
wait "$imp1"; wait "$imp2"
n1="$(grep -c '"email": "p1@cx"' "$CX/accounts.json")"
n2="$(grep -c '"email": "p2@cx"' "$CX/accounts.json")"
id1="$(python3 -c "import json,sys; print(next((a['id'] for a in json.load(open('$CX/accounts.json'))['accounts'] if a['email']=='p1@cx'), ''))")"
id2="$(python3 -c "import json,sys; print(next((a['id'] for a in json.load(open('$CX/accounts.json'))['accounts'] if a['email']=='p2@cx'), ''))")"
{ [ "$n1" = "1" ] && [ "$n2" = "1" ] && [ -n "$id1" ] && [ -n "$id2" ] && [ "$id1" != "$id2" ]; } \
  && t_ok "codex: parallel imports get distinct ids ($id1, $id2)" \
  || t_fail "codex parallel import" "p1=$n1($id1) p2=$n2($id2)"
codex-accounts remove "$id1" --yes >/dev/null 2>&1
codex-accounts remove "$id2" --yes >/dev/null 2>&1

# ---- C15. verify --------------------------------------------------------------------
out="$(codex-accounts verify --quick 2>&1)"
check "codex: verify --quick passes chatgpt accounts" "acct-01 a@cx: OK" "$out"
check "codex: verify --quick counts" "0 failure(s)" "$out"
out="$(codex-accounts verify 2>&1)"
check "codex: full verify PASS via the real-call matrix" "acct-01 a@cx: PASS" "$out"
check "codex: full verify zero failures" "0 failure(s)" "$out"
# verify parks a dead login it discovers, and a PASS clears an existing park
echo "authfail:acct-02" > "$FAKE_CTL2"
printf '%s\nreason=auth-error marked_at=t detail=x\n' "$now" > "$CX/acct-01/.expired"
out="$(codex-accounts verify 2>&1)"
check "codex: verify flags the dead login" "acct-02 b@cx: FAIL" "$out"
check "codex: verify says what fixes it" "codex-accounts relogin acct-02" "$out"
grep -q "reason=auth-error" "$CX/acct-02/.expired" 2>/dev/null \
  && t_ok "codex: verify parks the dead login" || t_fail "codex verify park" "no marker"
[ ! -f "$CX/acct-01/.expired" ] && t_ok "codex: a verify PASS clears an existing park" \
  || t_fail "codex verify unpark" "PASS left the marker in place"
rm -f "$FAKE_CTL2" "$CX/acct-02/.expired"
# an org-disabled account is parked as org-blocked
echo "orgfail:acct-02" > "$FAKE_CTL2"
out="$(codex-accounts verify 2>&1)"
check "codex: verify calls out the workspace block" "ORG BLOCKED" "$out"
grep -q "reason=org-blocked" "$CX/acct-02/.expired" 2>/dev/null \
  && t_ok "codex: verify parks the org-blocked account" || t_fail "codex verify org park" "no marker"
rm -f "$FAKE_CTL2" "$CX/acct-02/.expired"

# ---- C16. audit vocabulary + empty pool ---------------------------------------------
mkdir -p "$CX/acct-06"
python3 - "$CX/accounts.json" <<'EOF'
import json, sys
doc = json.load(open(sys.argv[1]))
doc['accounts'] = [a for a in doc['accounts'] if a['id'] != 'acct-06']
doc['accounts'].append({'id': 'acct-06', 'email': 'srv@cx', 'home': 'server'})
doc['accounts'].sort(key=lambda a: a['id'])
json.dump(doc, open(sys.argv[1], 'w'), indent=2)
EOF
out="$(python3 "$REPO_DIR/lib/codex_audit.py" "$CX" linux | awk -F'\t' '$1=="acct-06"{print $4}')"
[ "$out" = "missing" ] && t_ok "codex: home=server on the server means NO LOGIN, not 'elsewhere'" \
  || t_fail "codex home vocabulary" "expected missing on linux, got: $out"
out="$(python3 "$REPO_DIR/lib/codex_audit.py" "$CX" mac | awk -F'\t' '$1=="acct-06"{print $4}')"
[ "$out" = "remote" ] && t_ok "codex: home=server on the Mac is correctly 'elsewhere'" \
  || t_fail "codex home vocabulary" "expected remote on mac, got: $out"
python3 - "$CX/accounts.json" <<'EOF'
import json, sys
doc = json.load(open(sys.argv[1]))
doc['accounts'] = [a for a in doc['accounts'] if a['id'] != 'acct-06']
json.dump(doc, open(sys.argv[1], 'w'), indent=2)
EOF
rm -rf "$CX/acct-06"
cxemptypool="$WORK/cx-emptypool"
mkdir -p "$cxemptypool/tmp"
printf '{"version":1,"threshold":90,"accounts":[]}' > "$cxemptypool/accounts.json"
out="$(CODEX_ACCOUNTS_DIR="$cxemptypool" codex-accounts expired 2>&1)"
rc=$?
check "codex: expired on an empty pool says so" "No accounts registered yet" "$out"
[ "$rc" = "0" ] && t_ok "codex: empty pool exits 0" || t_fail "codex empty pool rc" "rc=$rc"

# ---- C17. security hardening --------------------------------------------------------
mkdir -p "$WORK/cx-canary" && : > "$WORK/cx-canary/DO_NOT_DELETE"
cp "$CX/accounts.json" "$WORK/cx-manifest.bak"
python3 - "$CX/accounts.json" <<'EOF'
import json, os, sys
d = json.load(open(sys.argv[1]))
d['accounts'].append({'id': '../cx-canary', 'email': 'evil@cx', 'home': 'mac'})
json.dump(d, open(sys.argv[1] + '.tmp', 'w'), indent=2); os.replace(sys.argv[1] + '.tmp', sys.argv[1])
EOF
out="$(codex-accounts remove ../cx-canary --yes 2>&1)"
rc=$?
[ -f "$WORK/cx-canary/DO_NOT_DELETE" ] && t_ok "codex: remove refuses path-traversal id" \
  || t_fail "codex path traversal" "remove ../cx-canary DELETED files outside the pool"
[ "$rc" != "0" ] && t_ok "codex: traversal id rejected nonzero" || t_fail "codex traversal rc" "rc=0"
codex-accounts list 2>&1 | grep -q "\.\./cx-canary" \
  && t_fail "codex id filter" "traversal id surfaced" \
  || t_ok "codex: invalid manifest ids are filtered out"
cp "$WORK/cx-manifest.bak" "$CX/accounts.json"
if [ "$(uname -s)" = "Darwin" ]; then
  python3 - "$CX/accounts.json" <<'EOF'
import json, os, sys
d = json.load(open(sys.argv[1]))
d['server_root'] = "/tmp/x'; touch /tmp/cx_multiacc_PWNED; #"
json.dump(d, open(sys.argv[1] + '.tmp', 'w'), indent=2); os.replace(sys.argv[1] + '.tmp', sys.argv[1])
EOF
  rm -f /tmp/cx_multiacc_PWNED
  out="$(CODEX_MULTIACC_NO_SYNC=0 codex-accounts sync 2>&1)"
  [ ! -f /tmp/cx_multiacc_PWNED ] && t_ok "codex: sync rejects injected server_root" \
    || { t_fail "codex command injection" "server_root injection EXECUTED"; rm -f /tmp/cx_multiacc_PWNED; }
  check "codex: injected server_root refused" "not a plain absolute path" "$out"
  cp "$WORK/cx-manifest.bak" "$CX/accounts.json"
  python3 - "$CX/accounts.json" <<'EOF'
import json, os, sys
d = json.load(open(sys.argv[1])); d['accounts'] = []
json.dump(d, open(sys.argv[1] + '.tmp', 'w'), indent=2); os.replace(sys.argv[1] + '.tmp', sys.argv[1])
EOF
  out="$(CODEX_MULTIACC_NO_SYNC=0 codex-accounts sync 2>&1)"
  rc=$?
  check "codex: empty manifest refuses to sync" "refusing to blank the target pools" "$out"
  [ "$rc" != "0" ] && t_ok "codex: empty-manifest sync exits nonzero" || t_fail "codex empty sync rc" "rc=0"
  cp "$WORK/cx-manifest.bak" "$CX/accounts.json"

  # replica + peer guards, codex flavor
  printf 'replica\n' > "$CX/sync-role"
  out="$(CODEX_MULTIACC_NO_SYNC=0 codex-accounts sync 2>&1)"
  rc=$?
  check "codex: replica pool refuses to push" "sync replica" "$out"
  [ "$rc" = "0" ] && t_ok "codex: replica sync exits 0" || t_fail "codex replica sync rc" "rc=$rc"
  # ...and a codex replica's auto_sync (fired by remove) is silent — no push, no nag
  out="$(CODEX_MULTIACC_NO_SYNC=0 codex-accounts import rep@cx --id acct-31 --no-sync 2>&1
         CODEX_MULTIACC_NO_SYNC=0 codex-accounts remove acct-31 --yes 2>&1)"
  case "$out" in *"sync failed"*) t_fail "codex replica auto_sync" "a replica mutation warned about sync: $out" ;;
    *) t_ok "codex: replica auto_sync is silent (no push, no warning)" ;; esac
  rm -f "$CX/sync-role"
  python3 - "$CX/accounts.json" <<'EOF'
import json, os, sys
d = json.load(open(sys.argv[1]))
d['peers'] = [{'target': 'gas@peer', 'root': "/tmp/x'; touch /tmp/cx_multiacc_PEER_PWNED; #", 'repo': '/tmp/y'}]
json.dump(d, open(sys.argv[1] + '.tmp', 'w'), indent=2); os.replace(sys.argv[1] + '.tmp', sys.argv[1])
EOF
  rm -f /tmp/cx_multiacc_PEER_PWNED
  out="$(CODEX_MULTIACC_NO_SYNC=0 codex-accounts sync 2>&1)"
  rc=$?
  check "codex: injected peer root refused" "peer root is not a plain absolute path" "$out"
  [ "$rc" != "0" ] && t_ok "codex: bad peer exits nonzero" || t_fail "codex peer validation rc" "rc=0"
  [ ! -f /tmp/cx_multiacc_PEER_PWNED ] && t_ok "codex: peer injection never executed" \
    || { t_fail "codex peer injection" "peer root injection EXECUTED"; rm -f /tmp/cx_multiacc_PEER_PWNED; }
  cp "$WORK/cx-manifest.bak" "$CX/accounts.json"
else
  t_ok "codex: sync validation tests skipped (Mac-only feature)"
fi

# ---- C18. status + npm layer --------------------------------------------------------
out="$(codex-accounts status 2>&1)"
check "codex: status shows threshold" "90%" "$out"
check "codex: status shows account" "a@cx" "$out"
check "codex: status shows plan" "plan pro" "$out"
if command -v node >/dev/null 2>&1; then
  out="$(node "$REPO_DIR/bin/cli.mjs" codex list 2>&1)"
  check "cli.mjs codex passthrough (list)" "a@cx" "$out"
  out="$(node "$REPO_DIR/bin/cli.mjs" --help 2>&1)"
  check "cli --help documents the codex pool" "codex-accounts" "$out"
else
  t_ok "codex npm-layer tests skipped (node not installed)"
fi

# ---- 20. panel pools: --json, credential transfer, instance roots, local sync -------
# Everything here runs against pools under $WORK/panel, addressed with the NEW
# CLAUDE_ACCOUNTS_ROOT/CODEX_ACCOUNTS_ROOT env — while CLAUDE_ACCOUNTS_DIR (the legacy
# spelling) still points at the suite's own pool, which is exactly the precedence a
# second app-robot instance on a shared machine depends on.
PP="$WORK/panel"
JP="$PP/claude-a"
JP2="$PP/claude-b"
PTOKEN="sk-ant-oat01-PANELPOOLTOKENPANELPOOLTOKENPANELPOOLTOKENPANELPOOL"
mkdir -p "$JP/acct-01" "$JP/acct-02" "$JP/acct-03" "$JP2"
# home must be THIS machine's kind: a credential-less home=mac account audits as
# 'remote' (its grant lives elsewhere) rather than 'missing' when the suite runs on
# Linux — which is what the publish CI runs on. Same rule as the relogin-redirect
# fixture (ddb3015).
PMK=mac; [ "$(uname -s)" = "Darwin" ] || PMK=linux
cat > "$JP/accounts.json" <<EOF
{
  "version": 1,
  "server": "root@203.0.113.7",
  "server_root": "/root/.claude-accounts",
  "server_repo": "/root/claude-multiacc",
  "threshold": 90,
  "accounts": [
    {"id": "acct-01", "email": "portable@test", "home": "$PMK", "added_at": "2026-01-02T03:04:05Z"},
    {"id": "acct-02", "email": "local@test", "home": "$PMK", "added_at": "2026-02-02T03:04:05Z"},
    {"id": "acct-03", "email": "nocred@test", "home": "$PMK", "added_at": "2026-03-02T03:04:05Z"}
  ]
}
EOF
printf '%s' "$PTOKEN" > "$JP/acct-01/server.token"
chmod 600 "$JP/acct-01/server.token"
printf '{"claudeAiOauth":{"accessToken":"a","refreshToken":"r","expiresAt":9999999999999,"refreshTokenExpiresAt":9999999999999}}' \
  > "$JP/acct-02/.credentials.json"
printf '{"version":1,"server":"none","server_root":"/root/.claude-accounts","server_repo":"/root/claude-multiacc","threshold":90,"accounts":[]}\n' \
  > "$JP2/accounts.json"
: > "$JP/.limits-kick"
: > "$JP2/.limits-kick"

CLAUDE_ACCOUNTS_ROOT="$JP" claude-accounts list --json > "$PP/list.json" 2>"$PP/list.err"
python3 - "$PP/list.json" "$JP" <<'EOF'
import json, sys
d = json.load(open(sys.argv[1]))
assert d["schema"] == "claude-multiacc/pool.v1", d["schema"]
assert d["provider"] == "claude" and d["kind"] == "list", d
assert d["pool"]["root"] == sys.argv[2], d["pool"]["root"]   # _ROOT beats legacy _DIR
by = {a["id"]: a for a in d["accounts"]}
assert by["acct-01"]["status"] == "active", by["acct-01"]
assert by["acct-01"]["credential_class"] == "portable" and by["acct-01"]["portable"], by["acct-01"]
assert by["acct-02"]["credential_class"] == "machine-local", by["acct-02"]
assert by["acct-02"]["portable"] is False, by["acct-02"]
assert by["acct-03"]["status"] == "missing" and by["acct-03"]["credential_class"] == "none", by["acct-03"]
assert by["acct-01"]["home_dir"].endswith("/acct-01"), by["acct-01"]
assert by["acct-01"]["email"] == "portable@test", by["acct-01"]
assert d["summary"] == {"total": 3, "active": 2, "limited": 0, "needs_login": 1,
                        "portable": 1, "selectable": 2,
                        # how the shim is CURRENTLY ranking: fresh | degraded | blind.
                        # This fixture has no telemetry at all, so: blind.
                        "telemetry": "blind",
                        "ranking_blind": True}, d["summary"]
assert d["pool"]["sync"]["mode"] == "server", d["pool"]["sync"]
EOF
[ $? -eq 0 ] && t_ok "list --json: stable schema, status/class per account, instance root" \
  || t_fail "list --json" "see $PP/list.json"
[ ! -s "$PP/list.err" ] && t_ok "list --json writes nothing to stderr" \
  || t_fail "list --json stderr" "$(head -c 120 "$PP/list.err")"

CLAUDE_ACCOUNTS_ROOT="$JP" claude-accounts status --json > "$PP/status.json"
python3 - "$PP/status.json" <<'EOF'
import json, sys
d = json.load(open(sys.argv[1]))
assert d["kind"] == "status", d["kind"]
a = {x["id"]: x for x in d["accounts"]}["acct-01"]
assert "last_picked" in a and "expired_marker" in a, a
assert a["credentials"]["token"] is True and a["credentials"]["oauth"] is False, a["credentials"]
assert a["credentials"]["token_age_days"] == 0, a["credentials"]
b = {x["id"]: x for x in d["accounts"]}["acct-02"]
assert b["credentials"]["oauth_refresh_expires_at"], b["credentials"]
EOF
[ $? -eq 0 ] && t_ok "status --json: adds last_picked + credential detail" \
  || t_fail "status --json" "see $PP/status.json"

cat > "$WORK/usage-panel.json" <<'EOF'
{"limits":[
  {"kind":"session","percent":12,"resets_at":"2099-01-01T00:00:00+00:00","scope":null},
  {"kind":"weekly_all","percent":93,"resets_at":"2099-01-03T00:00:00+00:00","scope":null}
]}
EOF
CLAUDE_ACCOUNTS_ROOT="$JP" CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-panel.json" \
  claude-accounts limits --json > "$PP/limits.json" 2>/dev/null
python3 - "$PP/limits.json" <<'EOF'
import json, sys
d = json.load(open(sys.argv[1]))
assert d["kind"] == "limits", d["kind"]
a = {x["id"]: x for x in d["accounts"]}["acct-01"]
assert a["usage"]["max_percent"] == 93, a["usage"]
assert a["usage"]["source"] == "token", a["usage"]
assert a["status"] == "limited" and a["limited"] is True, a
assert a["limit_reset_at"], a
assert a["selectable"] is False, a
# acct-02 has a live oauth credential, so it fetched the same fixture and parked too
assert d["summary"]["limited"] == 2, d["summary"]
EOF
[ $? -eq 0 ] && t_ok "limits --json: refreshes, then reports usage + limited state" \
  || t_fail "limits --json" "see $PP/limits.json"
rm -f "$JP/acct-01/.limited" "$JP/acct-02/.limited"

# ---- 20a. export-credential: portable only, exit codes a daemon can branch on -------
CLAUDE_ACCOUNTS_ROOT="$JP" claude-accounts export-credential acct-01 > "$PP/blob.json" 2>"$PP/blob.err"
rc=$?
[ "$rc" = "0" ] && t_ok "export-credential exits 0 for a portable account" \
  || t_fail "export rc" "rc=$rc $(head -c 120 "$PP/blob.err")"
python3 - "$PP/blob.json" "$PTOKEN" "$PMK" <<'EOF'
import json, sys
b = json.load(open(sys.argv[1]))
assert b["format"] == "claude-multiacc/credential" and b["version"] == 1, b
assert b["provider"] == "claude" and b["class"] == "portable", b
assert b["account"] == {"id": "acct-01", "email": "portable@test", "home": sys.argv[3],
                        "added_at": "2026-01-02T03:04:05Z"}, b["account"]
assert b["credential"] == {"type": "setup-token", "value": sys.argv[2]}, "credential mismatch"
assert b["exported_from"]["pool_root"].endswith("claude-a"), b["exported_from"]
EOF
[ $? -eq 0 ] && t_ok "export blob: self-contained credential + identity metadata" \
  || t_fail "export blob shape" "see $PP/blob.json"

out="$(CLAUDE_ACCOUNTS_ROOT="$JP" claude-accounts export-credential acct-02 2>&1)"
rc=$?
check "export refuses a machine-local credential" "MACHINE-LOCAL" "$out"
check "export says how to make it portable" "claude-accounts mint acct-02" "$out"
[ "$rc" = "3" ] && t_ok "machine-local export exits 3" || t_fail "machine-local exit code" "rc=$rc"
out="$(CLAUDE_ACCOUNTS_ROOT="$JP" claude-accounts export-credential acct-03 2>&1)"
rc=$?
check "export reports an account with no credential" "no credential material" "$out"
[ "$rc" = "4" ] && t_ok "credential-less export exits 4" || t_fail "no-credential exit code" "rc=$rc"
out="$(CLAUDE_ACCOUNTS_ROOT="$JP" claude-accounts export-credential acct-77 2>&1)"
[ $? != 0 ] && t_ok "export of an unregistered id fails" || t_fail "unknown id export" "exited 0"

CLAUDE_ACCOUNTS_ROOT="$JP" claude-accounts export-credential acct-02 --identity-only --out "$PP/ident.json" >/dev/null
rc=$?
python3 - "$PP/ident.json" <<'EOF'
import json, sys
b = json.load(open(sys.argv[1]))
assert b["class"] == "identity", b
assert "credential" not in b, "identity blob must carry no credential material"
assert b["account"]["email"] == "local@test", b
EOF
[ $? -eq 0 ] && [ "$rc" = "0" ] \
  && t_ok "export --identity-only works for a machine-local account (registry only)" \
  || t_fail "identity-only export" "rc=$rc, see $PP/ident.json"
CLAUDE_ACCOUNTS_ROOT="$JP" claude-accounts export-credential acct-01 --out "$PP/blob-out.json" >/dev/null
case "$(ls -l "$PP/blob-out.json" | cut -c1-10)" in
  -rw-------) t_ok "export --out writes the blob 0600" ;;
  *) t_fail "export --out perms" "$(ls -l "$PP/blob-out.json" | cut -c1-10)" ;;
esac

# ---- 20b. import-credential: faithful, idempotent, and picky ------------------------
out="$(CLAUDE_ACCOUNTS_ROOT="$JP2" claude-accounts import-credential < "$PP/blob.json" 2>&1)"
rc=$?
check "import-credential installs a portable credential" "Imported acct-01 (portable@test" "$out"
[ "$rc" = "0" ] && t_ok "import-credential exits 0" || t_fail "import rc" "rc=$rc"
python3 - "$JP2/accounts.json" "$JP2/acct-01/server.token" "$PTOKEN" "$PMK" <<'EOF'
import json, sys
accs = json.load(open(sys.argv[1]))["accounts"]
assert len(accs) == 1, accs
a = accs[0]
# byte-faithful metadata: the account reads identically on the second machine
assert a == {"id": "acct-01", "email": "portable@test", "home": sys.argv[4],
             "added_at": "2026-01-02T03:04:05Z"}, a
assert open(sys.argv[2]).read() == sys.argv[3], "token content changed in transfer"
EOF
[ $? -eq 0 ] && t_ok "imported account + credential round-trip unchanged" \
  || t_fail "import fidelity" "see $JP2"
case "$(ls -l "$JP2/acct-01/server.token" | cut -c1-10)" in
  -rw-------) t_ok "imported credential is 0600" ;;
  *) t_fail "imported credential perms" "$(ls -l "$JP2/acct-01/server.token" | cut -c1-10)" ;;
esac
# The write goes through a temp file in the account dir; none of it may survive.
[ -z "$(ls -a "$JP2/acct-01" | grep '^\.cred\.')" ] \
  && t_ok "no temp credential file is left behind by an import" \
  || t_fail "import leftovers" "$(ls -a "$JP2/acct-01" | grep '^\.cred\.')"
[ -z "$(ls -a "$JP2/tmp" 2>/dev/null | grep 'import-cred')" ] \
  && t_ok "no staged blob is left behind by an import" \
  || t_fail "staged blob leftovers" "$(ls -a "$JP2/tmp" | grep 'import-cred')"
CLAUDE_ACCOUNTS_ROOT="$JP2" claude-accounts import-credential --in "$PP/blob.json" >/dev/null 2>&1
n="$(python3 -c "import json;print(len(json.load(open('$JP2/accounts.json'))['accounts']))")"
[ "$n" = "1" ] && t_ok "re-importing the same account refreshes it, never duplicates" \
  || t_fail "import idempotence" "$n accounts after a second import"

python3 - "$PP/blob.json" "$PP/blob-apikey.json" <<'EOF'
import json, sys
b = json.load(open(sys.argv[1]))
b["credential"]["value"] = "sk-ant-api03-" + "A" * 60      # an API key, not a setup-token
json.dump(b, open(sys.argv[2], "w"))
EOF
out="$(CLAUDE_ACCOUNTS_ROOT="$JP2" claude-accounts import-credential --in "$PP/blob-apikey.json" 2>&1)"
rc=$?
check "import refuses an API key" "not a subscription setup-token" "$out"
[ "$rc" != "0" ] && t_ok "API-key import exits nonzero" || t_fail "api key import" "exited 0"

out="$(CODEX_ACCOUNTS_ROOT="$PP/codex-b" codex-accounts import-credential --in "$PP/blob.json" 2>&1)"
[ $? != 0 ] && t_ok "a claude blob cannot be imported into the codex pool" \
  || t_fail "cross-provider import" "exited 0"

out="$(CLAUDE_ACCOUNTS_ROOT="$JP2" claude-accounts import-credential --in "$PP/ident.json" 2>&1)"
check "identity-only import registers the account" "identity only, no credential" "$out"
check "identity-only import says what it still needs" "claude-accounts login" "$out"
[ ! -f "$JP2/acct-02/server.token" ] && t_ok "identity-only import installs no credential" \
  || t_fail "identity-only import" "wrote a credential file"

out="$(CLAUDE_ACCOUNTS_ROOT="$JP2" claude-accounts import-credential acct-02 --in "$PP/blob.json" 2>&1)"
rc=$?
check "import refuses to land the same email in a second slot" "already registered as acct-01" "$out"
[ "$rc" != "0" ] && t_ok "conflicting-id import exits nonzero" || t_fail "conflicting id" "exited 0"

# ---- 20c. instance isolation: the shim resolves the same root as the CLI ------------
out="$(CLAUDE_ACCOUNTS_ROOT="$JP2" claude 2>&1)"
check "shim honors CLAUDE_ACCOUNTS_ROOT (instance pool)" "CFG=acct-01" "$out"
[ -f "$JP2/selection.log" ] && t_ok "instance pool records its own selection log" \
  || t_fail "instance selection log" "missing at $JP2/selection.log"
grep -q "acct-01" "$ACC/selection.log" && ! grep -q "portable@test" "$ACC/accounts.json" \
  && t_ok "the default pool was untouched by the instance run" \
  || t_fail "pool isolation" "the instance run leaked into $ACC"

# ---- 20d. sync target: overridable, and a local-only mode that pushes nowhere -------
out="$(CLAUDE_ACCOUNTS_ROOT="$JP" CLAUDE_MULTIACC_NO_SYNC=0 claude-accounts sync --no-server 2>&1)"
rc=$?
check "sync --no-server stays local" "local-only" "$out"
[ "$rc" = "0" ] && t_ok "sync --no-server exits 0" || t_fail "sync --no-server rc" "rc=$rc"
out="$(CLAUDE_ACCOUNTS_ROOT="$JP" CLAUDE_MULTIACC_SYNC_TARGET=none CLAUDE_MULTIACC_NO_SYNC=0 \
       claude-accounts sync 2>&1)"
check "SYNC_TARGET=none makes sync local-only" "nothing pushed" "$out"
CLAUDE_ACCOUNTS_ROOT="$JP" CLAUDE_MULTIACC_SYNC_TARGET="ops@panel.example" \
  claude-accounts list --json > "$PP/list-target.json"
python3 - "$PP/list-target.json" <<'EOF'
import json, sys
s = json.load(open(sys.argv[1]))["pool"]["sync"]
assert s["mode"] == "server" and s["target"] == "ops@panel.example", s
EOF
[ $? -eq 0 ] && t_ok "SYNC_TARGET env overrides the manifest server" \
  || t_fail "sync target override" "see $PP/list-target.json"
out="$(CLAUDE_ACCOUNTS_ROOT="$JP2" CLAUDE_MULTIACC_NO_SYNC=0 \
       claude-accounts import-credential --in "$PP/blob.json" 2>&1)"
case "$out" in
  *"sync failed"*) t_fail "local-only auto_sync" "a local-only pool warned about a server push: $out" ;;
  *) t_ok "local-only pool: mutations never warn about a missing server" ;;
esac
# A replica pool must NEVER push — and pointing it at a local-only target must not
# become a way around that. Local mode narrows the rule (nothing pushes at all); the
# marker is still honored and still reported.
printf 'replica\n' > "$JP/sync-role"
out="$(CLAUDE_ACCOUNTS_ROOT="$JP" CLAUDE_MULTIACC_SYNC_TARGET=none CLAUDE_MULTIACC_NO_SYNC=0 \
       claude-accounts sync 2>&1)"
rc=$?
check "a replica in local-only mode still says it is a replica" "sync replica" "$out"
check "a replica in local-only mode pushes nothing" "nothing pushed either way" "$out"
[ "$rc" = "0" ] && t_ok "replica + local-only exits 0" || t_fail "replica local-only rc" "rc=$rc"
# `sync` refuses outright off the Mac ("sync runs on the Mac, not the server"), so the
# replica and target-injection rules can only be observed there. Stated as a skip rather
# than quietly asserting the wrong message on Linux — which is what broke the publish CI.
if [ "$(uname -s)" = "Darwin" ]; then
out="$(CLAUDE_ACCOUNTS_ROOT="$JP" CLAUDE_MULTIACC_NO_SYNC=0 claude-accounts sync 2>&1)"
check "a replica with a server target still refuses to push" "sync replica" "$out"
else
t_ok "server-target sync refusal skipped (sync is Mac-only; this host is $(uname -s))"
fi
out="$(CLAUDE_ACCOUNTS_ROOT="$JP" CLAUDE_MULTIACC_NO_SYNC=0 \
       claude-accounts import-credential --in "$PP/blob.json" 2>&1)"
case "$out" in
  *"sync failed"*|*"push"*) t_fail "replica auto_sync" "a replica mutation tried to push: $out" ;;
  *) t_ok "a mutation on a replica never pushes (auto_sync stays silent)" ;;
esac
rm -f "$JP/sync-role"

out="$(CLAUDE_ACCOUNTS_ROOT="$JP" CLAUDE_MULTIACC_SYNC_TARGET="ops@x; touch $WORK/PWNED" \
       CLAUDE_MULTIACC_NO_SYNC=0 claude-accounts sync 2>&1)"
rc=$?
if [ "$(uname -s)" = "Darwin" ]; then
  check "an injected SYNC_TARGET is refused" "not a plain user@host" "$out"
else
  t_ok "injected SYNC_TARGET refusal message skipped (sync is Mac-only here)"
fi
# The part that matters everywhere: refused, and never executed.
[ "$rc" != "0" ] && [ ! -f "$WORK/PWNED" ] && t_ok "injected sync target never executed" \
  || t_fail "sync target injection" "rc=$rc"

# ---- 20e. codex parity: no portable class, identity transfer only -------------------
CX2="$PP/codex-a"
mkdir -p "$CX2/acct-01"
cat > "$CX2/accounts.json" <<'EOF'
{"version":1,"server":"none","server_root":"/root/.codex-accounts","server_repo":"/root/claude-multiacc",
 "threshold":90,"accounts":[{"id":"acct-01","email":"cx@panel","home":"mac","added_at":"2026-04-05T06:07:08Z"}]}
EOF
python3 - "$CX2/acct-01/auth.json" <<'EOF'
import base64, json, sys, time
b = lambda o: base64.urlsafe_b64encode(json.dumps(o).encode()).decode().rstrip('=')
jwt = lambda c: b({"alg": "none"}) + '.' + b(c) + '.sig'
json.dump({"auth_mode": "chatgpt", "OPENAI_API_KEY": None,
           "tokens": {"id_token": jwt({"email": "cx@panel"}),
                      "access_token": jwt({"exp": time.time() + 9999}),
                      "refresh_token": "r", "account_id": "acc"},
           "last_refresh": "2026-08-01T00:00:00Z"}, open(sys.argv[1], "w"))
EOF
CODEX_ACCOUNTS_ROOT="$CX2" codex-accounts list --json > "$PP/cx-list.json"
python3 - "$PP/cx-list.json" "$CX2" <<'EOF'
import json, sys
d = json.load(open(sys.argv[1]))
assert d["provider"] == "codex" and d["pool"]["root"] == sys.argv[2], d["pool"]
a = d["accounts"][0]
assert a["status"] == "active", a
assert a["credential_class"] == "machine-local" and a["portable"] is False, a
assert d["summary"]["portable"] == 0, d["summary"]
assert d["pool"]["sync"]["mode"] == "local", d["pool"]["sync"]
EOF
[ $? -eq 0 ] && t_ok "codex list --json: same schema, no portable credentials" \
  || t_fail "codex list --json" "see $PP/cx-list.json"
out="$(CODEX_ACCOUNTS_ROOT="$CX2" codex-accounts export-credential acct-01 2>&1)"
rc=$?
check "codex export always refuses" "no portable credential type" "$out"
check "codex export points at the device-code login" "codex-accounts login acct-01" "$out"
[ "$rc" = "3" ] && t_ok "codex export exits 3 (machine-local)" || t_fail "codex export rc" "rc=$rc"
CODEX_ACCOUNTS_ROOT="$CX2" codex-accounts export-credential acct-01 --identity-only --out "$PP/cx-ident.json" >/dev/null
mkdir -p "$PP/codex-b"
printf '{"version":1,"server":"none","threshold":90,"accounts":[]}\n' > "$PP/codex-b/accounts.json"
out="$(CODEX_ACCOUNTS_ROOT="$PP/codex-b" codex-accounts import-credential --in "$PP/cx-ident.json" 2>&1)"
rc=$?
check "codex identity import registers the account" "Registered acct-01 (cx@panel" "$out"
check "codex identity import asks for a local sign-in" "codex-accounts login acct-01" "$out"
[ "$rc" = "0" ] && t_ok "codex identity import exits 0" || t_fail "codex identity import rc" "rc=$rc"
python3 - "$PP/codex-b/accounts.json" <<'EOF'
import json, sys
a = json.load(open(sys.argv[1]))["accounts"][0]
assert a["added_at"] == "2026-04-05T06:07:08Z", a    # source metadata preserved
EOF
[ $? -eq 0 ] && t_ok "codex identity transfer keeps the source added_at" \
  || t_fail "codex identity metadata" "see $PP/codex-b/accounts.json"

# ---- 20f. codex-review follow-ups: hostile pool roots, empty fields, marker rule ----
# A pool root carrying shell metacharacters must never reach a trap body or a remote
# command as code. (install.sh refuses to SCHEDULE such a root; the CLI must still be
# safe when one is used directly.)
QROOT="$PP/q'; touch $WORK/TRAP_PWNED; '"
mkdir -p "$QROOT"
printf '{"version":1,"server":"none","threshold":90,"accounts":[]}\n' > "$QROOT/accounts.json"
rm -f "$WORK/TRAP_PWNED"
out="$(CLAUDE_ACCOUNTS_ROOT="$QROOT" claude-accounts import-credential --in "$PP/blob.json" 2>&1)"
rc=$?
check "import works from a pool root with shell metacharacters" "Imported acct-01" "$out"
[ "$rc" = "0" ] && [ ! -f "$WORK/TRAP_PWNED" ] \
  && t_ok "a quoted pool root never executes as code (trap body)" \
  || { t_fail "pool root injection" "rc=$rc, sentinel=$([ -f "$WORK/TRAP_PWNED" ] && echo CREATED)"; rm -f "$WORK/TRAP_PWNED"; }
out="$(CLAUDE_ACCOUNTS_ROOT="$QROOT" CLAUDE_MULTIACC_USAGE_URL="file://$WORK/usage-panel.json" \
       claude-accounts limits --json 2>/dev/null | python3 -c "import json,sys;print(json.load(sys.stdin)['accounts'][0]['id'])" 2>&1)"
check "limits --json survives a quoted pool root" "acct-01" "$out"
[ ! -f "$WORK/TRAP_PWNED" ] && t_ok "the limits lock trap never executes a quoted root" \
  || { t_fail "limits trap injection" "sentinel created"; rm -f "$WORK/TRAP_PWNED"; }

# An empty metadata field must stay empty: the record separator is 0x1F precisely
# because bash collapses runs of IFS *whitespace*, which would shift added_at into home.
python3 - "$PP/blob.json" "$PP/blob-nohome.json" <<'EOF'
import json, sys
b = json.load(open(sys.argv[1]))
b["account"]["home"] = ""
b["account"]["email"] = "nohome@test"
json.dump(b, open(sys.argv[2], "w"))
EOF
NH="$PP/claude-nohome"
mkdir -p "$NH"
printf '{"version":1,"server":"none","threshold":90,"accounts":[]}\n' > "$NH/accounts.json"
CLAUDE_ACCOUNTS_ROOT="$NH" claude-accounts import-credential --in "$PP/blob-nohome.json" >/dev/null 2>&1
python3 - "$NH/accounts.json" <<'EOF'
import json, sys
a = json.load(open(sys.argv[1]))["accounts"][0]
assert a["email"] == "nohome@test", a
# home falls back to this machine, and added_at is NOT the value that would land there
# if the empty field had collapsed
assert a["home"] in ("mac", "linux"), a
assert a["added_at"] == "2026-01-02T03:04:05Z", a
EOF
[ $? -eq 0 ] && t_ok "a blob with an empty field imports without shifting the next one" \
  || t_fail "empty-field record" "see $NH/accounts.json"

# Metadata carrying a control character is refused rather than truncating the record.
python3 - "$PP/blob.json" "$PP/blob-ctrl.json" <<'EOF'
import json, sys
b = json.load(open(sys.argv[1]))
b["account"]["email"] = "evil@test\nacct-99"
json.dump(b, open(sys.argv[2], "w"))
EOF
out="$(CLAUDE_ACCOUNTS_ROOT="$NH" claude-accounts import-credential --in "$PP/blob-ctrl.json" 2>&1)"
rc=$?
check "metadata with a control character is refused" "control character" "$out"
[ "$rc" != "0" ] && t_ok "control-character blob exits nonzero" || t_fail "control char blob" "exited 0"

# An adopted account is a symlink: a credential must never be written THROUGH it,
# not even with --force (--force settles identity, it does not authorize escaping the pool).
mkdir -p "$PP/outside"
ln -s "$PP/outside" "$JP2/acct-09"
python3 - "$PP/blob.json" "$PP/blob-09.json" <<'EOF'
import json, sys
b = json.load(open(sys.argv[1]))
b["account"]["id"] = "acct-09"
json.dump(b, open(sys.argv[2], "w"))
EOF
out="$(CLAUDE_ACCOUNTS_ROOT="$JP2" claude-accounts import-credential acct-09 --in "$PP/blob-09.json" --force 2>&1)"
rc=$?
check "import refuses to write through an adopted symlink" "must never be written through it" "$out"
[ "$rc" != "0" ] && [ ! -f "$PP/outside/server.token" ] \
  && t_ok "--force does not authorize writing outside the pool" \
  || t_fail "symlink import" "rc=$rc, wrote=$([ -f "$PP/outside/server.token" ] && echo yes)"
rm -f "$JP2/acct-09"

# The exported blob must land 0600 even when a world-readable file is already there.
: > "$PP/pre-existing.json"
chmod 644 "$PP/pre-existing.json"
CLAUDE_ACCOUNTS_ROOT="$JP" claude-accounts export-credential acct-01 --out "$PP/pre-existing.json" >/dev/null
case "$(ls -l "$PP/pre-existing.json" | cut -c1-10)" in
  -rw-------) t_ok "export --out replaces a world-readable file with a 0600 one" ;;
  *) t_fail "export --out perms over existing file" "$(ls -l "$PP/pre-existing.json" | cut -c1-10)" ;;
esac

# The JSON report must apply the shim's marker rule exactly: a marker whose reset has
# passed is NOT limited (the shim deletes it and selects the account), while a garbled
# marker IS (the shim treats it as active rather than racing a concurrent write).
printf '%s\nbucket=weekly percent=95 reason=limits\n' "$((now-600))" > "$JP/acct-01/.limited"
out="$(CLAUDE_ACCOUNTS_ROOT="$JP" claude-accounts list --json | python3 -c "
import json,sys; a={x['id']:x for x in json.load(sys.stdin)['accounts']}['acct-01']
print(a['status'], a['limited'], a['selectable'])")"
check "an elapsed .limited marker does not read as limited" "active False True" "$out"
printf 'garbled\n' > "$JP/acct-01/.limited"
out="$(CLAUDE_ACCOUNTS_ROOT="$JP" claude-accounts list --json | python3 -c "
import json,sys; a={x['id']:x for x in json.load(sys.stdin)['accounts']}['acct-01']
print(a['status'], a['limited'])")"
check "a garbled .limited marker reads as limited (same as the shim)" "limited True" "$out"
rm -f "$JP/acct-01/.limited"

# --json must not swallow a following typo, and a broken refresh must not be reported
# as success just because the document rendered.
out="$(CLAUDE_ACCOUNTS_ROOT="$JP" claude-accounts list --json --bogus 2>&1)"
rc=$?
check "list --json rejects an unknown extra option" "unknown option: --bogus" "$out"
[ "$rc" != "0" ] && t_ok "list --json --bogus exits nonzero" || t_fail "strict --json parsing" "exited 0"
BROKEN="$PP/claude-broken"
mkdir -p "$BROKEN"
printf 'not json at all\n' > "$BROKEN/accounts.json"
out="$(CLAUDE_ACCOUNTS_ROOT="$BROKEN" claude-accounts limits --json 2>/dev/null)"
rc=$?
[ "$rc" != "0" ] && t_ok "limits --json propagates a failed refresh (nonzero)" \
  || t_fail "limits --json exit code" "exited 0 on an unreadable manifest"
printf '%s' "$out" | python3 -c "
import json,sys
d=json.load(sys.stdin)
assert d['accounts'] == [], d
assert any('manifest' in w for w in d['warnings']), d['warnings']
" 2>/dev/null \
  && t_ok "limits --json still emits a document (with a warning) when the pool is broken" \
  || t_fail "limits --json on a broken pool" "no usable document"

# ---- 46. a setup token has no identity: the --token paths must not pretend --------
# Regression for two field failures on 2026-08-24: `add <email> --token` died every time
# with "identity could not be read back" (it demanded --force for a ceremony that cannot
# exist), and `mint` named no account at all — so approving in the wrong browser session
# silently pinned another account's subscription to the slot, undetectably.
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 claude-accounts add --token 2>&1)"
rc=$?
check "add --token with no email refuses precisely" "name it: claude-accounts add <email> --token" "$out"
[ "$rc" != "0" ] && t_ok "add --token with no email exits nonzero" || t_fail "add --token no email" "exited 0"
[ ! -d "$ACC/acct-04" ] && t_ok "refused --token add leaves no dir behind" || t_fail "add --token cleanup" "dir left"
# ...and it must refuse BEFORE the ceremony: minting a real 1-year grant only to discard
# it would leave a live credential issued for nothing.
case "$out" in
  *"sign-in link"*) t_fail "add --token refuses before the ceremony" "setup-token already ran for an account it cannot name" ;;
  *) t_ok "add --token with no email never opens the ceremony" ;;
esac

# mint must refuse a bare acct-NN directory the manifest does not know: removed accounts
# and killed `add` runs leave those behind, and a token bound to one is unattributable.
mkdir -p "$ACC/acct-77"
out="$(printf 'sk-ant-oat01-ORPHANORPHANORPHANORPHANORPHANORPHAN\n' \
  | claude-accounts mint acct-77 --paste 2>&1)"
check "mint refuses an unregistered account dir" "unknown account: acct-77" "$out"
[ ! -s "$ACC/acct-77/server.token" ] && t_ok "no token is written into an orphan dir" \
  || t_fail "mint orphan dir" "server.token was written to an unregistered slot"
rm -rf "$ACC/acct-77"

# mint must NAME the account it is about to bind a token to (the only guard there is).
printf 'sk-ant-oat01-MINTNAMEDMINTNAMEDMINTNAMEDMINTNAMEDMINTNAMED\n' \
  | claude-accounts mint acct-01 --paste > "$WORK/mint-named.out" 2>&1
out="$(cat "$WORK/mint-named.out")"
check "mint --paste names the account in its prompt" "for acct-01" "$out"
check "mint warns that a setup token carries no identity" "carries no identity" "$out"

# ---- 47. every known verb answers --help with exit 0 (app-robot probes with it) ----
# app-robot's runner asks `<verb> --help` to decide whether a Mac's build has the verb;
# a non-zero exit reads as "too old" and parked panel-to-Mac credential distribution.
# _KNOWN_VERBS must hold EXACTLY what the dispatcher implements. A verb missing from it
# hides a real verb from the probe; a verb listed but unimplemented makes --help answer 0
# for something that does not exist, which is how the probe stops meaning anything. The
# first cut of this gate got both wrong (mint listed in codex, init-pool in claude), so
# the parity is checked from the source, both directions, for both binaries.
for _bin in claude-accounts codex-accounts; do
  _src="$REPO_DIR/bin/$_bin"
  _dispatch="$(grep -oE '^  [a-z0-9|_-]+\) shift; cmd_' "$_src" | sed -e 's/) shift; cmd_//' -e 's/^  //' | tr '|\n' '  ')"
  _gate="$(grep -m1 '^_KNOWN_VERBS=' "$_src" | sed -e 's/^_KNOWN_VERBS="//' -e 's/"$//')"
  _parity=1
  for _v in $_dispatch; do
    case " $_gate " in
      *" $_v "*) ;;
      *) _parity=0; t_fail "$_bin: dispatcher has '$_v', _KNOWN_VERBS does not" "app-robot's probe would read the verb as absent" ;;
    esac
    "$_bin" "$_v" --help >/dev/null 2>&1
    [ "$?" = "0" ] && t_ok "$_bin $_v --help exits 0" \
      || t_fail "$_bin $_v --help" "non-zero exit — app-robot would read the verb as missing"
  done
  for _v in $_gate; do
    case " $_dispatch " in
      *" $_v "*) ;;
      *) _parity=0; t_fail "$_bin: _KNOWN_VERBS lists '$_v', the dispatcher does not implement it" "--help would answer 0 for a verb that does not exist" ;;
    esac
  done
  [ "$_parity" = "1" ] && t_ok "$_bin: the help gate and the dispatcher list the same verbs"
  "$_bin" frobnicate --help >/dev/null 2>&1
  [ "$?" != "0" ] && t_ok "$_bin: an UNKNOWN verb still fails --help (the probe keeps its meaning)" \
    || t_fail "$_bin unknown verb --help" "exited 0 — the probe would accept a verb that does not exist"
done

# ---- summary ---------------------------------------------------------------------
echo
echo "passed: $PASS  failed: $FAIL"
[ "$FAIL" = "0" ] || exit 1
