docs(m7): reflect OPC UA / MxGateway UX (T13-T17) across component docs + CLAUDE/stillpending/completion-design

This commit is contained in:
Joseph Doherty
2026-06-18 04:13:21 -04:00
parent 39afa2743e
commit 40928535fd
11 changed files with 158 additions and 19 deletions
+34 -6
View File
@@ -69,6 +69,13 @@ Script-initiated DB **reads** via `Database.Connection().ExecuteReader(...)`
count as actions from a script and are in scope. Reads via DCL / subscriptions
are framework traffic and excluded.
**Extension beyond the script boundary — secured writes.** The `SecuredWrite`
channel is a deliberate widening of the original script-trust-boundary scope: a
two-person MxGateway write is **operator-initiated from the Central UI**, not
script-caused, but it crosses the same equipment-write trust boundary and warrants
the same append-only "who approved" trail. Its rows are emitted via the central
direct-write path (see Central direct-write below), not the site hot-path.
## The `AuditLog` Table (central)
Single wide table in central MS SQL, polymorphic by `Channel` + `Kind`
@@ -80,7 +87,7 @@ row per lifecycle event across all channels.
| `EventId` | `uniqueidentifier` PK | Generated where the event originates (site or central). Idempotency key. |
| `OccurredAtUtc` | `datetime2` | When the event happened (call returned, retry attempted, etc.). |
| `IngestedAtUtc` | `datetime2` | When central persisted the row (lags `OccurredAtUtc` for site-originated rows). |
| `Channel` | `varchar(32)` | `ApiOutbound` \| `DbOutbound` \| `Notification` \| `ApiInbound`. |
| `Channel` | `varchar(32)` | `ApiOutbound` \| `DbOutbound` \| `Notification` \| `ApiInbound` \| `SecuredWrite`. |
| `Kind` | `varchar(32)` | Event kind discriminator (see kinds list below). |
| `CorrelationId` | `uniqueidentifier` NULL | Ties multi-event operations together. `TrackedOperationId` for cached calls, `NotificationId` for notifications, request-id for inbound API. NULL for sync one-shot calls. |
| `ExecutionId` | `uniqueidentifier` NULL | The originating script execution / inbound request — the universal per-run correlation value; distinct from `CorrelationId`, which is the per-operation lifecycle id. Stamped on *every* audit row emitted by one execution. |
@@ -113,7 +120,7 @@ row per lifecycle event across all channels.
- `IX_AuditLog_Target_Occurred (Target, OccurredAtUtc)` — "what did we send to system X".
- Monthly partitioning on `OccurredAtUtc` from day one; purge is a partition switch (see Retention & Purge).
**`Kind` values (flat — 10 discriminators across all channels):**
**`Kind` values (flat — 14 discriminators across all channels):**
| Kind | Fires when |
|---|---|
@@ -127,11 +134,28 @@ row per lifecycle event across all channels.
| `InboundAuthFailure` | An inbound API request was rejected at the auth boundary (bad/missing key). One row, `Status=Failed`, `HttpStatus=401`. |
| `CachedSubmit` | Script-side enqueue of a cached call (`ExternalSystem.CachedCall` / `Database.CachedWrite`); first row in the cached-call lifecycle, written to site SQLite before any forward attempt. |
| `CachedResolve` | Terminal row for a cached operation — `Status` = `Delivered` / `Failed` / `Parked` / `Discarded`. |
| `SecuredWriteSubmit` | An operator submits a two-person MxGateway secured write (`Status=Submitted`); first row in the secured-write lifecycle. |
| `SecuredWriteApprove` | A verifier wins the approval CAS for a pending secured write. |
| `SecuredWriteReject` | A verifier rejects a pending secured write (`Status=Discarded`). |
| `SecuredWriteExecute` | The approved write was relayed to the site MxGateway — terminal outcome (`Delivered`-equivalent on success, `Failed` on error). |
Inbound API is intentionally collapsed to a single `InboundRequest` (or
`InboundAuthFailure` for auth rejections) row per request rather than a
multi-event lifecycle.
**Secured writes (`Channel = SecuredWrite`).** The four `SecuredWrite*` kinds
emit one row per lifecycle event of a two-person MxGateway write
(submit → approve → execute, or submit → reject). All rows of one operation share
the `PendingSecuredWrite.Id` (encoded as a `Guid`) in `CorrelationId` so they join,
and carry both `operatorUser` and `verifierUser` in `Extra` so a single row names
both parties. Rows are written via the central direct-write path (like Notification
Outbox dispatch and Inbound API), and emission is **best-effort** — an audit-write
failure never aborts the secured write itself.
**Known gap (follow-up):** these central direct-write rows currently leave
`SourceNode` **NULL** rather than stamping the writing central node's role name
(`central-a` / `central-b`) as the other central direct-write paths do — stamping
`SourceNode` for secured-write audit rows is a logged follow-up.
### `ExecutionId` vs `CorrelationId`
The table carries two correlation columns at different granularities:
@@ -252,10 +276,14 @@ rejections emit `ApiInbound.InboundAuthFailure` (`Status=Failed`, HTTP 401)
instead. The Notification Outbox dispatcher writes
`Notification.NotifyDeliver` with `Status=Attempted` per delivery attempt and
`Notification.NotifyDeliver` with `Status=Delivered`/`Parked`/`Discarded` on
terminal status. Central direct-writes use the same insert-if-not-exists
semantics keyed on `EventId`. `SourceSiteId` is NULL on all central direct-write
rows; `SourceNode` is stamped to the local central node's role name
(`central-a` / `central-b`).
terminal status. The ManagementActor writes the four `SecuredWrite.*` rows for the
two-person MxGateway write workflow (submit / approve / reject / execute) the same
way — central direct-write, best-effort, insert-if-not-exists. Central direct-writes
use the same insert-if-not-exists semantics keyed on `EventId`. `SourceSiteId` is
NULL on all central direct-write rows; `SourceNode` is stamped to the local central
node's role name (`central-a` / `central-b`) — **except** the `SecuredWrite.*` rows,
which currently leave `SourceNode` NULL (stamping is a logged follow-up; the
secured-write `SourceSiteId`, by contrast, *is* set to the target site).
## Cached Operations — Combined Telemetry