Skip to content

Commit 9551173

Browse files
authored
Merge pull request #1558 from midexol/fix/issue-1511-circuit-breaker-runbook
docs: add circuit-breaker manual-reset runbook to OPERATIONS.md
2 parents 5389dbc + f514ba3 commit 9551173

1 file changed

Lines changed: 123 additions & 0 deletions

File tree

docs/OPERATIONS.md

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,129 @@ Add backup verification to your CI pipeline:
484484
485485
For detailed, step-by-step procedures to handle incident response, outages, containment, rollbacks, database restoration, and validation across the smart contract, backend, and frontend stacks, refer to the [Disaster Recovery Runbook](DISASTER_RECOVERY.md).
486486
487+
## Circuit-breaker manual-reset runbook
488+
489+
The `RiskManagementService` maintains an in-memory circuit breaker for every tracked asset (`XLM`, `BTC`, `ETH`, `USDC`, and any assets added via the admin API). A breaker **trips** when an asset's tick-over-tick price change exceeds **20 %** (the `CIRCUIT_BREAKER_THRESHOLD`). While tripped, `shouldAllowRebalance()` returns `allowed: false` with reason code `CIRCUIT_BREAKER_ACTIVE`, blocking all automatic and manual rebalance operations for any portfolio that holds the affected asset.
490+
491+
Tripped breakers auto-recover after **5 minutes** (`CIRCUIT_BREAKER_COOLDOWN`). The steps below are for situations where you need to reset before that window expires, or where you need to confirm the system is healthy after an incident.
492+
493+
### 1. Diagnose – confirm the breaker is tripped
494+
495+
**Public status endpoint (no auth):**
496+
497+
```bash
498+
curl -s https://<API_HOST>/api/system/status | jq '.data.riskManagement'
499+
```
500+
501+
A tripped breaker looks like:
502+
503+
```json
504+
{
505+
"circuitBreakers": {
506+
"BTC": {
507+
"isTriggered": true,
508+
"triggerReason": "22.3% price movement",
509+
"cooldownUntil": 1722080760000,
510+
"triggeredAssets": ["BTC"]
511+
}
512+
},
513+
"enabled": true,
514+
"alertsActive": true
515+
}
516+
```
517+
518+
`isTriggered: true` with a `cooldownUntil` value in the future confirms the breaker is active. Convert the Unix millisecond timestamp to determine how much cooldown remains:
519+
520+
```bash
521+
node -e "console.log(new Date(1722080760000).toISOString())"
522+
```
523+
524+
**Per-portfolio risk check:**
525+
526+
```bash
527+
curl -s https://<API_HOST>/api/risk/check/<PORTFOLIO_ID> | jq '{allowed, reason, reasonCode}'
528+
```
529+
530+
If the response is `"reasonCode": "CIRCUIT_BREAKER_ACTIVE"`, rebalancing is blocked for that portfolio.
531+
532+
**Per-portfolio detailed circuit-breaker status:**
533+
534+
```bash
535+
curl -s https://<API_HOST>/api/risk/metrics/<PORTFOLIO_ID> | jq '.data.circuitBreakers'
536+
```
537+
538+
This returns the per-asset breaker map, including `triggerReason` and the precise `cooldownUntil` timestamp.
539+
540+
---
541+
542+
### 2. Decide – reset manually or wait?
543+
544+
| Situation | Recommended action |
545+
|-----------|-------------------|
546+
| Cooldown expires in < 3 minutes | **Wait.** Auto-recovery will fire; no operator action needed. |
547+
| Flash-crash or data anomaly confirmed as false alarm | **Reset manually.** Prices have stabilised and the trigger was a bad tick or feed glitch. |
548+
| Market still highly volatile (> 15 % EWMA vol) | **Wait or investigate further.** Resetting into continued volatility will likely re-trip the breaker immediately. |
549+
| Cooldown has expired but `isTriggered` is still `true` in status | Call `GET /api/system/status` again — `getCircuitBreakerStatus()` performs the expiry check lazily on each read. If the flag does not clear, restart the API process (see Safe shutdown and restart below). |
550+
| Incident requires immediate production rebalancing | Follow the manual-reset steps below, then monitor `/api/risk/check/:portfolioId` continuously after the reset. |
551+
552+
---
553+
554+
### 3. Perform the manual reset
555+
556+
The admin endpoint accepts an `X-Admin-Key` header (value of the `ADMIN_API_KEY` environment variable) and optionally a specific asset to reset. Omitting `asset` resets **all** tripped breakers.
557+
558+
**Reset a single asset (e.g. BTC):**
559+
560+
```bash
561+
curl -X POST https://<API_HOST>/api/admin/circuit-breaker/reset \
562+
-H "Content-Type: application/json" \
563+
-H "X-Admin-Key: <ADMIN_API_KEY>" \
564+
-d '{"asset": "BTC"}'
565+
```
566+
567+
**Reset all assets at once:**
568+
569+
```bash
570+
curl -X POST https://<API_HOST>/api/admin/circuit-breaker/reset \
571+
-H "Content-Type: application/json" \
572+
-H "X-Admin-Key: <ADMIN_API_KEY>" \
573+
-d '{}'
574+
```
575+
576+
Expected success response (`200 OK`):
577+
578+
```json
579+
{
580+
"success": true,
581+
"data": {
582+
"reset": ["BTC"],
583+
"message": "Circuit breaker(s) reset successfully"
584+
}
585+
}
586+
```
587+
588+
> **Note:** The `RiskManagementService` instance is in-process and in-memory. If the API runs as multiple instances behind a load balancer, send the reset request to **every instance** (or use a sticky session / internal broadcast mechanism). After any process restart the breaker state is cleared automatically.
589+
590+
---
591+
592+
### 4. Post-reset confirmation checklist
593+
594+
Run through the following checks after performing a reset to confirm the system has returned to normal operation:
595+
596+
- [ ] **Breaker cleared**`GET /api/system/status` returns `alertsActive: false` and `isTriggered: false` for the affected asset(s).
597+
- [ ] **Risk check passes**`GET /api/risk/check/<PORTFOLIO_ID>` returns `"reasonCode": "CIRCUIT_BREAKER_ACTIVE"` no longer; `allowed: true` (assuming no other blocks are active).
598+
- [ ] **Price feed is live**`GET /api/system/status` shows `"priceFeeds": true` under `services`. Stale or absent prices will re-trip the breaker on the next price tick if volatility is still high.
599+
- [ ] **Auto-rebalancer running**`GET /api/system/status``autoRebalancer.status.isRunning: true`. If the auto-rebalancer paused due to the circuit-breaker event, restart it:
600+
```bash
601+
curl -X POST https://<API_HOST>/api/auto-rebalancer/start \
602+
-H "X-Admin-Key: <ADMIN_API_KEY>"
603+
```
604+
- [ ] **No repeat trips** – Monitor `GET /api/system/status` for 5–10 minutes after the reset. If the breaker re-trips immediately, the underlying market condition has not stabilised; **do not keep resetting manually** — investigate the price feed or wait for conditions to calm.
605+
- [ ] **Notification delivered** – If circuit-breaker notifications are enabled (`event_circuit_breaker` preference), confirm users received the event-cleared or rebalancing-resumed notification (check `GET /api/notifications` for recent entries).
606+
- [ ] **Audit log entry** – Confirm the admin action is reflected in application logs (search for `circuit-breaker reset` at `INFO` level).
607+
608+
---
609+
487610
## Related docs
488611

489612
- Contributor setup: [docs/CONTRIBUTING.md](CONTRIBUTING.md)

0 commit comments

Comments
 (0)