vault
08 — Risks and Drawbacks
Risks and Drawbacks
A consolidated register written from the PM seat. Severity is reassessed each sprint.
Mission scope
- ⚠️ Provider readiness gap (narrowed). Fireblocks, KYC (Sumsub), email and the rate oracle have live implementations. Elliptic, the acquirer, the chain provider and TRES are still mock-only and throw on
live— so AML, sanctions and card acquiring cannot go live today. 05-External Providers - ⚠️ Single market launch unstated. KPI thresholds in 01-Overview are placeholders pending 09-Open Questions#Launch market.
Architecture
- ✅ Background queue shipped. pg-boss runs 11 queues in a dedicated worker process; payouts, webhooks, mail, expiry sweeps and reconciliation are off the request lifecycle. The load-balancer-timeout risk on 1000-row payouts is closed.
- ✅
runJobfire-and-forget removed — replaced by durable pg-boss jobs. - ⚠️ Single-region Postgres. No read replicas, no failover plan articulated. 09-Open Questions#Multi-region
- ⚠️ Payout rows still execute sequentially inside the worker. Throughput, not durability, is now the constraint. 09-Open Questions#Payout perf
- ⚠️
/v2is an undocumented parallel UI.app/v2,components/v2,lib/v2andlib/v2mocksimplement a second front-end partly on mock data, with no PRD page, no migration plan and no stated relationship to the shipped surfaces. Divergence risk grows the longer both exist.
Auth
- ✅ Admin escalation via env var removed. Admin access now comes from persisted
users.role = 'admin', so envvar access alone cannot self-promote an account. Bootstrap/recovery requires a deliberate database role update. - ✅ JWT cookie revocation shipped.
users.session_versionplusauth_session_revocation_tokensinvalidate live cookies;/reset/sessions/[token]gives users a self-service path. The old "valid until 30-day expiry" window is closed. - ⚠️ Second-factor secrets are app-stored. Password hashes (scrypt), TOTP secrets and recovery-code hashes all live in our own database. TOTP secrets are stored in a form the app can read — encrypting them at rest belongs with 09-Open Questions#Secrets at rest.
Domains
Ledger
- ⚠️ Wallet history is unbounded read. Page size is 50 but filter+sort happen post-fetch — fine today, will need indexed pagination at scale.
Fees
- ⚠️ Rate changes prospective only. Existing pending invoices keep their persisted
platform_fee_minor. - ✅ Tier model retired.
fee_tiers,users.tier,resolveUserFeeTierandresolveOpFeeTierare gone; rates are per-merchant columns onusersplus globalplatform_feesknobs. The old "per-operation rates not plumbed everywhere" risk no longer applies. - ⚠️ Per-merchant rates have no defaults surface. Rates live on each user row, so a newly-created merchant's fees depend on whatever the insert path sets. There is no admin view of "all merchants on non-standard rates".
- ⚠️ Public fee-schedule commitment is now harder to make honestly, since pricing is per-merchant rather than per-published-tier. 09-Open Questions#Fee schedule public commitment
Invoices
- ⚠️ Per-invoice sub-vault is opened on every
openedinvoice. Costs one Fireblocks vault per invoice. - ⚠️ Rate-frozen invoices never re-quote — math may diverge from oracle if edited.
Payouts
- ⚠️ AML cache is per-(address, network, currency), not per-merchant. Acceptable — Elliptic's API is per-address.
- ⚠️ Bearer-token endpoint backed by
api_keys, not a per-session/auth/tokenmint as in the spec. Revisit if integrators ask for short-lived tokens. - ⚠️ Funds preflight trims from the tail (highest
row_num). The merchant has no UI control over which rows get bumped. - ⚠️ Mocked 1% Fireblocks failure is non-deterministic per call → tests for
BATCH_PARTIALare probabilistic.
Sell
- ⚠️ Fiat side does not move.
status='pending'until a future fiat-rail worker flips it. 09-Open Questions#Sell payout rail
Pay-to
- ⚠️ Email confirmation rate-limit is per (slug, IP) — a determined attacker can rotate IPs.
Data model
- ⚠️
audit_log.metadataschema is loose. Useful but hard to type-check downstream consumers.
Provider readiness
- ✅ Live implementations landed for Fireblocks, KYC (Sumsub), email (Resend) and the rate oracle (CEX.IO).
- ⚠️ Five interfaces remain mock-only and throw on
live: chain, acquirer, risk, Elliptic, TRES. AML/sanctions and card acquiring are the critical-path blockers for a live launch.
Security
- ⚠️ Webhook secrets are not yet KMS-encrypted. Encrypt merchant callback secrets at rest before GA.
- ⚠️ TOTP secrets are stored plaintext in
users.totp_secret. A database read discloses every user's second factor. Pair with the webhook-secret work under 09-Open Questions#Secrets at rest. - ✅ Rate-limit storage moved to Postgres (
rate_limits). It survives deploys and is shared across instances — the in-process-map risk is closed.
Admin
- ⚠️ No undo on fee-rate change or KYC override.
- ⚠️ Partial bulk export.
/admin/logsand/api/invoices/exportexport;/admin/transactionsand/admin/usersstill require copy/paste.