Fire auto-dialer integration — NET WIZARDS Gateway (MQTT)
The fire signal for the Saudi Airlines / Arafat pilot comes from a NET WIZARDS Gateway (a division of WOM Technologies, Dubai) — an
industrial VFC-to-cloud fire-alarm communicator. This note is the
durable picture of how it integrates, what’s decided, what’s unknown,
and what’s gated. Source docs: _inbox/NW-INT-GW-25-001_v2.0.pdf (preliminary integration overview) + the WOM email thread.
TL;DR
- The Gateway is an outbound MQTT client that connects directly to a broker WE host (in our cloud, in-Kingdom). It does not route through our edge box, and it does not route through WOM’s cloud. Alarm data never touches a third-party cloud.
- We own: the broker, the data-plane CA + client certs, the topic namespace + tenant/site/gateway IDs, the downlink command channel, persistence, visualisation.
- WOM owns: the device + firmware, and firmware/OTA on a separate plane to their own cloud (never our concern, never carries alarm data).
- The exact alarm payload schema is NOT public — it’s in their ICD
(
NW-ICD-GW-26-001, v1.2), released only under a signed NDA + commercial term sheet. We have the envelope (enough to build the pipe), not the typed body.
Earlier misread — corrected
An earlier review flagged a data-residency/NCA problem because WOM’s
materials reference …s1.eu.hivemq.cloud and iiot.com.co. That was
WOM’s own demo/PoC cloud for a different prospect — not our integration
path. The integration overview is explicit: “the platform owns the
receiving endpoint, the CA, and the identifiers.” We host the broker,
in-Kingdom. There is no third-party-cloud residency issue for alarm
data. (OTA/firmware is the only thing that talks to WOM’s cloud, on a
physically separate trust plane.)
Topology
Fire panel ──VFC (8/16 dry contacts)──▶ NET WIZARDS Gateway
│ 4G/LTE (dual SIM) or Ethernet
▼ MQTTS, mTLS, outbound
┌───────────────────────────┐
│ OUR MQTT broker (cloud, │
│ in-Kingdom) │
└───────────────────────────┘
│ subscribe nw/v1/+/+/+/up/#
▼
our ingest → alarm_event → alert → FD view
│ publish …/dn/command
▼ (downlink: ack, config) Separately, OTA plane: Gateway ⇄ NET WIZARDS Cloud, firmware only, their CA, no partner role. Out of scope for us.
The data contract we DO know (the envelope)
Every message, every transport, both directions, is one JSON object:
{
"messageId": "<uuid v4>", // idempotency key
"messageType": "alarm | heartbeat | command | ack | ...",
"schemaVersion": "1.0.0",
"timestamp": "<ISO-8601 UTC, ms>",
"sequenceNumber": 0, // monotonic, per gateway
"tenantId": "...",
"siteId": "...",
"gatewayId": "...",
"direction": "uplink | downlink",
"payload": { /* typed by messageType — schema in the ICD */ }
} Addressing model (we choose {root}; v1 is their schema version):
{root}/v1/{tenantId}/{siteId}/{gatewayId}/{direction}/{messageType}
e.g. nw/v1/saudia/arafat/gw-001/up/alarm (QoS 1, lossless)
nw/v1/saudia/arafat/gw-001/up/heartbeat (QoS 0)
nw/v1/saudia/arafat/gw-001/dn/command (QoS 1)
backend subscribes: nw/v1/+/+/+/up/# Bidirectional is mandatory. Their words: “A platform that consumes
only the uplink does not constitute a complete integration.” We must
publish downlink (operator acknowledge, runtime config) to …/dn/command. The command vocabulary is in the ICD.
What we must provide WOM for the PoC
Per their email, to commission the device they need from us:
- MQTT broker URL / IP
- Port (1883 for the testing phase — see the security split below)
- Broker username + password
- The topic structure we want the device to publish to (our
{root}+ the tenant/site/gateway ids we assign this unit)
Plus on-site: 2× 4G data SIMs, 2-wire VFC cabling from the FACP to the gateway terminals. PoC trigger = a VFC contact closure, not the building BMS. (Short the input → device publishes an alarm. That is literally the “simulate a fire → alert in one place” demo.)
Security: a deliberate PoC-vs-production split
Decision: the 1883/plaintext-password path is a NO. WOM’s PoC email asks for username/password on 1883; their own spec forbids it (“mTLS only, no support for unencrypted transport in any deployment, including test and commissioning”). The “isolated, no real data” justification is false: the PoC runs at the real pilot site, wired to a real fire panel (VFC trigger) — a forged or sniffed alarm in the Makkah/Hajj context is a mass-safety incident, not a lab test. We already run a gateway-CA pattern for the edge fleet, so mTLS/8883 from first contact is achievable. All deployments, including the very first commissioning test, are mTLS/8883. Push WOM to configure the device accordingly; do not stand up a 1883 broker.
Production posture (= all postures): mqtts/8883, mTLS X.509, our
data-plane CA issuing/rotating/revoking per-gateway client certs, with
broker per-cert topic ACLs (a gateway may publish only under its own {tenant}/{site}/{gateway} prefix).
Impact on existing code
apps/api/src/device-ingest/* (POST /v1/device-alarms, HMAC) was
built before these docs. It is the wrong transport — the real path
is MQTT-broker ingest, and auth is mTLS not HMAC. Marked superseded
(not deleted): the envelope/idempotency/replay + fail-closed thinking
and the adversarial findings carry over. Keep the HTTPS binding as a
documented fallback — the vendor offers HTTPS with identical
semantics, and if broker hosting stays cost-gated the HTTP path is
closer to working than an unbuilt broker.
#237 is HARDER under MQTT, not “unchanged” (correcting an earlier
overstatement). The HTTP path at least had a per-request guard
setting req.wiqaia. An MQTT broker subscriber is a single long-lived
background consumer with no per-message HTTP request and no JWT — prisma.runAsUser needs an AuthContext it now has no natural source
for. The mTLS client cert authenticates the gateway to the broker (and the broker, not Nest, terminates that TLS), not a user to the
API. So identity must be resolved per-alarm from the broker-authenticated cert CN, mapped to a real seeded
building-scoped service identity — NOT from the envelope fields
(attacker-influenced), NOT a synthesised phantom user (silently fails
RLS), NOT RLS-widening (cross-tenant write hole). This is a
blocking, MQTT-specific design problem. See Gitea #237.
Delivery-assurance gaps — DESIGN before any build (life-safety)
An adversarial review (2026-05-15) found the commercial/topology story sound but the delivery-assurance core essentially undesigned. A lost alarm is as fatal as a forged one. These are gating designs, not “footnotes”:
- G1 — Lost-alarm / sequence-gap detection. QoS 1 only guarantees
delivery if the message reaches a persisting broker. No-signal /
broker-down / non-persistent-session = a real alarm vanishes and nothing notices (heartbeat is QoS 0, itself lossy). Need: a
per-gateway last-seen-
sequenceNumbertracker that raises a CRITICAL “possible missed alarm” on any gap or heartbeat-silence. Open question for WOM: does the gateway buffer alarms across connectivity loss and replay on reconnect? (vendor docs don’t say — must ask). - G2 — Broker is an unmitigated SPOF for the fire signal. Need: broker HA/cluster, persistent sessions (clean-session=false, non-expiring, server-side queueing), defined behaviour for alarms published during a broker restart/deploy, and broker-down synthetic monitoring.
- G3 — Double dispatch. Two API replicas both subscribed to
…/+/up/#each receive every alarm; QoS 1 is at-least-once. Todayalarm-ingest.service.tshas no idempotency key at all. Need: MQTT v5 shared subscriptions for the fleet topic AND a durable, atomicmessageIdunique constraint enforced in the same tx that creates the alert. (Ties to #237 H1.) - G4 — Identity-spoofing / topic-cert binding.
tenant/site/gatewayin the topic+envelope are attacker-influenceable. Broker must enforce per-cert topic ACLs; ingest must assert cert-CN == topic gateway == envelope gateway, fail closed on mismatch. - G5 — Downlink command authz. We must publish
…/dn/command(ack, config, and per vendor §7 re-assignment which can move a gateway to another tenant/site). Needs an authz model: which operator role, signed commands, gateway-side validation, audit. Undefined. - G6 — Data-plane PKI lifecycle. We own issuance + renewal +
revocation for every field gateway cert. An expired cert = that
building’s fire path silently dead. Needs rotation + expiry alerting
- revocation enforced at the broker (CRL/OCSP). A named workstream, not a one-liner.
- G7 — 7-year audit retention. Vendor checklist requires audit-grade retention (~7 yr for life-safety). Not yet in our persistence design.
Decisions / gates (owner / commercial action)
- NDA + commercial term sheet with NET WIZARDS → unlocks the ICD
(
NW-ICD-GW-26-001v1.2), JSON schemas, conformance sandbox. Without it the typedpayloadmapping cannot be finalised. Commercial — CEO. - Broker hosting in our cloud (GCP Dammam, in-Kingdom) — needs cloud applied (cost-gated) or a reachable endpoint. Owner.
- Ask WOM: gateway store-and-forward behaviour on connectivity loss? (G1 — the single most important life-safety unknown.)
- #237 identity model — now MQTT-specific: per-gateway identity derived from the broker-authenticated cert CN → a real seeded building-scoped service account. Blocked until ICD + broker model settled; do NOT guess (re-confirmed by review).
(The earlier “accept 1883 for the PoC?” decision is closed: NO — see the security section. All deployments are mTLS/8883.)
What’s actually buildable now (honest)
Only inert scaffolding: the broker infra-as-code (prod mTLS profile
only — no 1883 profile), and envelope validation + routing skeleton
that fails LOUD on an unknown messageType or unmapped alarm type (a silently-non-actionable real fire is fatal — the fire/smoke/heat/manual_pull/sprinkler_flow mapping is itself a guess
until the ICD). Everything load-bearing — idempotency (G3), lost-alarm
detection (G1), broker HA (G2), identity (#237/G4), downlink authz
(G5), PKI (G6) — is design-blocked, not buildable. Do not
characterise any of it as “ready” beyond scaffolding.