> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tayho.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Incident response

> Alert-linked runbooks for Tayho RPC, database, queue, signer, intent, indexer, API, and security incidents.

# Keeper incident response

Every incident starts by naming an incident commander, setting `OPERATOR_ID`, preserving
the current deployment ID and config fingerprint, and opening the Tayho Keeper dashboard.
Never delete Redis jobs or transaction rows manually. PostgreSQL remains authoritative.

For every drill or incident, retain the UTC timeline, operator identity, commit SHA, immutable
keeper/frontend image digests, Railway deployment IDs, dashboard snapshot, relevant trace IDs,
operator-command output, and the final audit-event IDs. Redact secrets and signed transaction bytes.

The incident commander owns containment and evidence. The service owner diagnoses and executes
approved recovery; security owns key compromise and protocol-integrity decisions; the release owner
owns deployment rollback. See [Observability](/operations/observability) for signal triage and the
[Security model](/operations/security-model) for authority boundaries.

## RPC outage

1. Determine whether the write RPC, one read voter, or quorum is unavailable. Compare endpoint
   circuit state, latency, chain ID, latest head, pending nonce, and receipt agreement.
2. If the read quorum is lost, drain all signing lanes. Do not lower the quorum or add two endpoints
   from the same provider to restore readiness cosmetically.
3. If only the write RPC failed, leave transaction journal entries intact and switch `RPC_URL` to an
   independently operated, chain-verified endpoint. The reconciler must resolve every ambiguous
   broadcast before the lane allocates another nonce.
4. Restore one replica per affected role, verify quorum head and receipt canonicality, then resume
   the second replicas. Retain endpoint/provider evidence and the recovery-time measurement.

## Database failover

1. Drain signing lanes if the database is reachable; otherwise let readiness fail closed and do not
   start replacement workers against an unverified database.
2. Promote or restore the Railway Postgres service according to the provider procedure. Confirm TLS,
   restricted runtime identity, schema version, migration lock state, and point-in-time target.
3. Before workers resume, compare nonterminal job/transaction lineages and every signer nonce with
   the independent RPC quorum. Run the duplicate-enqueue and restricted-write probes.
4. Resume one replica per role, observe the SLO gate, then restore the second replicas. Record actual
   RTO and RPO and attach the restore/failover evidence to the launch manifest.

## Nonce gap

1. Run `bun run jobs tx-pending` and inspect every lineage around the first missing nonce with
   `bun run jobs tx-show <transaction-id>`.
2. Compare the database reservation, deterministic transaction hashes, and pending/latest nonce on
   every quorum voter. Never send a filler transaction or edit `signer_nonces` manually.
3. If signed ciphertext exists, let reconciliation rebroadcast the identical bytes or its bounded
   same-nonce replacement. If allocation is unsigned, only the built-in tail-release path may abandon it.
4. Drain the whole signer lane if the source cannot be proven. Resume only after the gap is mined,
   reverted, safely released, or escalated for a signed operational-security decision.

## Stale oracle

1. Confirm timestamp, L1-head liveness, BBO validity, depth attestation, and whether the condition is
   isolated to one market or the attester/RPC plane.
2. Verify the on-chain market is paused. If automation has not paused it, use the authorized guardian
   procedure; do not substitute a manual price or relax staleness/depth limits during the incident.
3. Stop new intents durably with
   `OPERATOR_ID=<incident> bun run jobs intent-market-pause <chain-id> <market-id> <reason>`.
   Existing reduce-only and protective automation continues; do not stop it by scaling the engine down.
4. Resume only after independent price/depth comparison, a fresh attestation, and a canary cycle.
   Record the exact stale interval and positions affected.

## Mass liquidation

1. Distinguish a genuine market move from stale/crossed books, bad scaling, attester compromise, or
   duplicated discovery. Pause the market immediately if data integrity is uncertain.
2. Inspect liquidation queue age, block-share limits, simulations, vault buffer, queued USDC claims,
   signer balances, and RPC acceptance. Never bypass per-item simulation or batch gas caps.
3. If the move is genuine, keep bounded permissionless batches running and scale discovery separately
   from execution. Do not raise exposure, maintenance, or batching limits during the event.
4. Reconcile every submitted batch and child result before replay. After stabilization, account for
   liquidations, claims, haircut, TAYHO issuance, and error-budget consumption against chain receipts.

## Funding observation outage

1. Confirm whether impact, index, Redis sampling, attester authorization, RPC publication, or
   automation advancement is the failing stage. Keep exits available; do not bypass freshness to open risk.
