Skip to content

Architecture

Architecture

Rokks runs as a Docker Compose stack of ~16 services. All internal communication is over HTTPS with a mesh CA. No service exposes a host port except frontend (3000/3443).

Service map

ServiceDocker nameRole
Frontendfrontendnginx serving SPA + Edge proxy for all API traffic
RegistryregistrySQLite-backed bootstrap anchor — secrets, service discovery, config
AuthauthOAuth exchange — issues JWTs for user sessions
Doc Gatewaydoc-gatewayFirestore + GCS proxy — all document/blob reads and writes
SQL Gatewaysql-gatewayTimescaleDB / PostgreSQL proxy — all SQL queries
WorkerworkerSQLite job engine — ETL, sync, index, alert, AI generation jobs
EdgeedgeInternal API router — authenticates browser traffic, forwards to worker/gateways
AI GatewayaiLLM proxy — Gemini cloud or local LM Studio
Data Transformdata-transformSSE-streaming ETL backend for file processing
DispatcherdispatcherCron scheduler — reads schedules, acquires locks, submits jobs to Worker
SentinelsentinelAlert evaluator — evaluates alert rules, submits notification jobs
HealthcheckhealthcheckLiveness aggregator — probes all services and surfaces results
MonitormonitorMetrics aggregator — collects per-service operation logs
App ConnectappconnectAirtable connector — discovery, schema, incremental sync
Web APIwebapiWeb API connector — HTTP/WebSocket extraction
MonIOmonioOptional MongoDB-backed document backend (alternative to Firestore)

Communication topology

Browser
└── Edge (nginx, port 3000/3443)
├── /auth/* → Auth
├── /api/* → Edge service (proxy)
│ ├── /api/doc/* → Doc Gateway
│ ├── /api/sql/* → SQL Gateway
│ ├── /api/ai/* → AI Gateway
│ ├── /api/jobs/* → Worker
│ ├── /api/audit/* → Worker
│ └── /api/transform/* → Data Transform
└── / → SPA (static files)

All backend-to-backend calls (within Docker) use INTERNAL_SERVICE_SECRET for authentication and go directly between containers — they do not hop through Edge.

Data flow

External data source (CSV, API, Airtable)
└── Data Fabric (File Area / Web API / App Connect)
└── Worker job (ETL / WEBAPI_SYNC / AIRTABLE_SYNC)
├── Extract → raw rows in memory
├── Transform → runTransformation() (shared/transform.js)
└── Load → SQL Gateway → TimescaleDB
└── Dashboard widgets query via SQL Gateway

The same runTransformation function runs in both browser preview and backend execution — no divergence is possible.

Physical / logical layer duality

Every table has two names:

  • Physical name: t_<32-hex> — immutable, used in all DDL and SQL
  • Logical name: human-readable snake_case, exposed as a PostgreSQL view (CREATE OR REPLACE VIEW)

Column names follow the same pattern: f_<hex8> (physical) with a human label stored in table_metadata.columnMap.

Renames are pure metadata updates — zero DDL, zero data movement.