Notifications Architecture

⚠ Superseded (2026-07-12, CLO-7). This is a stale secondary doc; the authoritative Notify dossier is architecture.md(and ../notifier/architecture.mdfor the deployed worker host). Known rot below, kept only for historical context: the runtime diagram labels the delivery worker Notify.Worker, a project that does not exist (the worker is StellaOps.Notifier.Worker under src/Notifier/); and the §6 roadmap lists NOTIFY-SVC-38/39-* correlation/digest/simulation/quiet-hours work as future although all of it has shipped. Do not rely on this file for current contracts.

Imposed rule: Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.

This dossier distils the Notify architecture into implementation-ready guidance for service owners, SREs, and integrators. It complements the high-level overview by detailing process boundaries, persistence models, and extensibility points.


1. Runtime shape

          ┌──────────────────┐
          │ Authority (OpTok)│
          └───────┬──────────┘
                  │
          ┌───────▼──────────┐        ┌───────────────┐
          │ Notify.WebService│◀──────▶│ PostgreSQL    │
Tenant API│  REST + gRPC WIP │        │ rules/channels│
          └───────▲──────────┘        │ deliveries    │
                  │                   │ digests       │
   Internal bus   │                   └───────────────┘
 (NATS/Valkey/etc)│
                  │
        ┌─────────▼─────────┐      ┌───────────────┐
        │ Notify.Worker     │◀────▶│ Valkey / Cache│
        │ rule eval + render│      │ throttles/locks│
        └─────────▲─────────┘      └───────▲───────┘
                  │                        │
                  │                        │
           ┌──────┴──────┐       ┌─────────┴────────┐
           │ Connectors  │──────▶│ Slack/Teams/...  │
           │ (plug-ins)  │       │ External targets │
           └─────────────┘       └──────────────────┘

Both services share options via notify.yaml (see etc/notify.yaml.sample). For dev/test scenarios, an in-memory repository exists but production requires PostgreSQL + Valkey/NATS for durability and coordination.


2. Event ingestion and rule evaluation

  1. Subscription. Workers attach to the internal bus (Valkey Streams or NATS JetStream). Each partition key is tenantId|scope.digest|event.kind to preserve order for a given artefact.
  2. Normalisation. Incoming events are hydrated into NotifyEvent envelopes. Payload JSON is normalised (sorted object keys) to preserve determinism and enable hashing.
  3. Rule snapshot. Per-tenant rule sets are cached in memory. PostgreSQL LISTEN/NOTIFY triggers snapshot refreshes without restart.
  4. Match pipeline.
    • Tenant check (rule.tenantId vs. event tenant).
  1. Idempotency. Each action computes hash(ruleId|actionId|event.kind|scope.digest|delta.hash|dayBucket); matches within throttle TTL record status=Throttled and stop.
  2. Dispatch. If digest is instant, the renderer immediately processes the action. Otherwise the event is appended to the digest window for later flush.

Failures during evaluation are logged with correlation IDs and surfaced through /stats and worker metrics (notify_rule_eval_failures_total, notify_digest_flush_errors_total).


3. Rendering & connectors


4. Persistence model

TablePurposeKey fields & indexes
rulesTenant rule definitions.id, tenant_id, enabled; index on (tenant_id, enabled).
channelsChannel metadata + config references.id, tenant_id, type; index on (tenant_id, type).
templatesLocale-specific render bodies.id, tenant_id, channel_type, key; index on (tenant_id, channel_type, key).
deliveriesLedger of rendered notifications.id, tenant_id, sent_at; compound index on (tenant_id, sent_at DESC) for history queries.
digestsOpen digest windows per action.id (tenant_id:action_key:window), status; index on (tenant_id, action_key).
throttlesShort-lived throttle tokens (PostgreSQL or Valkey).Key format idem:<hash> with TTL aligned to throttle duration.

Records are stored using the canonical JSON serializer (NotifyCanonicalJsonSerializer) to preserve property ordering and casing. Schema migration helpers upgrade stored records when new versions ship.


5. Deployment & configuration


6. Roadmap alignment

BacklogArchitectural note
NOTIFY-SVC-38-001Standardise event envelope publication (idempotency keys) – ensure bus bindings use the documented key format.
NOTIFY-SVC-38-002..004Introduce simulation endpoints and throttle dashboards – expect additional /internal/notify/simulate routes and metrics; update once merged.
NOTIFY-SVC-39-001..004Correlation engine, digests generator, simulation API, quiet hours – anticipate new PostgreSQL tables (quiet_hours, correlation caches) and connector metadata (quiet mode hints). Review this guide when implementations land.

Action: schedule a documentation sync with the Notifications Service Guild immediately after NOTIFY-SVC-39-001..004 merge to confirm schema adjustments (e.g., correlation edge storage, quiet hour calendars) and add any new persistence or API details here.


Imposed rule reminder: Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.