This document specifies the design, security boundaries, cross-path propagation (UI, API, and background workers), operational edge cases, failure modes, and rollback/recovery procedures for the Trellis API public-status model.
Trellis computes a deterministic system state from four underlying sources:
- Maintenance Windows (
PublicStatusService.activateMaintenance) - Scoped Operational Pauses (
PauseControlService—trading,payments,withdrawals,defi,oracle,reconciliation,all) - Component Degradations (manual operator declarations and
DependencyHealthServicelatency/availability checks) - Core Readiness Probes (
HealthService.getReadiness()for PostgreSQL and Redis)
| State | Severity | HTTP Code (GET /status) |
Meaning |
|---|---|---|---|
operational |
0 |
200 OK |
All critical dependencies and operational scopes are healthy. |
degraded |
1 |
200 OK |
Partial degradation: elevated dependency latency, optional dependency outage, or a subsystem-only pause (defi, oracle, reconciliation). Core API reads and unaffected write scopes remain operational. |
paused |
2 |
200 OK (503 if all paused) |
High-impact financial scope (trading, payments, withdrawals, or all) is paused via PauseControlService. Writes to paused scopes are halted with Retry-After. |
maintenance |
3 |
503 Service Unavailable |
Planned or emergency maintenance window is active. Non-admin mutating API requests are rejected (readOnly: true), UI displays a non-dismissible maintenance banner, and non-essential background jobs are deferred. |
When multiple conditions overlap, the model resolves to the highest severity state (maintenance > paused > degraded > operational).
Status payloads are rendered in public status pages, browser UI banners, logs, and monitoring dashboards. Both public and operator views pass through the redaction boundary in src/health/public-status.model.ts:
- Accessible to: Unauthenticated callers, status page pollers, and frontend UI clients (
@Public(), rate-limited). - Contains:
state,httpStatusCode,summary,generatedAt,retryAfterSecondsmaintenanceWindow(active,publicMessage,scheduledStartAt,estimatedEndAt,affectedScopes)pausedScopes(scope,state,publicReason,expiresAt)components(api,database,cache,trading,payments,withdrawals,defi,oracle,reconciliation,background_workers)capabilities(readOnly,tradingEnabled,paymentsEnabled,withdrawalsEnabled,defiEnabled,oracleEnabled,reconciliationEnabled,backgroundJobsProcessing)
- Never Exposes:
- Database or Redis connection strings (
postgresql://...,redis://...) - Credentials, passwords, JWTs (
eyJ...), Bearer/Basic tokens, API keys (sk-...,pk_...), Stellar secret seeds (S...), Ethereum private keys (0x...), or PEM private key blocks - Environment variable names (
configKeys) or secret values - Internal IPv4/IPv6 addresses (
10.x.x.x,172.16-31.x.x,192.168.x.x,127.0.0.1) or internal hostnames (*.internal,*.local,localhost) - Raw SQL statements (
SELECT ... FROM,INSERT INTO, etc.) or Node.js stack trace frames - Unescaped HTML / XSS payloads (
<script>,onerror=,javascript:) - Operator identifiers (
activatedBy,declaredBy)
- Database or Redis connection strings (
- Accessible to: Authenticated
ADMINandMAINTAINERusers with 2FA (JwtAuthGuard,RolesGuard,AdminTwoFactorGuard). - Adds:
rawStateSources: Exact breakdown of active overrides, pause scopes, readiness status, and background worker mode.activePauses: Full pause metadata (activatedBy,activatedByRole,activatedAt,expiresAt,environment) with secret-scrubbedreason.dependencyDiagnostics: Per-dependency latency,sanitizedTarget(userinfo, query string, and fragment stripped),configKeys({ key, configured }booleans only — never values), and actionableremediationhints.backgroundPath: Current worker mode (processing,throttled,deferred),deferredScopes, andactiveBackoffMultiplier.stateHistory: Bounded audit log of recent state transitions (fromState,toState,trigger,changedAt,actorId,publicSummary,operatorNote).runbook: Contextual verification, rollback/recovery steps, and edge-case warnings for the active state.
GET /status/ui-bannerandevaluateUiStatusContract(publicStatus)return a deterministic UI banner contract:operational:visible: false,severity: "none"degraded:visible: true,severity: "warning",dismissible: truepaused:visible: true,severity: "error",dismissible: falsemaintenance:visible: true,severity: "info",dismissible: false
- Every state change in
PublicStatusServiceemits a"status.state_changed"event containing{ previousState, currentState, publicStatus, uiContract }and a WebSocket envelope (event: "system:status:update") so connected dashboard clients update banners and disable paused action buttons immediately.
evaluateApiRequestGate(publicStatus, context)enforces consistent HTTP behavior:- Always attaches
X-Trellis-System-Status: <state>andRetry-After: <seconds>(when applicable). - Status and health routes (
/status/*,/health/*) and read-only methods (GET,HEAD,OPTIONS) remain allowed across all states. - Users with the
ADMINrole bypass write blocks (bypassReason: "admin_override") so operators can execute recovery actions. - In
maintenance(or globalallpause), non-admin mutating requests (POST,PUT,PATCH,DELETE) receive503 Service Unavailablewith RFC 7807 Problem Details (errorCode: "SERVICE_UNAVAILABLE",retryable: true,retryAfterSeconds). - In
pausedordegradedstates, mutating requests targeting a paused scope receive503 Service Unavailable, while requests targeting unpaused scopes succeed normally.
- Always attaches
evaluateBackgroundJobGate(publicStatus, context)prevents background queues from amplifying incidents or exhausting retry budgets into dead-letter queues:operational:decision: "execute",backoffMultiplier: 1.degraded:decision: "execute_with_backoff",backoffMultiplier: 2,delayMs: 2000(unless the job's specific scope is paused, in which case it is deferred).paused: Jobs mapped to a paused scope (e.g.payments.settle->payments,trading.execute->trading) returndecision: "defer"withretryAfterSeconds, while unaffected jobs (e.g.email.send) continue executing.maintenance: All non-essential jobs returndecision: "defer"until maintenance completes; jobs markedisEssentialRecovery: trueexecute immediately.
- Overlapping States & Step-Down Transitions:
- If an operator activates a maintenance window while
tradingis paused andcacheis degraded, the public state ismaintenance. - When the maintenance window is cleared, the state steps down to
paused(notoperational) becausetradingis still paused. - When
tradingis resumed, the state steps down todegradeduntilcacherecovers, and only then transitions tooperational.
- If an operator activates a maintenance window while
- Premature Auto-Expiry During Long Maintenance or Migrations:
- Both
PauseScope.expiresAtandMaintenanceWindow.estimatedEndAtauto-expire when their timestamp passes. - Mitigation: If a database migration or settlement reconciliation takes longer than planned, re-POST
/status/maintenancewith an updatedestimatedEndAtbefore expiry so write locks and background job deferrals do not lift mid-migration.
- Both
- Credential Leaks in Operator Reason Strings or Driver Exceptions:
- Operators or automated circuit breakers might accidentally include a raw DSN, API key, SQL error, or internal IP in a pause/maintenance
reason. - Mitigation:
sanitizePublicReasonandsanitizeStatusTextscrub secrets, internal hosts, SQL statements, and stack traces before storing or returning any public or operator payload.
- Operators or automated circuit breakers might accidentally include a raw DSN, API key, SQL error, or internal IP in a pause/maintenance
- Health/Database Probe Failure While Querying Status:
- If PostgreSQL or
PauseControlServicethrows duringgetPublicStatus(),PublicStatusServicecatches the error, logs a sanitized warning, and degrades gracefully rather than returning an unhandled500.
- If PostgreSQL or
- Verify all migrations and infrastructure changes are complete:
curl -fsS -H "Authorization: Bearer $ADMIN_JWT" "$BASE_URL/api/v1/health/dependencies/diagnostics?refresh=true"
- Clear the maintenance window:
curl -fsS -X POST "$BASE_URL/api/v1/status/maintenance/clear" \ -H "Authorization: Bearer $ADMIN_JWT" \ -H "Content-Type: application/json" \ -d '{"reason": "Maintenance complete and readiness verified"}'
- Confirm public status returns
200 OKandstate: "operational":curl -fsS "$BASE_URL/api/v1/status"
- Inspect active pauses and operator diagnostics:
curl -fsS -H "Authorization: Bearer $ADMIN_JWT" "$BASE_URL/api/v1/status/operator"
- Resume the paused scope via
PauseControlController:curl -fsS -X POST "$BASE_URL/api/v1/pause-control/resume" \ -H "Authorization: Bearer $ADMIN_JWT" \ -H "Content-Type: application/json" \ -d '{"scope": "trading", "reason": "Upstream feed stabilized"}'
- Verify deferred background jobs drain normally and
GET /statusreflects the updated state.
- If caused by a manual component degradation, clear it once metrics recover:
curl -fsS -X POST "$BASE_URL/api/v1/status/degradation/clear" \ -H "Authorization: Bearer $ADMIN_JWT" \ -H "Content-Type: application/json" \ -d '{"componentId": "defi", "reason": "RPC latency back within budget"}'
- If caused by an automatic dependency check, resolve the upstream latency/outage and force a cache refresh via
GET /health/dependencies/diagnostics?refresh=true.
Run the standalone executable verification suite:
npm run validate:public-status