Recovery runbook (Fase 7)
Executable checks live in scripts/verify_recovery.py. Run after any
infra change or before a demo:
PYTHONPATH=backend/src python scripts/verify_recovery.py
1. Process / container restart
docker compose --profile app up -d- Confirm
GET /ready→{"status":"ready"} - Confirm
GET /healthshowsdatabase.status=okwhen Postgres is used - Re-open a prior case via
GET /api/investigations/{case_id}— steps and evidence must still load from PostgreSQL (not wiped by backend restart) - Live events for new investigations are dual-written to memory +
truelock.operational_events(external_case_key)
2. Provider failure / failover
- Configure at least two keys (
GEMINI_KEY_A,GEMINI_KEY_B) or leave both empty for offline deterministic mode - On HTTP 429 / 5xx the router marks the provider failed and emits
ROUTING_EVENT {from,to,reason}(visible underGET /api/metricsandGET /api/events?case_id=OPS) - When no healthy provider remains, Gemini returns offline fallback; the investigator continues with the deterministic tool plan
3. Hard budget stop
- Session spend is tracked in the usage ledger
- When spend ≥
hard_budget_stop_usd(default 280), the client refuses new model calls and falls back offline — the agent cannot exceed the budget - Step/time budgets (
MAX_INVESTIGATION_STEPS/MAX_INVESTIGATION_SECONDS) still forceESCALATEindependently
4. Concurrent investigations
- Concurrent
POST /api/investigations/startfor the same lead is serialized by an in-process lock - With Postgres, identical lead + dataset fingerprint returns the persisted investigation (idempotent)
5. Frontend / UI errors
- Lead load and investigate failures surface as visible error banners
- Q&A without evidence shows insufficiency + empty evidence refs
6. Database recovery
docker compose up -d postgres
bash scripts/migrate.sh
bash scripts/seed_demo.sh
bash scripts/test_database.sh
If migrations fail midway, fix forward-only — do not rewrite applied files.