vault
07 — Non-Functional Requirements
Non-Functional Requirements
The cross-cutting properties: security, observability, reliability, compliance posture, permissions and disaster recovery. Each section states what is in place today and what is still open.
Security
- Secrets: all credentials in env (
AUTH_SECRET,DATABASE_URL, etc). API keys stored as sha256 hash only. - Address validation: per-network regex enforced before any AML lookup (server + client).
- Webhooks signed with
webhook_secret; consumers must verify. - Rate limiting is DB-backed (
rate_limitstable,lib/rate-limit/window.ts+lockout.ts) — it survives deploys and is shared across instances. Applied to REST (lib/api/rest-limits.ts, keyed per API key), login/TOTP lockout, and pay-to email submission. - App-local interactive auth: scrypt password hashes, TOTP secrets, WebAuthn passkeys and recovery-code hashes are stored by the app.
users.session_version+auth_session_revocation_tokensallow session revocation;auth_known_devicesdrives new-device detection. - API keys carry an
expires_atin addition torevoked_at. audit_logis append-only at the DB level (0012_audit_log_append_only).- Log redaction is centralised in
lib/observability/redact.ts(auth headers, session tokens, secrets, challenge/passkey values, PII) and applied to every sink. - ⚠️ Still open:
webhook_secretand high-risk PII are not encrypted at rest (09-Open Questions#Secrets at rest). - See 08-Risks and Drawbacks#Security.
Observability
audit_logis the spine — every state-changing event lands there with structuredmetadata.- Mocked email outbound visible at
/admin/emails. - ✅ OpenTelemetry is wired.
instrumentation.tsboots@opentelemetry/sdk-nodewith the OTLP HTTP trace exporter (Node runtime only) when the Better Stack env vars are set. - ✅ Structured logging via Pino with a
@logtail/pinotransport to Better Stack; falls back to stdout/CloudWatch when unconfigured. - ✅ Browser telemetry (Web Vitals + global errors) and server error capture via
onRequestError. - ✅ Health checks:
/api/health(shallow, for the ALB) and/api/health/deep(probes the DB, returns 503 on dependency failure). - ✅ Dead-man switches: worker + web heartbeats.
- ✅ Outgoing-API logging (
lib/observability/outgoing-api-logging.ts) instruments provider calls. - ❌ Still no metrics export (Prometheus
/metricsplanned). - Full rollout detail: observability/better-stack.
Reliability
- ✅ pg-boss is live.
worker/worker.tsruns a dedicated worker process over 11 queues; payout execution, webhook delivery, mail, order expiry, invoice reminders, Fireblocks polling and balance reconciliation are all off the request lifecycle. The old fire-and-forgetrunJobhelper is gone. - ✅ Durable inbound provider callbacks —
provider_webhook_eventsinbox +provider.webhook.processqueue, so a crash replays rather than drops. - ✅ Outbound circuit breaker — reconciliation drift beyond the hard threshold opens an
outbound_haltsrow that pauses sends and payouts for that asset. - ⚠️ Worker topology, retry policy and cron ownership are still being tuned — see 09-Open Questions#Background queue.
Compliance
- Block flag on user prevents login + impersonation.
- KYC override audited.
- AML score recorded per row in payouts.
- ❌ FATF Travel Rule (real-time originator + beneficiary identity per transfer): not implemented. EU TFR threshold is zero — applies to any amount once we touch EU traffic. Vendor / protocol open at 09-Open Questions#Travel Rule integration.
- ❌ DAC8 (annual tax reporting per customer per asset): not implemented. Applies from 1 January 2026; first cycle 2027. Build plan at 09-Open Questions#DAC8 build.
- ⚠️ Sanctions screening (OFAC / EU consolidated / UK HMT): delivered via Elliptic in the target architecture (Elliptic combines wallet-AML risk and sanctions in one product). MVP only mocks AML risk; live cutover lights up sanctions automatically. Required for Travel Rule compliance.
- See 15-Compliance for the full brief and engineering surface.
- ❌ Tax reporting: see 09-Open Questions#Tax & regulatory.
Permission matrix
| Account | Scope | Privileged actions allowed |
|---|---|---|
| User | Own data — own balance, history, product actions | None at platform level |
| Merchant | Merchant-bounded — merchant data, API, subaccount delegation within boundary | Subaccount grants (03-Domains#Subaccounts planned), API key revoke (own keys), webhook config |
| Admin | Internal role-based — least privilege; not all admins have all sections | Full list at 14-Operations#Privileged actions |
Admin role granularity (which admin gets which section) is itself an open question — see 09-Open Questions#Admin role split.
Compliance posture
The architecture is built to satisfy regulated-operations expectations rather than to retrofit them. Detailed framing lives in 14-Operations#Compliance themes; below is the at-a-glance picture.
DORA-relevant
- Environment separation (12-Environments).
- Production-like testing posture (Test mirrors Prod).
- Logging + monitoring (
audit_log, CloudTrail planned). - DB DR for Test + Prod (table below).
- Provider dependency visibility (05-External Providers).
- Controlled admin access (AWS IAM Identity Center SSO + dedicated AWS WAF ruleset, planned).
- Documented security boundaries (13-Network-Model).
MiCA-relevant
- Traceable transaction handling (transaction-centric model).
- Reviewable administrative actions (
audit_logeverywhere). - Accounting + reconciliation clarity (TRES planned, 11-Target-Architecture#Why TRES (not just Postgres)).
- Secure transaction execution path (Fireblocks + CoSigner planned).
- Provider boundary clarity (05-External Providers).
CASP licensing direction tracked at 09-Open Questions#Tax & regulatory.
Disaster recovery (target)
| Scope | Dev | Test | Prod |
|---|---|---|---|
| DB backup | Best-effort | Required | Required |
| DB DR to secondary region | None | Required | Required |
| Application failover | Single AZ | Multi-AZ | Multi-AZ |
| RPO target | n/a | < 1h | < 15min |
| RTO target | n/a | < 4h | < 1h |
DR is DB-first — the rest of the application can be redeployed from artifacts. See 12-Environments#DR strategy.
Performance budget
| Surface | P95 target | Today |
|---|---|---|
/my/wallet initial render |
< 400 ms | within budget |
/my/payouts/new validate (30 rows) |
< 1.5 s | within budget |
/my/payouts/new validate (1000 rows) |
< 8 s | untested — see 09-Open Questions#Payout perf |
POST /api/v1/payouts/:id/execute |
< 1 s to acknowledge | now enqueues onto payout.execute; the request no longer waits for row execution |
| Payout batch drain (1000 rows) | < 60 s | sequential per-row in the worker; mocked latency 60 ms; ≈ 60 s expected |
The old "1000-row execute times out behind the load balancer" risk is closed by the queue cutover. Batch throughput is still sequential — parallelism is open at 09-Open Questions#Payout perf.