2. Compare raw samples from both independent origins and inspect window coverage/gaps. Never synthesize
   a missing interval from a later price, reuse one source as both inputs, or manually change the formula.
3. If either source or a funding key may be compromised, have a guardian pause affected markets, revoke
   both funding attesters, preserve Redis/provider evidence, and rotate keys plus the affected source.
4. Resume with testnet/canary publication, verify the auditor's recomputed rate/delta/reserve and vault-wide
   reserve reconciliation, then unpause. Missed intervals remain uncharged.

## Deployment rollback

1. Stop promotion immediately. Preserve the failed environment JSONL, deployment IDs, image digests,
   migration deployment, dashboard state, and first failing trace.
2. Any promotion failure, timeout, cancellation, runner loss, failed canary, or failed evidence
   upload activates the separate rollback job through its environment-scoped token. It restores every
   production runtime service to the supplied last-known-good digests and retains independent
   rollback evidence. Verify those deployment digests; never rebuild or use a mutable tag.
3. If automation itself failed, use `railway environment edit ... source.image <previous@sha256>` for
   each touched service while signing roles remain drained. Database migrations
   must be forward-compatible or have a separately reviewed corrective migration; never downgrade
   schema by hand.
4. Run the canary gate against the restored services, reconcile nonterminal transactions, then record
   the rollback with `OPERATOR_ID=<incident> bun run jobs deployment-record <digest>`.

## Job failure burn

1. Identify the affected role and outcome labels. Compare queue age, RPC health, signer
   balance, database health, and the latest deployment marker.
2. Drain the affected instance with `bun run jobs drain <instance-id> 300`.
3. Inspect dead jobs with `bun run jobs dead-list` and one lineage with
   `bun run jobs job-show <job-id>` plus `bun run jobs tx-show <transaction-id>`.
4. Replay only after the dependency is healthy: `bun run jobs dead-replay <job-id>`.
5. If burn continues, roll Railway back to the last healthy deployment. Record the rollback.

## Queue backlog

1. Check PostgreSQL outbox age, BullMQ depth, Redis memory, and worker heartbeats.
2. Do not raise concurrency for a signing role while it has a nonterminal transaction.
3. Scale discovery separately from execution. Add replicas only after confirming database
   pool headroom and RPC limits.
4. If Redis was lost, keep workers drained, restore Redis, let the outbox republish, verify
   idempotency keys, then resume one canary replica before the rest.

## Stuck transaction

1. Run `bun run jobs tx-pending`, then `bun run jobs tx-show <transaction-id>`.
2. Compare pending/latest nonces and every configured RPC receipt. Never create a fresh
   transaction for the same job or manually advance the database nonce.
3. Let the reconciler rebroadcast the exact encrypted bytes or produce bounded EIP-1559
   replacements. If replacement budget is exhausted, drain the signer and escalate.
4. Resume only after the journal lineage is mined, reverted, superseded, or explicitly
   abandoned before signing.

## Signer balance

1. Confirm the address is a worker role and not an owner, treasury, or oracle identity.
2. Fund only the minimum operating amount from the approved treasury procedure.
3. Verify `tayho_signer_balance_wei` recovers and readiness becomes healthy.
4. Record the funding transaction in the incident timeline.

## Journal-key rotation

1. Add the new key to `TX_JOURNAL_KEYS` while retaining every old key; set the new active ID.
2. Deploy one canary replica and confirm it writes the new key ID and decrypts older lineages.
3. Deploy all replicas. Run `bun run jobs key-status`.
4. Only after `OPERATOR_ID=... bun run jobs key-retire-check <old-id>` succeeds may the old
   key be removed. The command refuses active keys, live lineages, and terminal ciphertext still
   inside `JOURNAL_KEY_RETENTION_SECONDS` (30 days minimum). It also requires a fresh independent-RPC
   quorum head, proves every affected lineage is beyond `TX_REORG_LOOKBACK_BLOCKS`, and compares
   every persisted receipt-block hash with a majority-agreed canonical block hash.
5. Redeploy, verify reconciliation, and retain the audit record. No remote signer adapter is used.

## Signer rotation

1. Register the replacement signer on-chain without removing the old signer.
2. Run `OPERATOR_ID=... bun run jobs signer-drain <old-address> 120`; both replicas keep serving from
   the other key while PostgreSQL prevents new allocations to the old lane.
