diff --git a/docs/requirements/Component-Commons.md b/docs/requirements/Component-Commons.md index 5c5a5dc..c341755 100644 --- a/docs/requirements/Component-Commons.md +++ b/docs/requirements/Component-Commons.md @@ -118,7 +118,7 @@ Commons must define the shared DTOs and message contracts used for inter-compone - **Debug View DTOs**: Subscribe/unsubscribe requests, one-shot snapshot request (`DebugSnapshotRequest`), initial snapshot, stream filter criteria. - **Script Execution DTOs**: Script call requests (with recursion depth), return values, error results. - **System-Wide Artifact DTOs**: Shared script packages, external system definitions, database connection definitions, notification list definitions. -- **Notification DTOs**: `NotificationSubmit` — the site→central notification submission carrying `NotificationId` (GUID), `ListName`, `Subject`, `Body`, provenance (`SourceSiteId`, `SourceInstanceId`, `SourceScript`), and `SiteEnqueuedAt` — and `NotificationSubmitAck`, the central acknowledgement returned after the `Notifications` row is persisted (ack-after-persist), which the site Store-and-Forward Engine waits on before clearing the buffered message. Recipient resolution is *not* part of the contract — the site forwards only `(listName, subject, body)` and central resolves the list at delivery time. `Notify.Status` also round-trips a small status-query request and a status-record response (status, retry count, last error, key timestamps) once a notification has been forwarded. These contracts are `record` types subject to the additive-only evolution rules in REQ-COM-5a — relevant because a notification submission can cross the site→central version skew boundary; future per-type fields are carried by adding optional fields (or via the `Notifications` table's `TypeData` JSON hook) rather than by changing existing ones. +- **Notification DTOs**: `NotificationSubmit` (site→central submission: `NotificationId`, `ListName`, `Subject`, `Body`, provenance, `SiteEnqueuedAt`) and `NotificationSubmitAck` (central acknowledgement returned only after the `Notifications` row is persisted — ack-after-persist — which the site Store-and-Forward Engine waits on before clearing the buffered message). `NotificationStatusQuery` / `NotificationStatusResponse` back the `Notify.Status` script API, round-tripping a status record (status, retry count, last error, key timestamps) once a notification has been forwarded. Recipient resolution is *not* part of any contract — the site forwards only `(listName, subject, body)` and central resolves the list at delivery time. Subject to the additive-only evolution rules in REQ-COM-5a, since a submission can cross the site→central version-skew boundary. All message types must be `record` types or immutable classes suitable for use as Akka.NET messages (though Commons itself must not depend on Akka.NET). @@ -200,7 +200,8 @@ ScadaLink.Commons/ │ ├── DataConnection/ # data-connection subscribe/write/health messages │ ├── Instance/ # attribute get/set request/command messages │ ├── Integration/ # external-integration call request/response -│ ├── Notification/ # NotificationSubmit + ack, Notify.Status query/response +│ ├── Notification/ # NotificationSubmit + ack, +│ │ # NotificationStatusQuery/Response │ ├── InboundApi/ # Route.To() request messages │ ├── RemoteQuery/ # event-log and parked-message query messages │ └── Management/ # HTTP/ClusterClient management commands + registry diff --git a/docs/requirements/Component-ConfigurationDatabase.md b/docs/requirements/Component-ConfigurationDatabase.md index e9cf328..00ce59d 100644 --- a/docs/requirements/Component-ConfigurationDatabase.md +++ b/docs/requirements/Component-ConfigurationDatabase.md @@ -55,7 +55,7 @@ The configuration database stores all central system data, organized by domain a - **Notification Lists**: List definitions (name, `Type` discriminator — `Email` / `Teams` / … — selecting the list's notification type and type-specific targets). - **Notification Recipients**: Recipients per list (name, email address). - **SMTP Configuration**: Email server settings. -- **Notifications**: The durable central notification queue owned by the Notification Outbox — one row per notification, the single source of audit truth. The schema is **type-agnostic** so it records any notification type the system supports (email today, Microsoft Teams and others later): a `Type` discriminator selects the type, and a `TypeData` JSON column (`nvarchar(max)`) carries any future per-type fields without a schema change. Columns: `NotificationId` (GUID, primary key — generated at the site, used as the idempotency key), `Type`, `ListName`, `Subject`, `Body`, `TypeData`, `Status`, `RetryCount`, `LastError`, `ResolvedTargets`, `SourceSiteId`, `SourceInstanceId`, `SourceScript`, `SiteEnqueuedAt`, `CreatedAt`, `LastAttemptAt`, `NextAttemptAt`, `DeliveredAt`. `Status` is a `NotificationStatus` enum stored with values `Pending`, `Retrying`, `Delivered`, `Parked`, `Discarded` (the site-local `Forwarding` state is never persisted centrally). Indexed on `Status` and `NextAttemptAt` for efficient dispatcher polling of due rows, and on `SourceSiteId` and `CreatedAt` for KPI computation and the Central UI query page. Terminal rows (`Delivered`, `Parked`, `Discarded`) are removed by a daily purge job after a configurable retention window (default 365 days); non-terminal rows are never purged. See Component-NotificationOutbox.md for the full lifecycle. +- **Notifications**: The durable central notification queue owned by the Notification Outbox — one row per notification, the single source of audit truth. The schema is **type-agnostic** so it records any notification type the system supports (email today, Microsoft Teams and others later): a `Type` discriminator selects the type, and a `TypeData` JSON column (`nvarchar(max)`) carries any future per-type fields without a schema change. Columns: `NotificationId` (GUID, primary key — generated at the site, used as the idempotency key), `Type`, `ListName`, `Subject`, `Body`, `TypeData`, `Status`, `RetryCount`, `LastError`, `ResolvedTargets`, `SourceSiteId`, `SourceInstanceId`, `SourceScript`, `SiteEnqueuedAt`, `CreatedAt`, `LastAttemptAt`, `NextAttemptAt`, `DeliveredAt`. `Status` is a `NotificationStatus` enum stored with values `Pending`, `Retrying`, `Delivered`, `Parked`, `Discarded` (the site-local `Forwarding` state is never persisted centrally). Indexed on `Status` and `NextAttemptAt` for efficient dispatcher polling of due rows, and on `SourceSiteId` and `CreatedAt` for KPI computation and the Central UI query page. Terminal rows are removed by a daily purge job — see Scheduled Maintenance below. See Component-NotificationOutbox.md for the full lifecycle. ### Inbound API - **API Keys**: Key definitions (name/label, key value, enabled flag). @@ -272,7 +272,7 @@ The Configuration Database supports seeding initial data required for the system ### Notifications Table Purge -The `Notifications` table grows one row per notification and is never trimmed by normal operation — `Discarded` rows are deliberately retained for audit. To bound table growth while preserving a strong audit trail, a **daily purge job** deletes terminal rows (`Delivered`, `Parked`, `Discarded`) whose `CreatedAt` is older than a configurable retention window (default 365 days). Non-terminal rows (`Pending`, `Retrying`) are never purged. The purge is a bulk `DELETE` against `INotificationOutboxRepository`; it is owned and scheduled by the Notification Outbox component (see Component-NotificationOutbox.md), which supplies the retention window from `NotificationOutboxOptions`. The Configuration Database component provides only the repository operation and the table. +The `Notifications` table grows one row per notification and is never trimmed by normal operation — `Discarded` rows are deliberately retained for audit. To bound table growth while preserving a strong audit trail, a **daily purge job** deletes terminal rows (`Delivered`, `Parked`, `Discarded`) older than a configurable retention window (default 365 days). Non-terminal rows (`Pending`, `Retrying`) are never purged. The purge is a bulk `DELETE` against `INotificationOutboxRepository`; it is owned and scheduled by the Notification Outbox component (see Component-NotificationOutbox.md), which supplies the retention window from `NotificationOutboxOptions`. The Configuration Database component provides only the repository operation and the table. ---