Trade8:Router - Detailed Design Document
Audience: maintainers, reviewers, and architects modifying the service. Overview in
README.md; business functionality inBUSINESS.md.
1. Scope & Context
Owns:
- All outbound broker connectivity and its session bookkeeping (sequences,
heartbeats, logons); the wire journal (
router.message); per-destination session state (router.session); the fills (router.order_transaction,router.fill_fix_tags) - Router is the sole writer of execution records; therouterevent family (trade8:router:*).
Does NOT own (delegates):
- Routing catalogs → Trade8:Desk (
destination,gateway,market_gateway_destination,office_broker,msg_template- read via declared cross-schema grants only). - Order acceptance/authorization → Trade8:Desk/Trade8:Risk.
- Fee catalog (types/formulas/params source rows:
desk.fee_catalog) → Desk; Router evaluates. - P&L → Trade8:Pnl (consumes fills). Market data → Trade8:Feed.
- Scheduling (journal purge) → Sentin8:Cron.
2. Runtime Architecture
Request lifecycle - routed order (desk consumer family)
Fill lifecycle - inbound executions
3. Data Model
Schema router, db trade8; role trade8_router_service; RLS env block;
shell-generated outbox/journal.
router.message- retention-purged via Cron (30-day default); BYTEA wire payloads; per-destination seq ordering.router.order_transaction- the fills (relocated from Desk; Router sole writer); fee columns are ×10⁴ raw ints with currency refs preserved for conversion at Pnl.router.fill_fix_tags- broker-dialect tags preserved verbatim.desk.*reads:destination,gateway,market_gateway_destination,office_broker,msg_template(declared cross-schema grants in registration + init.sql grants block).
4. API Surface
Public (BFF-proxied ops)
| Endpoint | Gate | Notes |
|---|---|---|
GET /v1/sessions | trade8:router:session:manage | destination session wall |
POST /v1/sessions/{id}/reset | trade8:router:session:manage | sequence reset (audited) |
GET /v1/messages | trade8:router:session:read | journal window |
POST /v1/journal/purge | trade8:router:journal:read | Cron proxy-back (30d retention) |
Internal (M2M)
| Endpoint | Gate |
|---|---|
POST /v1/internal/executions/{destination} | trade8:router:executions:report |
Admin (RFW GET /v1/admin/ui/{page})
| Page | Data endpoint |
|---|---|
sessions | GET /v1/admin/sessions (+ POST reset alias) |
journal | GET /v1/admin/journal |
fees | GET /v1/admin/fees (fee catalog from desk.fee_catalog) |
Plus the shared Shield policy-admin API block. (Design §registration declares the additional destinations/templates/messages admin endpoints; they are implemented at the desk/router pages above - the disposition is tracked in Open Questions.)
Events
| Direction | Event | Trigger |
|---|---|---|
| Emitted | trade8:router:fill:executed | inbound fill/correction normalized |
| Emitted | trade8:router:fill:cancelled | broker cancel of resting order |
| Emitted | trade8:router:fill:rejected | broker reject |
| Emitted | trade8:router:session:status | session state transitions |
| Emitted | trade8:desk:order:routed | accepted order leased + journaled |
| Consumed | trade8:desk:order:accepted | route/render/journal lease |
(Outbox → relay → consumer flow: see the sequence diagrams in §2.)
5. Cross-Cutting Concerns
- AuthN/AuthZ - 8-step pipeline; Shield targets
trade8:router:{session,executions,journal}:*; internal execution callbacks are M2M-gated with the declaredexecutions:reportaction. - Tenant + RLS - consumer/execution paths set context on the pool
connection then
begin(); explicit env filters; declared desk-catalog grants only (no blanket schema access); service role has no BYPASSRLS. - Idempotency - sequence lease is atomic per destination; fills dedupe
on broker
exec_id/transaction id; outbox rows keyed by row id. - Audit - wire journal + preserved FIX tags are the compliance record; audit journal rows pushed to Sys with retry.
- Secrets -
credentials_refis a secret reference; Router never persists plaintext broker credentials. - TLS - post-quantum via
sentin8-tls; broker dial-out inherits the platform TLS posture when endpoints support it. - Graceful drain - shell upgrade-instance drains; sessions reconnect on the new instance (state is DB-backed).
6. Failure Modes & Recovery
- Broker session loss - watchdog marks down after stale heartbeat; reconnect re-logs on (seq-continuation or reset per policy).
- Inbound gap - resend window computed from the journal; gaps replay from stored messages.
- Transaction failure - consumer Nacks; leases roll back; redelivery re-leases (deduped by idempotency semantics downstream).
- Purge safety - the purge deletes only rows older than retention and runs inside a scheduled window.
7. Concurrency & Performance Model
- Single consumer for the desk family (order routing is low-rate vs fills); per-destination FIFO within consumer handling.
- The normalizer hot path is allocation-light (byte buffers + one tx).
- Heartbeat watchdog runs on a 10s cadence (SQL-only update).
- Pool capped via the shared helper; txs are short (lease/insert).
8. Invariants, Insecure-Defaults Rejected, Unsafe Rationale
- Fills are written exactly once per broker execution and only by Router;
no other service holds insert grants on
router.order_transaction. - Outbound wire messages exist in the journal iff the lease happened - one tx, no partial delivery states.
- Shadow sessions never transmit; the journal keeps their mirror.
- Plaintext broker credentials are never stored (secret refs only) and never echoed through admin surfaces.
- Fee formulas are data, evaluated by the tested DSL - no free-text formula execution.
- No
unsafeblocks; codecs verify checksums (FIX) and frame lengths (OUCH) and reject malformed input.
9. Testing Strategy
- Golden wire tests live with the codecs (crates
fix/ouch). - Charger differential tests pin each formula type against hand-computed values (×10⁴ scale).
tests/sql_matrix.rs(skip-offline): RLS matrix; journal append/read-filter/purge-by-age; session-counter upsert/reset; outbound-seq lease atomicity.- Deliberately not tested here: broker side of the wire (integration venue), Desk submit choreography, Pnl fold math.
10. Open Questions
- Socket transport for FIX/OUCH dial-out - final wiring of the session state machines to live TCP/TLS endpoints (designed; the HTTP adapter pattern is implemented for exchange8-style gateways).
- Registration/admin endpoint parity -
registration.rsdeclares/v1/admin/templates,/v1/admin/messages,/v1/admin/destinationsdata endpoints thatmain.rsdoes not route yet (the destinations editor lives in Desk's admin pages): implement or prune the declarations. - Fee condition predicates - the design's condition DSL (typed
predicates) vs free
fee_catalogparams: which surface admins edit. - Resend-from-journal driver - the end-to-end resend loop needs a live session to be exercised; add a session-sim fixture to the integration layer.
Architecture & Design: trade8:router and its underlying concepts were designed by Hamid Jahanzad.