3. Run `OPERATOR_ID=... bun run jobs signer-retire-check <old-address>` to prove every terminal
   lineage is outside the retention and reorg window against an independent-RPC quorum. Within 15
   minutes of that successful audited proof, run `OPERATOR_ID=... bun run jobs signer-pool-rotate <chain-id> <environment:role> <old-address> <new-address>`, replace the old entry in the service-scoped
   `SIGNER_KEYS` secret, and immediately roll both replicas. Old replicas fail pool allocation after
   the fenced handoff; new replicas must match the durable two-address membership. Readiness verifies
   both keys' balance, narrow authorization, and identity separation.
4. Re-run `OPERATOR_ID=... bun run jobs signer-retire-check <old-address>` before on-chain removal. It rejects live lineages
   and terminal lineages inside `SIGNER_RETIRE_FINALITY_SECONDS` (24 hours minimum), then applies
   the same independent-RPC finality proof used by journal-key retirement.
5. Remove the old signer on-chain only after the command succeeds, then archive the local key.

## Protocol audit finding

1. Open the finding with `bun run jobs audit-list open` and `bun run jobs audit-show <finding-id>`.
   Record the pinned block/hash, check, subject, expected/observed values, evidence, trace context,
   and linked audit job before changing protocol or worker state.
2. Treat every critical result as real until independently disproved. For reserve, solvency,
   permission, topology, code-hash, or projection findings, pause new engine intake and drain the
   relevant signing lanes. Pause affected markets through the guardian procedure when user funds or
   price integrity may be at risk.
3. Compare the pinned state through all three RPC providers. Inspect the relevant contract storage,
   receipts, database job/outbox/transaction metadata, and event projection. Never resolve a finding
   merely to clear an alert.
4. Record ownership with `OPERATOR_ID=<incident> bun run jobs audit-ack <finding-id>`. A suppression
   requires a reason and expires within 24 hours:
   `OPERATOR_ID=<incident> bun run jobs audit-suppress <finding-id> <seconds> <reason>`.
   Suppressed findings remain in history but leave the actionable Prometheus gauge on the next
   audit; expiry automatically reopens them.
5. After remediation, run `OPERATOR_ID=<incident> bun run jobs audit-recheck`. Resolve manually only
   when the evidence is understood; any failing audit reopens it automatically. Retain the finding,
   audit run, operator events, remediation transaction hashes, and review decision.

## Auditor stale

1. Confirm the single `tayho-auditor` Railway instance, `/readyz`, Prometheus scrape, PostgreSQL,
   Redis/BullMQ, and all three independent RPC providers. The auditor has no signer and must never be
   made healthy by adding a private key or weakening quorum.
2. Inspect auditor jobs with `bun run jobs job-show <job-id>`, dead work with `bun run jobs dead-list`,
   and the dashboard's audit age, duration, RPC-disagreement, and projection-lag panels. Replay only
   after the dependency is healthy.
3. On a checkpoint hash mismatch, let the automatic reorg rewind run. If a full reconciliation keeps
   reporting projection drift, preserve evidence and run
   `OPERATOR_ID=<incident> bun run jobs audit-rebuild-projection <chain-id> <deployment-block>`.
   With the auditor stopped, supersede obsolete scheduled audits using
   `OPERATOR_ID=<incident> bun run jobs audit-backlog-supersede <chain-id>`, clear only that
   BullMQ role queue, then schedule one `audit-recheck` before restarting the singleton.
   After the fresh rebuild starts, `audit-backlog-supersede <chain-id> dead-only` can close the
   retained historical delivery failures without touching the active audit; attempt and audit-event
   evidence remains durable.
   The deployment block must precede initial worker registration.
4. Restart the one Railway instance only after confirming there is no duplicate auditor deployment.
   PostgreSQL idempotency, BullMQ fencing, and the durable event checkpoint make restart safe. Do not
   promote a release or resume drained traffic until a fresh pinned audit completes with no critical
   findings and projection lag is within the configured bound.

## Engine replica loss

1. Confirm both `tayho-engine` Railway replicas, `/readyz`, Prometheus targets, PostgreSQL, Redis,
   and the independent RPC quorum. The engine is signerless; never add a key to restore readiness.
2. Preserve request/intent trace IDs and check intake acknowledgement, validation age, checkpoint lag,
   trigger backlog, index outbox lag, and PostgreSQL connection saturation.
3. Restart only the missing replica using the reviewed digest. Fencing prevents duplicate market work;
   do not clear leases, Redis keys, or checkpoints manually.
4. Resume promotion only after two replicas are scraped for ten minutes and the intent auditor reports
   zero projection/reservation drift.

## Intent intake burn

1. Separate client rejection from rate-limit pressure, dependency unavailability, and PostgreSQL commit
   latency using `tayho_intent_requests_total` and the acknowledgement histogram.
