docs(architecture): MQTT contract reference
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
# MQTT contract
|
||||
|
||||
The MQTT topic schema is the inter-process contract of the platform. All publishers and subscribers must respect this schema. Versioned in `packages/proto-mqtt/` of the future code repo as JSON Schema files.
|
||||
|
||||
See [ADR-0003](../../decisions/0003-mqtt-como-espina-dorsal.md) for why MQTT is the spine.
|
||||
|
||||
## Naming conventions
|
||||
|
||||
- Lowercase, slash-separated.
|
||||
- Site prefix only when bridged to hub: `sites/<site_id>/...`.
|
||||
- Local topics (within a site) **don't** include the site prefix.
|
||||
- Underscore prefix (`_registry`, `_cmd`, `_bridge`) marks system-level topics, distinct from device topics.
|
||||
- `+` is single-level wildcard, `#` is multi-level (MQTT standard).
|
||||
|
||||
## Topic catalog
|
||||
|
||||
### Device events
|
||||
|
||||
```
|
||||
hai/<cam_id>/events/<event_type>
|
||||
```
|
||||
|
||||
- `event_type` ∈ `person | car | truck | motorcycle | bicycle | animal | package | license-plate | custom`.
|
||||
- Payload (JSON):
|
||||
```json
|
||||
{
|
||||
"id": "evt-8a2f",
|
||||
"label": "person",
|
||||
"conf": 0.92,
|
||||
"zone": "entrance",
|
||||
"ts": "2026-05-09T19:34:22.142Z",
|
||||
"ts_nts": "2026-05-09T19:34:22.142Z",
|
||||
"sha_clip": "a1f7...",
|
||||
"sha_keyframe": "b2c8..."
|
||||
}
|
||||
```
|
||||
- QoS: 1.
|
||||
- Retained: no.
|
||||
- Bridged: yes, with prefix `sites/<site_id>/`.
|
||||
|
||||
### Device state
|
||||
|
||||
```
|
||||
hai/<cam_id>/state/<key>
|
||||
```
|
||||
|
||||
- `key` ∈ `online | fps | npu_pct | last_keepalive | error`.
|
||||
- Retained: yes.
|
||||
- Bridged: yes (status info needed at hub).
|
||||
|
||||
### Snapshots
|
||||
|
||||
```
|
||||
hai/<cam_id>/snapshots/<n>
|
||||
```
|
||||
|
||||
- Binary JPEG, ~50-100KB.
|
||||
- Retained: yes (last frame).
|
||||
- **Never bridged** (sovereignty rule, see [ADR-0005](../../decisions/0005-sovereignty-vs-hyperscaler.md)).
|
||||
|
||||
### Telemetry (raw)
|
||||
|
||||
```
|
||||
hai/<cam_id>/telemetry/<key>
|
||||
```
|
||||
|
||||
- `key` ∈ `fps | bitrate | latency | drops`.
|
||||
- High-frequency, low-value individually.
|
||||
- Retained: no.
|
||||
- **Never bridged** raw — aggregated and republished by `healthd`.
|
||||
|
||||
### Service registry
|
||||
|
||||
```
|
||||
hai/_registry/announce
|
||||
```
|
||||
|
||||
- Published by Cell containers and other discoverable services.
|
||||
- Payload:
|
||||
```json
|
||||
{
|
||||
"type": "frigate | enricher | re-id | healthd | hai-console",
|
||||
"id": "cell-01",
|
||||
"host": "192.168.20.10",
|
||||
"port": 5000,
|
||||
"caps": ["frigate", "reid"],
|
||||
"version": "0.4.2",
|
||||
"started_at": "2026-04-25T14:18:00Z"
|
||||
}
|
||||
```
|
||||
- Retained: yes.
|
||||
- Bridged: yes (hub needs to know what's deployed where).
|
||||
|
||||
```
|
||||
hai/_registry/heartbeat/<id>
|
||||
```
|
||||
|
||||
- Published every 30s by each registered service.
|
||||
- Retained: no.
|
||||
- Bridged: no (hub uses bridge state for liveness).
|
||||
|
||||
### Health reports
|
||||
|
||||
```
|
||||
hai/healthd/health
|
||||
```
|
||||
|
||||
- Aggregated selftest result, published every 5min.
|
||||
- Payload: structured object with results per test.
|
||||
- Retained: yes.
|
||||
- Bridged: yes (hub needs health for fleet view).
|
||||
|
||||
### Bridge status
|
||||
|
||||
```
|
||||
hai/_bridge/status
|
||||
```
|
||||
|
||||
- Published by router to indicate bridge health.
|
||||
- Payload: `{ "connected": true, "queue": 0, "rtt_ms": 38, "last_disconnect": "..." }`.
|
||||
- Retained: yes.
|
||||
- Special: this topic is **published locally by the router** about its own bridge state. Operators see it in the MQTT panel.
|
||||
|
||||
### Commands (incoming from hub)
|
||||
|
||||
```
|
||||
_cmd/<command>
|
||||
```
|
||||
|
||||
- Published by the hub, consumed by router or Cell services.
|
||||
- Payload includes `requested_by`, `ts`, `correlation_id`.
|
||||
- Bridged inbound: yes, with hub-side topic `sites/<site_id>/_cmd/<command>` mapped to local `_cmd/<command>`.
|
||||
|
||||
### Configuration changes (incoming from hub)
|
||||
|
||||
```
|
||||
_config/<resource>
|
||||
```
|
||||
|
||||
- Used by hub to push GitOps-equivalent config updates.
|
||||
- In practice, GitOps does most of this; this topic is reserved for low-latency overrides.
|
||||
- Retained: yes.
|
||||
- Bridged inbound: yes.
|
||||
|
||||
### Evidence chain (post-MVP, see ADR-0007)
|
||||
|
||||
```
|
||||
hai/_evidence/manifest
|
||||
```
|
||||
|
||||
- Published whenever a clip is finalized with its signed manifest.
|
||||
- Bridged: yes (hub keeps a second copy for hash-chain verification).
|
||||
|
||||
## QoS guidelines
|
||||
|
||||
- **QoS 0**: telemetry, snapshots, frequent low-value data.
|
||||
- **QoS 1**: events, state, registry, health, commands. Default for anything that should not be lost.
|
||||
- **QoS 2**: not used. Cost not justified for our use cases.
|
||||
|
||||
## ACL guidelines
|
||||
|
||||
- Cell containers can `pub/sub` only `hai/<their_id>/...` and `hai/_registry/announce`.
|
||||
- Router can `pub/sub` everything locally.
|
||||
- Bridge writes only to remapped namespaces, can't pub locally.
|
||||
- Operator console (read-only) can subscribe to `hai/#` but not publish.
|
||||
- Future: per-operator ACLs based on Keycloak roles.
|
||||
|
||||
## Bridge policy (sovereignty matrix)
|
||||
|
||||
What goes UP (site → hub):
|
||||
|
||||
```
|
||||
hai/+/events/# → sites/<site_id>/hai/+/events/# QoS 1
|
||||
hai/+/state/# → sites/<site_id>/hai/+/state/# QoS 1, retained
|
||||
hai/_registry/announce → sites/<site_id>/_registry/announce QoS 1, retained
|
||||
hai/+/health → sites/<site_id>/health/+ QoS 0
|
||||
hai/_bridge/status → sites/<site_id>/_bridge/status QoS 1, retained
|
||||
hai/_evidence/manifest → sites/<site_id>/_evidence/manifest QoS 1
|
||||
```
|
||||
|
||||
What goes DOWN (hub → site):
|
||||
|
||||
```
|
||||
sites/<site_id>/_cmd/# → _cmd/# QoS 1
|
||||
sites/<site_id>/_config/# → _config/# QoS 1, retained
|
||||
```
|
||||
|
||||
What is **never bridged**:
|
||||
|
||||
```
|
||||
hai/+/snapshots/# (binary JPEGs, sovereignty)
|
||||
hai/+/telemetry/# (raw, aggregated only)
|
||||
$SYS/# (broker internals)
|
||||
```
|
||||
|
||||
This policy lives in `/etc/mosquitto/conf.d/bridge.conf` on the router, version-controlled in the site-config repo. The MQTT panel in the console renders it from the live config.
|
||||
Reference in New Issue
Block a user