Persist the canonical AuditOutcome and make structured audit rows visible.
- ConfigAuditLog gains a nullable Outcome column, stored as the AuditOutcome
enum member name (nvarchar(16), mirroring how AdminRole is persisted). The
AuditWriterActor flush now writes Outcome = evt.Outcome.ToString(). Nullable so
legacy rows and the bespoke stored-procedure path (no derived outcome) write
NULL.
- Migration 20260602135350_AddConfigAuditLogOutcome: additive nullable column,
no backfill. Up adds the column, Down drops it. Chains after
20260602112419_CanonicalizeAdminRoles; `dotnet ef migrations
has-pending-model-changes` is clean.
- ClusterAudit visibility fix: the page filtered solely on ClusterId, but the
structured AuditWriterActor path stamps NodeId (ClusterId null), so those rows
were invisible. Extracted ClusterAuditQuery.ForClusterAsync (shared by the page
and tests) which ORs in rows whose NodeId belongs to a node in the cluster —
membership resolved from ClusterNode (NodeId -> ClusterId). SP-path
ClusterId-stamped rows still match.
Tests: ControlPlane 45/45 (adds Outcome persistence + Denied-outcome asserts);
new Configuration ClusterAuditQueryTests 3/3 (both-paths visible, other-cluster
excluded, page-size cap); AdminUI 121/121. Configuration Unit suite is green on a
clean run (a pre-existing timing flake in ResilientConfigReaderTests, untouched
here, occasionally fails under parallel load and passes in isolation).
- ClusterAudit (/clusters/{id}/audit) — reads ConfigAuditLog with the
EventId/CorrelationId columns added in F3; shown as a Cluster tab
- VirtualTags (/virtual-tags) — fleet-wide read view
- ScriptedAlarms (/scripted-alarms) — fleet-wide read view
- Scripts (/scripts) — fleet-wide; expandable code preview
- RoleGrants (/role-grants) — per Q4, surfaces the fleet-wide
LDAP-group → role mapping from
Authentication:Ldap:GroupToRole
(read-only; reload via host restart)
- Certificates (/certificates) — own/trusted/issuer/rejected store
contents resolved against
OpcUa:PkiStoreRoot config (F13a)
- Reservations (/reservations) — ExternalIdReservation table
- AlarmsHistorian (/alarms-historian) — live HistorianAdapterActor sink
status via the F11 GetStatus query;
5s polling
ScriptLog deferred (needs the F16-deferred ScriptLogHub bridge).
ClusterNav extended with the Audit tab.
Adds an AdminUI → Runtime project reference so the historian status page can
inject IRequiredActor<HistorianAdapterActorKey>. NuGet audit suppression for
the transitive Opc.Ua.Core advisory mirrored from the Runtime project.
All 104 v2 tests still green.