2. Inspect affected state with `bun run jobs intent-list` and `bun run jobs intent-show <chain-id> <hash>`.
   A `Received` response is durable receipt, never proof of execution.
3. If one market is abusive, pause only its intake with `intent-market-pause`; do not raise global limits
   during an incident. Existing automatic exits continue to run.
4. Resume with `OPERATOR_ID=<incident> bun run jobs intent-market-resume <chain-id> <market-id>` only
   after database and rate-limit headroom recover. Retain the pause/resume audit-event IDs.

## Intent engine lag

1. Compare validation age, index-outbox lag, Redis checksum drift, checkpoint block/hash, trigger backlog,
   trigger-to-queue latency, RPC disagreement, BullMQ depth, and relayer batch utilization.
2. PostgreSQL is authoritative. If Redis drift is confirmed, preserve both summaries and run
   `OPERATOR_ID=<incident> REDIS_URL=... bun run jobs intent-index-rebuild <chain-id>`; never edit keys.
3. Revalidate only an eligible funding-blocked intent with
   `OPERATOR_ID=<incident> bun run jobs intent-revalidate <chain-id> <order-hash>`.
4. For RPC disagreement, keep the market paused at its durable checkpoint until independent providers
   agree. Never lower quorum or manually advance a checkpoint.

## Intent reservation drift

1. Treat the auditor finding as critical. Inspect the finding, intent lifecycle/events, account funding
   snapshot, held reservations, OCO siblings, and linked settlement job without decrypting payloads.
2. Pause intake for affected markets. Do not edit reserved totals or release reservations manually.
3. Rebuild Redis only if its checksum differs; reservation truth is PostgreSQL and must be repaired by a
   reviewed forward migration or replay of the fenced lifecycle operation.
4. Run a fresh full audit and retain before/after counts, checksums, trace IDs, and audit events.

## Funding blocked intents

1. Check Permit2 allowance/expiry, wallet balance, session remaining spend, position ownership, and the
   pinned validation block. Funding blocks are soft-reservation outcomes, not engine failures.
2. Do not spend or reserve collateral on behalf of a trader. After funds return, use automatic refresh or
   the audited `intent-revalidate` command for a specific order.
3. If growth is systemic, pause new opening-intent intake while protective closes continue, and inspect
   frontend/session-cap guidance before resuming.

## Intent key rotation

1. Add the new local key to the shared engine/relayer `INTENT_KEYRING`, set `INTENT_ACTIVE_KEY_ID`, and
   deploy one engine and one relayer canary while retaining all prior keys.
2. Run `bun run jobs intent-key-status`; verify new ciphertext uses the active ID and old executable
   intents still validate and settle.
3. Deploy both replicas. `OPERATOR_ID=<change> bun run jobs intent-key-retire-check <old-id>` must report
   zero executable-intent blockers and refuse the active key before it may be removed.
4. Remove the key from both roles in one controlled rollout and retain the command and deployment audit
   events. No remote KMS/HSM/MPC adapter is introduced.

## Reorg

The reconciler verifies canonical block hashes inside the configured lookback. An orphaned
receipt reopens the durable job, fences stale execution, and rebroadcasts the canonical
lineage. Page if reorg count is nonzero; pause affected markets if finality assumptions no
longer hold. Never mark a job complete from one RPC response.

## Indexer lag or failure

1. Check the single `tayho-indexer` writer's `/health`, `/ready`, `/status`, JSON logs, and Prometheus
   target. Compare `ponder_sync_block_timestamp` with `ponder_indexing_timestamp` to separate RPC
   ingestion lag from slow handler/database work.
2. For RPC errors, preserve provider/error labels and verify both configured HTTP origins independently.
   Rotate or replace the failing endpoint; do not change `PONDER_START_BLOCK`, skip a block range, or
   point production at an unqualified single provider.
3. For database errors, stop release promotion and restore the separately owned `tayho_indexer`
   logical database. Do not point Ponder at the keeper's `railway` logical database and do not edit
   projection rows manually.
4. A reorg counter increase is evidence that Ponder automatically rewound its transaction log. Compare
   `/status` with the independent auditor and wait for `/ready`; never replay handlers or delete a
   deployment schema by hand.
5. If a deployment remains in backfill, keep the prior Railway deployment serving. Inspect RPC limits,
   database latency below 50 ms, pool saturation, and handler duration. Roll back the indexer image if
   the candidate cannot reach realtime within the release window.
