Architecture overview¶
This page describes the current production-oriented architecture in the repository.
When to use this page¶
Use this page to understand how services depend on one another, how data flows end to end, and where to triage failures.
System shape¶
The platform runs as a Docker-based service mesh:
Visual monitor designs, numerical compilation, activation claims and recovery contracts are described in Monitoring pipelines.
frontend(React): user-facing UI.api-gateway(FastAPI): external API facade under/v1.tactic-middleware(FastAPI): data adapter and RADAR orchestration.db-sync: schema initialization and readiness API.mqtt-proxy: selected collection, sampling and accepted MQTT publishing.mqtt-radar: anomaly workload image; TACTIC launches one workload per monitor.timescaledb(PostgreSQL + TimescaleDB): operational persistence.emqx-main, plus optionalemqx-worker: internal broker.source-broker: deterministic local source telemetry broker.mailpit: local mail capture for authentication flows.socket-proxy: restricted Docker API access for TACTIC.
Service responsibilities and interplay¶
| Service | Owns | Reads from | Writes to | Critical dependencies |
|---|---|---|---|---|
| Frontend | User workflows and UI state | API Gateway /v1 |
Browser state | API Gateway, reached through /api on the frontend's own origin |
| API Gateway | External route contract and request composition | TACTIC data, membership, catalogue and monitoring endpoints | HTTP responses | TACTIC readiness |
| TACTIC | Domain data adapter and RADAR lifecycle | Database services, Docker API proxy, model catalogue | Database and container lifecycle | Docker API, database |
| DB Sync | Schema bootstrap and readiness | Database | Database schema | Database availability |
| MQTT Proxy | Selected admission, sampling and batching | Source-scoped raw EMQX topics | Numeric history, latest state, accepted EMQX topics | Catalog, internal EMQX and database |
| EMQX | Internal monitoring message bus | Bridge publishers | MQTT streams | Broker health and authentication settings |
| MQTT RADAR | Online anomaly detection | EMQX topics and model configuration | Monitoring evidence and anomalies | Topic stream, model initialization, database |
| TimescaleDB | Relational and time-series persistence | Write-capable services | Query results | Storage and schema readiness |
End-to-end flows¶
1. Authentication and session flow¶
- The frontend calls Gateway authentication routes under
/v1/auth. - The Gateway delegates data operations to TACTIC.
- Password hashes and verification state are resolved through database-backed services.
- Verification and reset emails use the configured SMTP service; local mode uses Mailpit.
- The frontend stores the bearer token and attaches it to later API calls.
2. Telemetry ingestion flow¶
- An administrator saves MQTT broker, entity, topic and payload-field definitions through Sources.
- Collection is explicitly enabled for chosen readings; saving definitions alone does not select them.
- TACTIC subscribes to selected upstream topics using operator-provisioned networking and credentials, then forwards generation-scoped envelopes through internal EMQX.
- MQTT Proxy decodes admitted readings, applies collection policy, stores numeric history/latest state and emits accepted topics.
- RADAR consumes
telemetry/<entity>/<reading>observations. Gateway serves stored history and live monitoring evidence to the UI.
See Collection for revision transitions and limits.
3. Monitoring lifecycle flow¶
- An operator selects sensors and a strategy in the frontend.
- The frontend calls Gateway monitor routes under
/v1/monitors. - TACTIC validates the request, persists the desired workload generation and claims its selected sensors before calling Docker. Pending and stopping monitors retain their sensor claims until reconciliation confirms the change.
- TACTIC creates or adopts the deterministic
radar-<service-id>workload. Reconciliation resumes interrupted creation requests and removes delayed creations whose intent was deleted, within the same database deployment scope. - A managed workload checks its persisted desired state and lifecycle version before starting its detector or subscribing. A delayed creation after stop or deletion exits without beginning monitoring. The admitted workload runs either the static-baseline or adaptive-stream lane.
- Runtime state and processing stages are surfaced through the Gateway.
Reconciliation ends its database snapshot transaction before inspecting Docker with bounded concurrency. It applies observations in short row transactions after checking the workload generation again, preserving newer runtime heartbeats. Stopping scales a Swarm service to zero and waits for acknowledged task shutdown before releasing its input claim. Retiring task records are checked even if the service was externally removed. Unknown or orphaned tasks and Docker failures retain the claim for retry; lifecycle cleanup relies on an available Swarm manager with authoritative task records during node recovery.
Collection changes persist stop intent and release catalogue locks while affected workloads stop. They recheck both catalogue and collection revisions before applying the prepared change. A concurrent edit rejects the stale change while completed monitor stops remain durable.
4. Evidence and anomaly flow¶
- RADAR consumes selected telemetry topics from EMQX.
- The configured strategy calculates scores, p-values where applicable, and tracker evidence.
- Evidence is written to
monitoring_evidence; threshold crossings produceanomaliesrecords. - The frontend retrieves service state, evidence, counts, and events through the Gateway.
Startup order and readiness¶
Local Compose starts everything with one command; its depends_on health
conditions form two chains:
timescaledb→db-sync(/ready/schema) →tactic-middleware(/ready) →api-gateway(/health) →frontend.timescaledb,db-syncandemqx-main→mqtt-proxy(/readyonce the collection revision is applied).
Production Swarm has no start order; the release gates on
infra/scripts/check_application.py instead.
Gateway liveness can succeed while TACTIC-backed data or monitoring operations remain unavailable.
Service-to-service HTTP contracts¶
| Caller | Callee | Critical paths | Purpose |
|---|---|---|---|
| Frontend | API Gateway | /v1/auth/*, /v1/chargers/*, /v1/telemetry/*, /v1/monitors/*, /v1/anomalies* |
Primary user contract |
| API Gateway | TACTIC | /api/v1/data/*, /api/v1/members/*, /api/v1/system/*, /api/v1/monitors/*, /api/v1/orchestration/radar/*, /api/v1/models/* |
Data access, membership, catalogue, pipelines and orchestration; test_tactic_gateway_contract.py checks every call |
| API Gateway (startup) | DB Sync | /health |
Waits for the schema service before serving |
| Health tooling | API Gateway | /health |
Gateway liveness |
| Health tooling | TACTIC | /health, /ready |
Orchestration and registry readiness |
| Internal checks | DB Sync | /health, /ready/schema |
Schema readiness |
| Internal checks | MQTT Proxy | /health, /ready |
Collection readiness |
| Release gate | Every service | infra/scripts/check_application.py |
Production readiness after deployment |
Failure modes and recovery checkpoints¶
| Symptom | Likely breakpoint | First checks |
|---|---|---|
| Frontend loads but has no data | Gateway-to-TACTIC data path | Gateway health, TACTIC readiness, both logs |
| Monitoring start fails | Model validation, sensor ownership, or Docker API | TACTIC readiness, /v1/monitors/models, active services |
| Telemetry is missing | Source, proxy parsing, or database write | Source publish, proxy logs/readiness, database health |
| Evidence is absent | RADAR workload or broker topic path | Service list, RADAR logs, EMQX stream |
| Authentication email is missing | SMTP configuration | Mail service logs and EMAIL_* transport settings |
Deployment context¶
- Local default:
docker compose up -d --build(loadsdocker-compose.override.ymlfor container names, ports, file sync and reload) - Cluster override:
docker-compose.cluster.yml - Swarm:
infra/stack/docker-compose.swarm.yml - Swarm ingress overlay: add the integrated production ingress overlays
See Deployment modes for operational commands.
Aberrant catalog integration¶
RADAR pins the aberrant[faiss] release in
backend/services/mqtt/radar/pyproject.toml. Shared configuration, Gateway, and TACTIC
read off_key_core/models/adaptive_catalog.json; they do not import NumPy,
SciPy, FAISS, or Aberrant. The snapshot is generated from the same installed
library and optional dependencies as RADAR. models/adaptive.py owns only the
supported-model policy, application defaults, resource limits, and compatibility
translations. Constructor schemas and default capability descriptions come
from Aberrant. Algorithm family is separate from Offkey's adaptive_aberrant
routing family.
After changing the dependency pin or model policy, regenerate and verify:
uv sync --project backend --all-packages --all-groups --frozen
uv run --project backend python -m off_key_mqtt_radar.catalog --write
uv run --project backend python -m off_key_mqtt_radar.catalog --check
CI compares the snapshot with the installed runtime, and RADAR's Docker build
runs the same check. API responses expose catalog_id, algorithm_family,
available, and default_capabilities. These capabilities describe application
defaults, not arbitrary edited parameters. Shared request validation checks
schemas, sensor cardinality, resource budgets, and preprocessing policy. RADAR
resolves actual capabilities, checks model/PCA warm-up, and executes constructor
validation at startup. Models needing observer-, bucket-, or graph-based
readiness cannot be added by simply editing the allowlist.
AdaptiveStreamConfig.detector_mapping() produces the versioned Aberrant
configuration, with a feature-schema guard first. Pipeline monitors build the
same configuration in pipelines/adapters.py (see
Monitoring pipelines); both fill the detector's
feature-bound parameters (abs_diff, key, keys) through
bind_detector_parameters in off_key_core.models.adaptive. RADAR constructs the pipeline
through DetectorConfig, then applies Offkey's explicit unit-interval input
policy where required. Checkpoints contain the normalized configuration,
configuration fingerprint, input keys, package version, and lifecycle state.
Signed atomic checkpoint I/O remains owned by Offkey. Runtime model information
includes the resolved model identity and capabilities.
Integrated production releases seed the tested RADAR image on backend nodes, stop
active workers and restart their saved configurations under new UUIDs with fresh
calibration. The production workflow verifies them before completing the release.
Adaptive configuration carries its expected Aberrant version to reject mixed
versions. Cross-version model pickles are incompatible: preserve them for a
rollback, but allow the upgraded monitors to warm up and calibrate afresh.
Validate the image and the opt-in test_adaptive_lane_e2e.py flow against a
non-production MQTT/PostgreSQL stack before deploying.