Full System Test Report

Event → alert/notification pipeline · 2 service bundles · 2 companies · run 2026-10-04 · branch feat/event-outbox-e4 on mapp.doriai.com

PASS  Event delivered end-to-end from the (mock) MLC to the correct per-company MQTT contact point, for both companies. Email channel blocked by missing Gmail credentials (see caveats).

Architecture under test

ConceptModel
Contact pointsRoot / system level (Dori company) — one shared pool; platform-admin managed; visible to every company.
Notification routesPer company — each route wires an event → a root contact point.
Events for routingScoped to the company's service bundles (what its cameras run).
Notify gateTrigger fires pg_notify when is_notification OR an active route matches (no is_message/is_trigger).
MLCedge_simulator (mock MLC) — POST /sim/inject/inference.

Provisioned configuration

CompanyService bundle → events (notify)Routes (event → contact point)
Acme Warehousing (co 2)Warehouse Vision: Pallet Jam ✓, Forklift NearMiss ✓, Pallet Clear ✗Pallet Jam → Ops MQTT C2 dori/2/alerts + Ops Email C2; Forklift NearMiss → Ops MQTT C2
RetailCo Stores (co 3)Retail Vision: Shelf Empty ✓, Queue Overflow ✓, Shelf Restocked ✗Shelf Empty → Ops MQTT C3 dori/3/alerts + Ops Email C3; Queue Overflow → Ops MQTT C3

Contact points are root-level (shared pool of 5): Ops MQTT, Ops MQTT C2/C3, Ops Email C2/C3. Each company also has credentials, an MLC + config, a user (entity), a location and a camera (device).

Pipeline exercised

mock MLC (/sim/inject/inference) → Kafka (dori.inference.events) → analytics_app → INSERT inference.inference_event → notify_process() trigger (route-gated) → pg_notify('event_insert') → DIM (websocket) + CIM /event_process/ → CIM dispatch_via_routes → MQTT contact point (mosquitto) / email

Results

#CheckResult
1Root contact points visible to any company (global pool)PASS — 5 root contact points listed
2Events scoped to each company's bundlesPASS — co2 = Warehouse events, co3 = Retail events
3Routes are per company; new route references a root contact pointPASS
4Inject Pallet Jam (Acme) → inference_event written, company resolved via job→devicePASS
5CIM dispatch via routesPASS — dispatched-via-routes
6MQTT alert delivered to the correct per-company topicPASS
7Email alert deliveredBLOCKED — Gmail creds missing on host

MQTT messages captured on the broker (the payoff):

dori/2/alerts   Pallet Jam on 1:     ← Acme (Warehouse Vision)
dori/3/alerts   Shelf Empty on 2:    ← RetailCo (Retail Vision)

CIM dispatch log:

event_process type=Pallet Jam  company=2 -> dispatched-via-routes (1 recipients)
event_process type=Shelf Empty company=3 -> dispatched-via-routes (1 recipients)

Caveats / follow-ups

Email channel. send_gmail needs /app/config/client_secret.json (Gmail OAuth) or a test webhook (EMAIL_TEST_WEBHOOK). Not present on the box, so email routes error. The route resolution and recipient selection (incl. address resolved from the assigned user entity) work; only the actual send is blocked.
MQTT broker. The deployed docker-compose.yml predates the test services, so the broker ran as a standalone mosquitto container on the app network. It is not in compose and won't survive a stack restart — add mosquitto to the deployed compose for persistent MQTT.
Fix applied during this test. CIM only connected its MQTT publisher when EVENT_METHOD=mqtt (default kafka), so route-based MQTT alerts never reached the broker. CIM now connects the MQTT publisher unconditionally.

Related: event pipeline design · Notifications Console · DB data model · ICD data model