Files
scadalink-design/docs/plans/2026-05-18-notification-outbox.md
Joseph Doherty f177b58699 docs(plans): implementation plan for the notification outbox
11 bite-sized documentation tasks covering the new component doc and
revisions across the affected component docs, README, and CLAUDE.md,
with a final cross-reference consistency sweep.
2026-05-18 23:01:30 -04:00

19 KiB
Raw Blame History

Notification Outbox Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers-extended-cc:executing-plans to implement this plan task-by-task.

Goal: Update the ScadaLink design documentation to introduce a central Notification Outbox — a new component #21 that receives store-and-forwarded notifications from sites, logs them to a type-agnostic Notifications table, and delivers them with retry, parking, status handles, and KPIs.

Architecture: This is a documentation-only change. The source of truth for the design is docs/plans/notif.md (the approved, refined design). Each task updates one or more existing component documents (or creates the new one) so the documentation set is internally consistent. There is no code and no test suite — verification for each task is a cross-reference consistency check (grep for stale references) plus a read-through against notif.md.

Tech Stack: Markdown design documents under docs/requirements/, the master README.md index, and CLAUDE.md. Conventions are defined in CLAUDE.md (Document Conventions, Editing Rules).

Source of truth: docs/plans/notif.md — every task below implements a slice of it. Read it first.


Task 1: Create Component-NotificationOutbox.md

Files:

  • Create: docs/requirements/Component-NotificationOutbox.md
  • Reference: docs/plans/notif.md (full design)
  • Reference: docs/requirements/Component-StoreAndForward.md (structure template — closest analogue)

Step 1: Read the source and a template

Read docs/plans/notif.md in full, and docs/requirements/Component-StoreAndForward.md for the standard component-document structure (Purpose, Location, Responsibilities, design sections, Dependencies, Interactions).

Step 2: Write the component document

Create Component-NotificationOutbox.md following the standard structure. Required sections:

  • Purpose — central component that receives store-and-forwarded notifications from sites, logs every one to the Notifications table (single audit source), and delivers them via per-type adapters with retry, parking, status tracking, and KPIs.
  • Location — Central cluster. NotificationOutboxActor is a singleton on the active central node.
  • Responsibilities — owns the durable central queue (Notifications table), the dispatcher loop, retry scheduling, parking, per-notification status tracking, KPI computation. Delivery work runs on a dedicated blocking-I/O dispatcher.
  • End-to-end flow — reproduce the flow diagram from notif.md (script → site S&F → central ingest → outbox dispatch → adapter).
  • The Notifications table — the full field table from notif.md, the status lifecycle (ForwardingPendingRetryingDelivered / Parked / Discarded), retry policy (reuses SMTP config max-retry + fixed interval), and retention (daily purge of terminal rows after ~1-year configurable window).
  • Ingest & idempotency — insert-if-not-exists on NotificationId; ack-after-persist; at-least-once site→central delivery.
  • Dispatcher — polls due rows, resolves the list, delivers via the matching adapter.
  • Delivery adaptersINotificationDeliveryAdapter per Type returning success | transient | permanent; Email adapter implemented now, Teams/others future.
  • Active/standby — singleton on active central node; state in MS SQL; no Akka replication; resumes from the table on failover.
  • Monitoring — KPIs (queue depth, stuck count, parked count, delivered-last-interval, oldest-pending age); stuck = Pending/Retrying older than 10 min (configurable), display-only.
  • ConfigurationNotificationOutboxOptions: dispatch interval, stuck-age threshold, terminal-row retention window.
  • Dependencies — Notification Service (definitions + delivery adapters), Configuration Database (Notifications table), CentralSite Communication (notification submission), Health Monitoring (KPIs), Central UI (Outbox page).
  • Interactions — Site S&F Engine (inbound submissions), Notification Service, Central UI, Health Monitoring.

Match the prose density and heading style of the neighbouring component docs. Edit in place; no backup files (per CLAUDE.md Editing Rules).

Step 3: Verify consistency

Run: grep -n "Notification Outbox\|NotificationOutboxActor\|Notifications table" docs/requirements/Component-NotificationOutbox.md Expected: the new terms appear; the document reads as a peer of the other component docs.

Run: grep -rn "Forwarding" docs/requirements/Component-NotificationOutbox.md Expected: the Forwarding status is documented as site-local, never stored centrally.

