PRD: Device signal & notifications
| Module | Device (CORE-04) | PRD ID | PRD-MON-001 |
| Status | In-progress | Owner | Device / Signal squad |
| Date | 2026-05-21 | Version | v0.1 |
| Packages | @nx/signal · @nx/outreach · @nx/core | URD | MON |
TL;DR
Gives KICKO a server-side activity-notification backbone: a Kafka consumer ingests activity events, a worker resolves the right recipient users within an organizer/merchant scope, persists a notification record, and pushes it live to connected clients over WebSocket. The result - administrators see device and platform activity surface in near real time instead of polling, and the first live use case (inquiry-submitted) lights up end to end.
1. Context & Problem
Device Identity & Health Monitoring (MON) needs the platform to capture activity and surface it to administrators in near real time. Today there is no server-side notification backbone: events happen in commerce/outreach but nothing fans them out to the right users or pushes them to a live UI, so administrators cannot watch activity as it happens.
This increment builds that backbone in @nx/signal - an end-to-end activity-notification pipeline (Kafka consumer → worker → persisted record → WebSocket emit) - together with recipient resolution scoped to an organizer/merchant and a stable WebSocket transport. It is the real-time transport that monitoring and activity surfacing ride on; heartbeat/health-status reporting itself (URD-MON-001/002) and remote device actions (URD-MON-004/005) are tracked separately.
2. Goals & Non-Goals
Goals
- A Kafka-fed activity-notification pipeline in
@nx/signal: consumer → worker → persisted record → WebSocket emit. - Recipient resolution - notifications fan out to the correct users within an organizer/merchant scope.
- A stable WebSocket transport (rooms + topics + socket-event service) with a fixed room/topic naming (
signal/notification,NOTIFICATION_CREATED). - Notification read-state: REST endpoints to fetch notifications and mark them read.
- First real-time use case end to end: inquiry-submitted WebSocket notification in
@nx/outreach.
Non-Goals
- Heartbeat emission and the 15-minute offline threshold (URD-MON-001/002).
- Remote deactivate / remote data wipe (URD-MON-004/005).
- The per-device health dashboard UI (the consuming client surface).
- Porting the notification toolkit out of
@nx/signalinto a shared package.
3. Success Metrics
| Metric | Target / signal |
|---|---|
| Delivery latency | Activity event → client WebSocket emit in near real time (sub-second under normal load) |
| Recipient accuracy | Notifications reach exactly the users within the organizer/merchant scope; no cross-tenant leakage |
| Durability | Every emitted notification has a persisted record; read-state survives reconnect |
| Coverage | First live path (inquiry-submitted) verified end to end; pipeline reusable for future event types |
4. Personas & Use Cases
| Persona | Goal in this feature |
|---|---|
| Administrator / Owner | See platform & device activity surface live without polling |
| Connected client (web/app) | Receive notifications over a live WebSocket and reflect read-state |
| Outreach handler | Be alerted the moment an inquiry is submitted |
Core scenarios: an activity event lands on Kafka → the worker resolves recipients within the organizer/merchant scope and persists a notification → the record is pushed over WebSocket to connected clients → a client fetches its notifications and marks them read; an inquiry submission emits a live WebSocket notification through the same transport.
5. User Stories
- As an administrator, I want activity to appear in my UI as it happens, so that I can monitor the platform without refreshing.
- As a connected client, I want to receive notifications over a live WebSocket, so that I see updates the instant they occur.
- As a connected client, I want to fetch my notifications and mark them read, so that read-state is consistent across sessions.
- As an outreach handler, I want a live notification when an inquiry is submitted, so that I can respond promptly.
- As the platform, I want notifications to fan out only to users within the right organizer/merchant scope, so that no tenant sees another's activity.
6. Functional Requirements
| # | Requirement | URD ref |
|---|---|---|
| FR-1 | A Kafka consumer ingests activity events into the notification pipeline | URD-MON-003 |
| FR-2 | A worker resolves recipient user IDs within an organizer/merchant scope | URD-MON-003 |
| FR-3 | Each notification is persisted as an activity-notification record in @nx/core | URD-MON-003 |
| FR-4 | A WebSocket transport (rooms + topics + socket-event service) pushes the notification to connected clients live, on room signal/notification / topic NOTIFICATION_CREATED | URD-MON-003 |
| FR-5 | REST endpoints fetch a user's notifications and mark them read | URD-MON-003 |
| FR-6 | Inquiry submission in @nx/outreach emits a WebSocket notification to interested clients | URD-MON-003 |
Full requirement text and acceptance criteria live in the Device URD. This PRD references them rather than restating them.
7. Non-Functional Requirements
| Area | Requirement |
|---|---|
| Tenancy & authz | Recipient resolution is scoped per organizer/merchant; notifications never cross tenant boundaries |
| Durability | Every WebSocket emit is backed by a persisted notification record; read-state is retrievable after reconnect |
| Performance / scale | Kafka decouples ingest from fan-out; the worker handles bursts without blocking producers |
| Transport stability | Fixed room/topic naming (signal/notification, NOTIFICATION_CREATED) so clients subscribe against a stable contract |
| i18n | User-facing notification labels are bilingual ({ en, vi }) where surfaced |
8. UX & Flows
Key surfaces: the notification component lives in @nx/signal (components/notification - consumer, worker, socket-event service, rooms, topics) with REST in controllers/activity-notifications; the inquiry path lives in @nx/outreach (components/websocket, controllers/inquiry). The consuming dashboard UI is out of scope for this increment.
9. Data & Domain
| Entity | Role |
|---|---|
activity-notification | The persisted notification record (recipient, payload, read-state) in @nx/core |
| Notification component | @nx/signal Kafka consumer, worker, socket-event service, rooms, topics |
| WebSocket room / topic | signal/notification room and NOTIFICATION_CREATED topic - the live delivery channel |
| Recipient resolution | PolicyDefinitionRepository lookup of user IDs within an organizer/merchant |
Conceptual only - full schema lives in the developer domain model.
10. Dependencies & Assumptions
Depends on
@nx/core- owns theactivity-notificationschema/model/repository, the Kafka topic + types, and recipient resolution viaPolicyDefinitionRepository.- Kafka - the ingest seam between activity sources and the notification worker.
- WebSocket transport - the live delivery channel to connected clients.
@nx/outreach- the first event producer (inquiry submission).
Assumptions
- Activity sources publish events onto the agreed Kafka topic.
- Organizer/merchant membership data is available for recipient resolution.
- Clients maintain a WebSocket connection and subscribe to the
signal/notificationroom.
11. Risks & Open Questions
| Risk / question | Mitigation / status |
|---|---|
| Recipient resolution could leak across tenants | Resolution scoped per organizer/merchant; verified against membership lookup |
| WebSocket disconnects could drop live notifications | Every emit is backed by a persisted record; clients re-fetch read-state on reconnect |
| Room/topic naming drift between producers and clients | Naming fixed to signal/notification / NOTIFICATION_CREATED as a stable contract |
Notification toolkit lives in @nx/signal, not shared | Accepted for this increment; extraction to a shared package is a future consideration |
MON heartbeat/health still pending | Out of scope here; this increment delivers the transport only (see §2) |
12. Release Plan & Launch Criteria
| Aspect | Plan |
|---|---|
| Phase | P2 (MON is a P2, In-progress feature) |
| Rollout | All merchants; no feature flag |
| Migration | New activity-notification entity; no data migration |
| Launch criteria | Activity event → recipient resolution → persisted record → WebSocket emit verified end to end; inquiry-submitted path verified; read-state survives reconnect |
| Monitoring | Notification volume, delivery latency, WebSocket connection health, recipient-resolution correctness |
13. FAQ
Does this deliver device heartbeat / health status? No - this increment delivers the real-time notification transport that monitoring rides on. Heartbeat emission and the offline threshold (URD-MON-001/002) are tracked separately.
How are recipients chosen? The worker resolves user IDs within the relevant organizer/merchant scope via PolicyDefinitionRepository; notifications never cross tenant boundaries.
What happens if a client is disconnected when an event fires? The notification is still persisted; the client re-fetches its notifications and read-state over REST on reconnect.
What room/topic do clients subscribe to? Room signal/notification, topic NOTIFICATION_CREATED - a fixed, stable contract.
Why is the inquiry-submitted path in here? It is the first real-time use case proving the transport end to end, emitted from @nx/outreach over the same WebSocket backbone.
References
- URD: Device - Device Identity & Health Monitoring - requirements (
MONarea) - Module: Device - overview + traceability
- Developer: @nx/signal · @nx/outreach · @nx/core