Files
wdmUI/docs/01-architecture/mqtt-contract.md
T

5.3 KiB

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 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_typeperson | car | truck | motorcycle | bicycle | animal | package | license-plate | custom.
  • Payload (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>
  • keyonline | 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).

Telemetry (raw)

hai/<cam_id>/telemetry/<key>
  • keyfps | 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:
    {
      "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.