6. If the active projection is corrupt, restore the indexer database to an isolated sibling, start the
   reviewed image from the original earliest deployment block, and compare market/position/funding
   counts and sampled event identities against chain plus the independent auditor before switching
   traffic. The engine and trading workers do not depend on Ponder for execution and must not be made
   to trust a hurried projection repair.

## PostgreSQL restore drill

1. In testnet, choose a target time in Railway Postgres PITR history. Restore the coordination and
   indexer logical databases to isolated sibling services.
2. Point one isolated auditor at the restored auditor-scoped `DATABASE_URL`; never overwrite production.
3. Run migrations, verify the expected schema version, constraints, worker history, audit events, outbox
   backlog, signer nonces, and every nonterminal transaction hash against chain quorum.
4. Prove that duplicate enqueue and transaction reservation remain fenced.
5. Destroy the isolated fork only after evidence is retained. Repeat monthly and before launch.

## API degradation

1. Confirm both `tayho-api` replicas, their `/readyz` responses, Prometheus targets, current image
   digests, and the Cloudflare-to-Railway path. Read the readiness breakdown for engine, indexer,
   RPC, and Redis. Never add database, signer, journal, or encryption credentials to restore it.
2. Split failures by `source` in `tayho_api_upstream_requests_total`. For `engine`, preserve the
   request ID and upstream trace ID and follow the intent-engine runbook. For `indexer`, follow the
   indexer runbook. A failed intent POST was not retried by the API; clients may repeat the same
   signed intent safely and receive its idempotent order-hash acknowledgement.
3. Compare the failing route and status class, then call the private upstream readiness endpoint from
   the API environment. Do not extend write timeouts or add automatic POST retries during an incident.
4. Roll only one API replica to the last-known-good qualified digest, observe it, then restore the
   second. Rotate `ENGINE_INTERNAL_TOKEN` on both API and engine services together if compromise is
   suspected; mismatched credentials intentionally fail closed.

## Realtime degradation

1. Check `tayho_api_realtime_pump_total`, `tayho_api_websocket_connections`, rejected protocol
   messages, and `slow_consumer` deliveries by replica. Verify Redis latency and stream length with
   `XLEN tayho:realtime:v1`; never delete the stream during diagnosis.
2. If the indexer pump fails, confirm one replica can renew `tayho:realtime:v1:lease:indexer-pump`
   and reach the private indexer feed. A leader loss is safe: another replica takes the expiring
   lease and event IDs deduplicate overlap.
3. If Redis is unavailable, REST snapshots remain the recovery path but readiness intentionally
   drains API replicas. Restore Redis, allow the pump to resume from its persisted cursor, and test
   a WebSocket snapshot plus reconnect replay before returning traffic.
4. Slow consumers are closed with a retryable code. Do not increase buffers during an incident;
   clients reconnect with `resumeFrom`, or accept `resync_required` and a fresh snapshot.

## Market data degradation

1. Check both `tayho-market-data` replicas, `/readyz`, the upstream circuit gauge, and failures split
   by `book` versus `metadata`. Preserve the selected asset, request ID, upstream timestamp, and image
   digest; never log bearer credentials or raw authorization headers.
2. Query Hyperliquid `outcomeMeta` and the affected `l2Book` directly. Confirm the outcome is still
   active, USDC quoted, two-sided, monotonic, and inside the configured freshness window. A thin,
   crossed, empty, or stale book is a valid fail-closed result, not a reason to relax validation.
3. If only one replica is unhealthy, drain and roll it to the last-known-good qualified digest. If the
   shared upstream is unhealthy, leave funding/depth publication stopped; exits remain available and
   guardian staleness/depth controls protect affected markets.
4. Resolution must remain pending unless an explicit final side is available from the governed source.
   Never infer a result from a price near zero/one, manually patch an observation, or reuse a funding
   token for depth/resolution.
5. After recovery, run the HIP-4 upstream qualification workflow, confirm fresh authenticated samples
   from all four routes, then observe one complete 15-minute funding interval before clearing the incident.

## Security compromise

Drain the affected role, revoke it on-chain, rotate its local signer, rotate journal encryption
keys if ciphertext confidentiality may be affected, and rotate database/Redis/RPC credentials.
Preserve audit events, deployment metadata, logs, traces, and the affected image digest. Do not
replay dead letters until the new signer passes readiness and separation checks.
For obsolete revenue `skim` dead letters that never acquired transaction lineage, use the audited
`periodic-backlog-supersede revenue skim` command. It fails closed if any candidate has transaction
lineage; never use it for user orders or transaction-producing retries.