Step 4: Commit

git add docs/requirements/Component-NotificationOutbox.md
git commit -m "docs(notification-outbox): add Component-NotificationOutbox design doc"

Task 2: Revise Component-NotificationService.md

Files:

  • Modify: docs/requirements/Component-NotificationService.md
  • Reference: docs/plans/notif.md

Step 1: Read the current document

Read docs/requirements/Component-NotificationService.md in full. It currently describes site-side SMTP delivery.

Step 2: Apply the revisions

  • Purpose / Location — the service is now central-only: manages notification-list and SMTP definitions, and provides the delivery adapters. It no longer delivers from sites.
  • Definitions — notification lists gain a Type field (Email now; Teams/others later) plus type-specific targets. Definitions and SMTP config are no longer deployed to sites (remove the deploy-to-sites / local-SQLite responsibilities).
  • Delivery — replace the site-side delivery section. Delivery is performed centrally by the Notification Outbox calling an INotificationDeliveryAdapter per type. The Email adapter is the relocated SMTP composition/send logic. Recipient resolution happens at central at delivery time.
  • Script APINotify.To("list").Send(subject, body) is now async with a status handle: it returns a NotificationId immediately. Add Notify.Status(notificationId) which returns a status record (status, retry count, last error, key timestamps). Note the site-local Forwarding status.
  • Error classification — there is no synchronous permanent-failure return to the script anymore; permanent failures result in a Parked row. Reframe accordingly.
  • Dependencies / Interactions — depends on Notification Outbox (not the site S&F Engine directly); remove the local-SQLite dependency.

Step 3: Verify consistency

Run: grep -n "site\|SQLite\|deploy" docs/requirements/Component-NotificationService.md Expected: no remaining claims that sites send email or that lists/SMTP config deploy to site SQLite.

Step 4: Commit

git add docs/requirements/Component-NotificationService.md
git commit -m "docs(notification-outbox): central-only Notification Service, typed lists, async API"

Task 3: Revise Component-StoreAndForward.md

Files:

  • Modify: docs/requirements/Component-StoreAndForward.md
  • Reference: docs/plans/notif.md

Step 1: Read the current document

Read docs/requirements/Component-StoreAndForward.md. It lists three categories including notifications, with SMTP as the notification delivery target.

Step 2: Apply the revisions

  • The notification category is retained, but its delivery target changes from SMTP to the central cluster. "Delivering" a buffered notification now means handing it to the Communication Layer for central and clearing it on central's ack.
  • The site→central forward uses a fixed retry interval configured in the host appsettings.json (it concerns reaching central, not any notification list — distinct from the per-entity retry settings used by the other two categories).
  • Update the Message Lifecycle, Retry Policy, Message Format, Dependencies, and Interactions sections so the notification category is described in terms of central delivery, not SMTP.
  • External-system-call and cached-database-write categories are unchanged.

Step 3: Verify consistency

Run: grep -n -i "notif" docs/requirements/Component-StoreAndForward.md Expected: notification references describe central as the target; no SMTP claims remain.

Step 4: Commit

git add docs/requirements/Component-StoreAndForward.md
git commit -m "docs(notification-outbox): retarget S&F notification category to central"

Task 4: Revise Component-HealthMonitoring.md

Files:

  • Modify: docs/requirements/Component-HealthMonitoring.md
  • Reference: docs/plans/notif.md (Monitoring section)

Step 1: Read the current document

Read docs/requirements/Component-HealthMonitoring.md. The Monitored Metrics table includes "Store-and-forward buffer depth" with a notification sub-category.

Step 2: Apply the revisions

  • Add Notification Outbox KPIs as central-computed headline metrics (queue depth, stuck count, parked count) — distinct from the site-reported metrics, since the outbox is central-side and not part of the site health report.
  • Clarify that the S&F buffer-depth notification metric now means "notifications awaiting forward to central" (the site→central leg), still site-reported.
  • Note that outbox KPIs are point-in-time, computed on demand from the Notifications table (no time-series store), consistent with the existing "current status only" philosophy.

Step 3: Verify consistency

Run: grep -n -i "notif\|outbox" docs/requirements/Component-HealthMonitoring.md Expected: outbox KPIs present as central-computed; S&F notification metric reworded to the site→central leg.

Step 4: Commit

