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.
This commit is contained in:
Joseph Doherty
2026-05-18 23:01:30 -04:00
parent bbfa0c515e
commit f177b58699
2 changed files with 397 additions and 0 deletions

View File

@@ -0,0 +1,380 @@
# 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 (`Forwarding``Pending``Retrying``Delivered` / `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 adapters** — `INotificationDeliveryAdapter` 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.
- **Configuration** — `NotificationOutboxOptions`: 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**
```bash
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 API** — `Notify.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**
```bash
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**
```bash
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**
```bash
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**
```bash
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**
```bash
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**
```bash
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**
```bash
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**
```bash
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**
```bash
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**
```bash
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`.

View File

@@ -0,0 +1,17 @@
{
"planPath": "docs/plans/2026-05-18-notification-outbox.md",
"tasks": [
{"id": 7, "subject": "Task 1: Create Component-NotificationOutbox.md", "status": "pending"},
{"id": 8, "subject": "Task 2: Revise Component-NotificationService.md", "status": "pending", "blockedBy": [7]},
{"id": 9, "subject": "Task 3: Revise Component-StoreAndForward.md", "status": "pending", "blockedBy": [7]},
{"id": 10, "subject": "Task 4: Revise Component-HealthMonitoring.md", "status": "pending", "blockedBy": [7]},
{"id": 11, "subject": "Task 5: Revise Component-SiteEventLogging.md", "status": "pending", "blockedBy": [7]},
{"id": 12, "subject": "Task 6: Revise Component-Communication.md", "status": "pending", "blockedBy": [7]},
{"id": 13, "subject": "Task 7: Revise Component-CentralUI.md", "status": "pending", "blockedBy": [7]},
{"id": 14, "subject": "Task 8: Revise Component-ConfigurationDatabase.md and Component-Commons.md", "status": "pending", "blockedBy": [7]},
{"id": 15, "subject": "Task 9: Update README.md", "status": "pending", "blockedBy": [7, 8, 9, 10, 11, 12, 13, 14]},
{"id": 16, "subject": "Task 10: Update CLAUDE.md", "status": "pending", "blockedBy": [7, 8, 9, 10, 11, 12, 13, 14]},
{"id": 17, "subject": "Task 11: Cross-reference consistency sweep", "status": "pending", "blockedBy": [7, 8, 9, 10, 11, 12, 13, 14, 15, 16]}
],
"lastUpdated": "2026-05-18"
}