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:

  1. MQTT broker URL / IP
  2. Port (1883 for the testing phase — see the security split below)
  3. Broker username + password
  4. 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 JWTprisma.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-sequenceNumber tracker 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. Today alarm-ingest.service.ts has no idempotency key at all. Need: MQTT v5 shared subscriptions for the fleet topic AND a durable, atomic messageId unique constraint enforced in the same tx that creates the alert. (Ties to #237 H1.)
  • G4 — Identity-spoofing / topic-cert binding. tenant/site/gateway in 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)

  1. NDA + commercial term sheet with NET WIZARDS → unlocks the ICD (NW-ICD-GW-26-001 v1.2), JSON schemas, conformance sandbox. Without it the typed payload mapping cannot be finalised. Commercial — CEO.
  2. Broker hosting in our cloud (GCP Dammam, in-Kingdom) — needs cloud applied (cost-gated) or a reachable endpoint. Owner.
  3. Ask WOM: gateway store-and-forward behaviour on connectivity loss? (G1 — the single most important life-safety unknown.)
  4. #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.

WiqAIa+ Docs · Rizoma