Keeper incident response
Every incident starts by naming an incident commander, settingOPERATOR_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 for signal triage and the
Security model for authority boundaries.
RPC outage
- 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.
- 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.
- If only the write RPC failed, leave transaction journal entries intact and switch
RPC_URLto an independently operated, chain-verified endpoint. The reconciler must resolve every ambiguous broadcast before the lane allocates another nonce. - 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
- Drain signing lanes if the database is reachable; otherwise let readiness fail closed and do not start replacement workers against an unverified database.
- 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.
- 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.
- 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
- Run
bun run jobs tx-pendingand inspect every lineage around the first missing nonce withbun run jobs tx-show <transaction-id>. - Compare the database reservation, deterministic transaction hashes, and pending/latest nonce on
every quorum voter. Never send a filler transaction or edit
signer_noncesmanually. - 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.
- 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
- Confirm timestamp, L1-head liveness, BBO validity, depth attestation, and whether the condition is isolated to one market or the attester/RPC plane.
- 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.
- 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. - Resume only after independent price/depth comparison, a fresh attestation, and a canary cycle. Record the exact stale interval and positions affected.
Mass liquidation
- 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.
- 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.
- 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.
- 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
- 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.
- 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.
- 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.
- 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
- Stop promotion immediately. Preserve the failed environment JSONL, deployment IDs, image digests, migration deployment, dashboard state, and first failing trace.
- 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.
- 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. - 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
- Identify the affected role and outcome labels. Compare queue age, RPC health, signer balance, database health, and the latest deployment marker.
- Drain the affected instance with
bun run jobs drain <instance-id> 300. - Inspect dead jobs with
bun run jobs dead-listand one lineage withbun run jobs job-show <job-id>plusbun run jobs tx-show <transaction-id>. - Replay only after the dependency is healthy:
bun run jobs dead-replay <job-id>. - If burn continues, roll Railway back to the last healthy deployment. Record the rollback.
Queue backlog
- Check PostgreSQL outbox age, BullMQ depth, Redis memory, and worker heartbeats.
- Do not raise concurrency for a signing role while it has a nonterminal transaction.
- Scale discovery separately from execution. Add replicas only after confirming database pool headroom and RPC limits.
- 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
- Run
bun run jobs tx-pending, thenbun run jobs tx-show <transaction-id>. - Compare pending/latest nonces and every configured RPC receipt. Never create a fresh transaction for the same job or manually advance the database nonce.
- Let the reconciler rebroadcast the exact encrypted bytes or produce bounded EIP-1559 replacements. If replacement budget is exhausted, drain the signer and escalate.
- Resume only after the journal lineage is mined, reverted, superseded, or explicitly abandoned before signing.
Signer balance
- Confirm the address is a worker role and not an owner, treasury, or oracle identity.
- Fund only the minimum operating amount from the approved treasury procedure.
- Verify
tayho_signer_balance_weirecovers and readiness becomes healthy. - Record the funding transaction in the incident timeline.
Journal-key rotation
- Add the new key to
TX_JOURNAL_KEYSwhile retaining every old key; set the new active ID. - Deploy one canary replica and confirm it writes the new key ID and decrypts older lineages.
- Deploy all replicas. Run
bun run jobs key-status. - 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 insideJOURNAL_KEY_RETENTION_SECONDS(30 days minimum). It also requires a fresh independent-RPC quorum head, proves every affected lineage is beyondTX_REORG_LOOKBACK_BLOCKS, and compares every persisted receipt-block hash with a majority-agreed canonical block hash. - Redeploy, verify reconciliation, and retain the audit record. No remote signer adapter is used.
Signer rotation
- Register the replacement signer on-chain without removing the old signer.
- 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. - 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, runOPERATOR_ID=... bun run jobs signer-pool-rotate <chain-id> <environment:role> <old-address> <new-address>, replace the old entry in the service-scopedSIGNER_KEYSsecret, 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. - Re-run
OPERATOR_ID=... bun run jobs signer-retire-check <old-address>before on-chain removal. It rejects live lineages and terminal lineages insideSIGNER_RETIRE_FINALITY_SECONDS(24 hours minimum), then applies the same independent-RPC finality proof used by journal-key retirement. - Remove the old signer on-chain only after the command succeeds, then archive the local key.
Protocol audit finding
- Open the finding with
bun run jobs audit-list openandbun 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. - 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.
- 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.
- 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. - 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
- Confirm the single
tayho-auditorRailway 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. - Inspect auditor jobs with
bun run jobs job-show <job-id>, dead work withbun run jobs dead-list, and the dashboard’s audit age, duration, RPC-disagreement, and projection-lag panels. Replay only after the dependency is healthy. - 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 usingOPERATOR_ID=<incident> bun run jobs audit-backlog-supersede <chain-id>, clear only that BullMQ role queue, then schedule oneaudit-recheckbefore restarting the singleton. After the fresh rebuild starts,audit-backlog-supersede <chain-id> dead-onlycan 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. - 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
- Confirm both
tayho-engineRailway replicas,/readyz, Prometheus targets, PostgreSQL, Redis, and the independent RPC quorum. The engine is signerless; never add a key to restore readiness. - Preserve request/intent trace IDs and check intake acknowledgement, validation age, checkpoint lag, trigger backlog, index outbox lag, and PostgreSQL connection saturation.
- Restart only the missing replica using the reviewed digest. Fencing prevents duplicate market work; do not clear leases, Redis keys, or checkpoints manually.
- Resume promotion only after two replicas are scraped for ten minutes and the intent auditor reports zero projection/reservation drift.
Intent intake burn
- Separate client rejection from rate-limit pressure, dependency unavailability, and PostgreSQL commit
latency using
tayho_intent_requests_totaland the acknowledgement histogram. - Inspect affected state with
bun run jobs intent-listandbun run jobs intent-show <chain-id> <hash>. AReceivedresponse is durable receipt, never proof of execution. - 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. - 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
- 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.
- 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. - Revalidate only an eligible funding-blocked intent with
OPERATOR_ID=<incident> bun run jobs intent-revalidate <chain-id> <order-hash>. - 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
- 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.
- Pause intake for affected markets. Do not edit reserved totals or release reservations manually.
- 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.
- Run a fresh full audit and retain before/after counts, checksums, trace IDs, and audit events.
Funding blocked intents
- 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.
- Do not spend or reserve collateral on behalf of a trader. After funds return, use automatic refresh or
the audited
intent-revalidatecommand for a specific order. - If growth is systemic, pause new opening-intent intake while protective closes continue, and inspect frontend/session-cap guidance before resuming.
Intent key rotation
- Add the new local key to the shared engine/relayer
INTENT_KEYRING, setINTENT_ACTIVE_KEY_ID, and deploy one engine and one relayer canary while retaining all prior keys. - Run
bun run jobs intent-key-status; verify new ciphertext uses the active ID and old executable intents still validate and settle. - 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. - 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
- Check the single
tayho-indexerwriter’s/health,/ready,/status, JSON logs, and Prometheus target. Compareponder_sync_block_timestampwithponder_indexing_timestampto separate RPC ingestion lag from slow handler/database work. - 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. - For database errors, stop release promotion and restore the separately owned
tayho_indexerlogical database. Do not point Ponder at the keeper’srailwaylogical database and do not edit projection rows manually. - A reorg counter increase is evidence that Ponder automatically rewound its transaction log. Compare
/statuswith the independent auditor and wait for/ready; never replay handlers or delete a deployment schema by hand. - 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.
- 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
- In testnet, choose a target time in Railway Postgres PITR history. Restore the coordination and indexer logical databases to isolated sibling services.
- Point one isolated auditor at the restored auditor-scoped
DATABASE_URL; never overwrite production. - Run migrations, verify the expected schema version, constraints, worker history, audit events, outbox backlog, signer nonces, and every nonterminal transaction hash against chain quorum.
- Prove that duplicate enqueue and transaction reservation remain fenced.
- Destroy the isolated fork only after evidence is retained. Repeat monthly and before launch.
API degradation
- Confirm both
tayho-apireplicas, their/readyzresponses, 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. - Split failures by
sourceintayho_api_upstream_requests_total. Forengine, preserve the request ID and upstream trace ID and follow the intent-engine runbook. Forindexer, 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. - 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.
- Roll only one API replica to the last-known-good qualified digest, observe it, then restore the
second. Rotate
ENGINE_INTERNAL_TOKENon both API and engine services together if compromise is suspected; mismatched credentials intentionally fail closed.
Realtime degradation
- Check
tayho_api_realtime_pump_total,tayho_api_websocket_connections, rejected protocol messages, andslow_consumerdeliveries by replica. Verify Redis latency and stream length withXLEN tayho:realtime:v1; never delete the stream during diagnosis. - If the indexer pump fails, confirm one replica can renew
tayho:realtime:v1:lease:indexer-pumpand reach the private indexer feed. A leader loss is safe: another replica takes the expiring lease and event IDs deduplicate overlap. - 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.
- Slow consumers are closed with a retryable code. Do not increase buffers during an incident;
clients reconnect with
resumeFrom, or acceptresync_requiredand a fresh snapshot.
Market data degradation
- Check both
tayho-market-datareplicas,/readyz, the upstream circuit gauge, and failures split bybookversusmetadata. Preserve the selected asset, request ID, upstream timestamp, and image digest; never log bearer credentials or raw authorization headers. - Query Hyperliquid
outcomeMetaand the affectedl2Bookdirectly. 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. - 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.
- 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.
- 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 revenueskim 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.