git add docs/requirements/Component-HealthMonitoring.md
git commit -m "docs(notification-outbox): add central-computed outbox KPIs to Health Monitoring"

Task 5: Revise Component-SiteEventLogging.md

Files:

  • Modify: docs/requirements/Component-SiteEventLogging.md
  • Reference: docs/plans/notif.md (Refinement decisions — site-side diagnostics)

Step 1: Read the current document

Read docs/requirements/Component-SiteEventLogging.md. The "Events Logged" table has categories for Script, Alarm, Deployment, Connection, S&F, Instance Lifecycle.

Step 2: Apply the revisions

  • Add a Notification category to the Events Logged table. It records forward failures and long-buffered notifications only — not routine enqueue/forward success events (central holds the authoritative record; site logging covers the in-transit blind spot).
  • Add notification to the Event Type enumeration in the Event Entry Schema.
  • Update Dependencies/Interactions to mention the site S&F notification path as a source.

Step 3: Verify consistency

Run: grep -n -i "notif" docs/requirements/Component-SiteEventLogging.md Expected: the Notification category appears and is scoped to forward failures / long-buffered only.

Step 4: Commit

git add docs/requirements/Component-SiteEventLogging.md
git commit -m "docs(notification-outbox): add Notification category to Site Event Logging"

Task 6: Revise Component-Communication.md

Files:

  • Modify: docs/requirements/Component-Communication.md
  • Reference: docs/plans/notif.md (end-to-end flow, ingest & idempotency)

Step 1: Read the current document

Read docs/requirements/Component-Communication.md. It has a numbered "Communication Patterns" section (18, with 6a) and a "Message Timeouts" table.

Step 2: Apply the revisions

  • Add a new communication pattern — Notification Submission (Site → Central) — pattern 9. Pattern: fire-and-forget with acknowledgment (like pattern 5). The site S&F engine sends a NotificationSubmit message to central; central acks after persisting the row to the Notifications table. The NotificationId GUID is the idempotency key.
  • Update the Purpose sentence's list of what the transport carries to include notification submission.
  • Add a row to the Message Timeouts table for the new pattern (if it uses ask-with-ack).

Step 3: Verify consistency

Run: grep -n -i "notification" docs/requirements/Component-Communication.md Expected: the new pattern and the NotificationSubmit message appear.

Step 4: Commit

git add docs/requirements/Component-Communication.md
git commit -m "docs(notification-outbox): add Notification Submission communication pattern"

Task 7: Revise Component-CentralUI.md

Files:

  • Modify: docs/requirements/Component-CentralUI.md
  • Reference: docs/plans/notif.md (Monitoring — Surfacing)

Step 1: Read the current document

Read docs/requirements/Component-CentralUI.md. The "Workflows / Pages" section already has a "Notification List Management" page and a "Parked Message Management" page; the Health Monitoring Dashboard is also listed.

Step 2: Apply the revisions

  • Add a new Notification Outbox page section (Deployment Role) under Workflows / Pages, near Health Monitoring Dashboard / Parked Message Management. Contents: KPI tiles (queue depth, stuck, parked, delivered-last-interval, oldest-pending age); a queryable notification list filterable by status, type, source site, list, and time range, with a stuck-only toggle and keyword search on subject; Retry and Discard actions on parked notifications; badged stuck rows.
  • Note the typed-list change in the Notification List Management page (lists now have a Type).
  • Note the Health Monitoring Dashboard now shows headline outbox KPI tiles.
  • Follow the established UI conventions (Blazor Server + Bootstrap, custom components, clean corporate design — per CLAUDE.md).

Step 3: Verify consistency

Run: grep -n -i "outbox\|notification" docs/requirements/Component-CentralUI.md Expected: the Notification Outbox page is present and consistent with the design.

Step 4: Commit

git add docs/requirements/Component-CentralUI.md
git commit -m "docs(notification-outbox): add Notification Outbox page to Central UI"

Task 8: Revise Component-ConfigurationDatabase.md and Component-Commons.md

Files:

  • Modify: docs/requirements/Component-ConfigurationDatabase.md
  • Modify: docs/requirements/Component-Commons.md
  • Reference: docs/plans/notif.md (Notifications table)

Step 1: Read the current documents

Read both documents. Configuration Database describes EF Core repositories, migrations, audit logging. Commons describes POCO entities, repository interfaces, and message contracts.

