Labels / Complexity: bug, incident, redis, Backend, reliability · Extremely High — 500
Problem
The incident-response pause switch in quantara/web_app/api/pausable.py is a module-level, in-process flag:
pause_controller = PauseController() # threading.Lock + bool, no persistence
protocol_pause_middleware reads pause_controller.status().paused directly. Because the state lives only in the memory of the process that handled the admin request, pausing does not propagate to other processes.
Consequences:
- In any multi-worker or multi-replica deployment (the README and
devops/ compose files run the backend as a service that can be scaled), POST /api/admin/pause pauses only the single worker that served the request. All other workers keep accepting deposits, closes, and withdrawals.
- On a restart, the pause is silently lost, so an operator who paused the protocol to stop a live incident comes back after a deploy with the protocol serving again.
- The token comparison in
verify_admin_token uses != rather than hmac.compare_digest, which is a minor timing side channel on the admin credential (secondary to the core defect).
The project already has durable, cross-process state primitives this should reuse: the Redis-backed session_store (quantara/web_app/api/session.py), the wallet-connect Redis session (quantara/web_app/api/walletconnect.py), and the notification dedupe (quantara/web_app/telegram/dedupe.py). The pause switch is the one incident control that is not durable.
Root cause
pause_controller = PauseController() # ← process-local bool
# ...
async def protocol_pause_middleware(request, call_next):
if (... and pause_controller.status().paused): # ← only sees this worker's state
return JSONResponse(status_code=503, content={"detail": PROTOCOL_PAUSED_DETAIL})
Why this is architecturally hard
- The shortcut — "store the bool in Redis" — is directionally right but underspecified: the middleware now has to read Redis on every
/api/* request, which needs a shared client, a read path that fails open or closed, and a caching strategy so the pause check does not add a Redis round-trip to every request.
- There is no established config/feature-flag abstraction to copy; the contributor must decide whether this is a Redis key (
protocol:paused), a small dataclass backed by Redis, or a shared "incident controller" with an in-memory cache plus TTL.
- Fail-closed vs fail-open on Redis outage is a real safety decision: fail-open re-enables a broken circuit breaker during an incident, fail-closed takes the whole protocol down when Redis blips. The choice must be documented.
- The admin token check and the pause endpoints currently bypass the standard
APIError envelope (quantara/web_app/api/errors.py), so aligning them with the rest of the API is a secondary but necessary cleanup.
Proposed design
# durable pause backed by Redis, cached briefly in-process
class PauseController:
async def is_paused(self) -> bool:
return await redis.get("protocol:paused") == b"1"
async def protocol_pause_middleware(request, call_next):
if is_api_path(request) and not is_exempt(request.url.path) and await pause_controller.is_paused():
return JSONResponse(status_code=503, content={"detail": PROTOCOL_PAUSED_DETAIL})
return await call_next(request)
Offer the fail-open/fail-closed choice and cache TTL explicitly in the PR description.
Downstream impact
No ABI or public API change beyond the admin endpoints' behaviour; the frontend does not call /api/admin/pause. Operators' runbooks and any incident automation that expects an in-process toggle must be updated.
Acceptance criteria
Service
Tests
Out of scope
Do not add role-based admin auth or audit logging in this issue; only make the pause switch durable and constant-time.
Getting started
Files in scope: quantara/web_app/api/pausable.py, quantara/web_app/api/main.py (middleware registration). Verify with:
cd quantara && poetry run pytest web_app/tests -k pausable
Good first files to read: quantara/web_app/api/pausable.py, quantara/web_app/api/session.py, quantara/web_app/telegram/dedupe.py.
Labels / Complexity: bug, incident, redis, Backend, reliability · Extremely High — 500
Problem
The incident-response pause switch in
quantara/web_app/api/pausable.pyis a module-level, in-process flag:protocol_pause_middlewarereadspause_controller.status().pauseddirectly. Because the state lives only in the memory of the process that handled the admin request, pausing does not propagate to other processes.Consequences:
devops/compose files run the backend as a service that can be scaled),POST /api/admin/pausepauses only the single worker that served the request. All other workers keep accepting deposits, closes, and withdrawals.verify_admin_tokenuses!=rather thanhmac.compare_digest, which is a minor timing side channel on the admin credential (secondary to the core defect).The project already has durable, cross-process state primitives this should reuse: the Redis-backed
session_store(quantara/web_app/api/session.py), the wallet-connect Redis session (quantara/web_app/api/walletconnect.py), and the notification dedupe (quantara/web_app/telegram/dedupe.py). The pause switch is the one incident control that is not durable.Root cause
Why this is architecturally hard
/api/*request, which needs a shared client, a read path that fails open or closed, and a caching strategy so the pause check does not add a Redis round-trip to every request.protocol:paused), a small dataclass backed by Redis, or a shared "incident controller" with an in-memory cache plus TTL.APIErrorenvelope (quantara/web_app/api/errors.py), so aligning them with the rest of the API is a secondary but necessary cleanup.Proposed design
Offer the fail-open/fail-closed choice and cache TTL explicitly in the PR description.
Downstream impact
No ABI or public API change beyond the admin endpoints' behaviour; the frontend does not call
/api/admin/pause. Operators' runbooks and any incident automation that expects an in-process toggle must be updated.Acceptance criteria
Service
POST /api/admin/pauseis visible to every backend worker/replica, not just the serving process.Tests
cd quantara && poetry run pytest web_app/tests.Out of scope
Do not add role-based admin auth or audit logging in this issue; only make the pause switch durable and constant-time.
Getting started
Files in scope:
quantara/web_app/api/pausable.py,quantara/web_app/api/main.py(middleware registration). Verify with:Good first files to read:
quantara/web_app/api/pausable.py,quantara/web_app/api/session.py,quantara/web_app/telegram/dedupe.py.