Step 2: Apply the revisions

In Component-ConfigurationDatabase.md:

  • Add the Notifications table to the data access layer — a new EF Core entity, repository, and migration. Note the type-agnostic schema (Type discriminator + TypeData JSON), the status enum, and the daily purge job for terminal rows after the configurable retention window.

In Component-Commons.md:

  • Add the Notification entity POCO (persistence-ignorant, in Entities/) and its repository interface (in Interfaces/).
  • Add the NotificationSubmit message contract (and its ack) under Messages/, following the additive-only versioning rule.
  • Add the typed notification-list fields if notification-list entities live in Commons.

Step 3: Verify consistency

Run: grep -n -i "notification" docs/requirements/Component-ConfigurationDatabase.md docs/requirements/Component-Commons.md Expected: the Notifications table, Notification entity, repository interface, and message contracts are documented in their respective layers.

Step 4: Commit

git add docs/requirements/Component-ConfigurationDatabase.md docs/requirements/Component-Commons.md
git commit -m "docs(notification-outbox): add Notifications table, entity, and message contracts"

Task 9: Update README.md

Files:

  • Modify: README.md

Step 1: Read the current document

Read README.md. It has a Technology Stack table, a Component Design Documents table (20 rows), and architecture diagrams.

Step 2: Apply the revisions

  • Add row 21 — Notification Outbox to the Component Design Documents table, linking docs/requirements/Component-NotificationOutbox.md, with a one-line description.
  • Update the Notification Service row (#8) description if it no longer reflects central-only delivery / typed lists.
  • Update the Technology Stack "Notifications" row to reflect central delivery.
  • If any architecture diagram shows the notification path, update it; otherwise leave diagrams unchanged.

Step 3: Verify consistency

Run: grep -n -c "Component-" README.md and confirm the component table has 21 component rows. Run: grep -n "Notification Outbox" README.md Expected: the new component is row 21 and linked correctly.

Step 4: Commit

git add README.md
git commit -m "docs(notification-outbox): add Notification Outbox to README component index"

Task 10: Update CLAUDE.md

Files:

  • Modify: CLAUDE.md

Step 1: Read the current document

Read CLAUDE.md. It has "Current Component List (20 components)" and a "Key Design Decisions" section.

Step 2: Apply the revisions

  • Change the heading to "Current Component List (21 components)" and add 21. Notification Outbox with a one-line description.
  • Update the Notification Service entry (#8) to reflect central-only definitions + delivery adapters.
  • Under "Key Design Decisions → External Integrations" (or a suitable subsection), add entries capturing: notifications store-and-forwarded site→central; central Notifications table as single audit source; type-agnostic table (email now, Teams later); async Notify.Send returning a status handle; central Notification Outbox singleton; site-local Forwarding status.

Step 3: Verify consistency

Run: grep -n "21\|Notification Outbox" CLAUDE.md Expected: component count is 21 and the new component + design decisions are listed.

Step 4: Commit

git add CLAUDE.md
git commit -m "docs(notification-outbox): update component list and design decisions in CLAUDE.md"

Task 11: Cross-reference consistency sweep

Files:

  • Verify: all of docs/requirements/, README.md, CLAUDE.md

Step 1: Check for stale notification-delivery claims

Run: grep -rn -i "site.*send.*email\|email.*via SMTP\|SMTP.*at sites\|notification list.*deploy" docs/ README.md CLAUDE.md Expected: no results claiming sites send email directly or that notification lists deploy to sites.

Step 2: Check cross-references resolve

Run: grep -rn "Notification Outbox\|NotificationOutbox" docs/requirements/ README.md CLAUDE.md Expected: every reference points to a real component / document; no dangling references.

Step 3: Check component count agreement

Confirm README.md component table and CLAUDE.md component list both say 21 components and list Notification Outbox identically.

Step 4: Review the full diff

Run: git diff main --stat (or review the branch's commits) Read through the changes and confirm the documentation set is internally consistent with docs/plans/notif.md.

Step 5: Commit any fixes

git add -A
git commit -m "docs(notification-outbox): fix stale cross-references after outbox addition"

(Skip the commit if the sweep found nothing to fix.)


Done

All component documents, the README index, and CLAUDE.md reflect the central Notification Outbox design. The documentation set is internally consistent with docs/plans/notif.md.