5.7 KiB
Audit (who-did-what)
Status: Draft. Normalized component — path to shared code. Goal: converge the three
sister projects onto a canonical AuditEvent record + AuditOutcome enum + two thin seams
(IAuditWriter, IAuditRedactor), proposed as the ZB.MOM.WW.Audit library, while each
project keeps its own transport, storage, domain vocabulary, and redaction policy.
- The one target:
spec/SPEC.md - Canonical event model + field reference:
spec/EVENT-MODEL.md - The proposed shared library:
shared-contract/ZB.MOM.WW.Audit.md - Divergences + backlog:
GAPS.md - Current state, per project:
current-state/
Why audit is a strong normalization candidate
All three projects record a structured who-did-what trail with an actor identity, an action
verb, and a timestamp. Two (OtOpcUa + ScadaBridge) already have a named AuditEvent record
with an EventId idempotency key, Actor, and CorrelationId. ScadaBridge already ships
both canonical seams under slightly different names (IAuditWriter is byte-for-byte the
spec; IAuditPayloadFilter is the canonical IAuditRedactor). OtOpcUa's record is almost
field-for-field aligned. MxGateway has a narrow API-key-lifecycle log that maps cleanly.
The one new field across all three is AuditOutcome — no project stores it explicitly today;
each encodes it implicitly and derives it at adoption. This is the bulk of the per-project
work. Transport, storage, domain vocabulary, and redaction policy are not unified — each
project keeps its own bespoke implementation behind the seam.
Audit closes the loop on Auth. Every audit row's Actor is exactly the identity that the
ZB.MOM.WW.Auth component normalizes (LDAP/GLAuth principal, API-key name). The library keeps
Actor as a plain string (no Auth dependency), but at adoption each emit site supplies the
Auth principal.
IAuditRedactor naming is aligned with Telemetry's ILogRedactor — same shape and naming
discipline so a future ZB.MOM.WW.Hosting aggregator wires both redactors with one mental
model — but there is no cross-package dependency between the two libraries.
Status by project
| Project | Audit today | Seams present | AuditOutcome |
Adoption status |
|---|---|---|---|---|
| OtOpcUa | Akka cluster-broadcast AuditEvent → cluster-singleton AuditWriterActor (batch 500/5 s, two-layer dedup) over EF ConfigAuditLog (SQL Server). Also a legacy SQL stored-procedure write path (bare EventType, NULL EventId). Admin UI page ClusterAudit.razor. |
No named IAuditWriter seam; no redactor seam. |
Not stored — encoded in EventType strings (OpcUaAccessDenied/CrossClusterNamespaceAttempt → Denied; config-write verbs → Success). |
Not started |
| MxAccessGateway | Single SQLite-backed IApiKeyAuditStore / ApiKeyAuditEntry — key lifecycle (CLI + dashboard) + constraint denials only. No authn events persisted; no production read consumer. |
Narrow custom seam (IApiKeyAuditStore); no general IAuditWriter; redaction is by-construction (secret never enters the record type). |
Not stored — derived: constraint-denied → Denied; all others → Success. |
Not started |
| ScadaBridge | Full pipeline: site SQLite hot-path (SqliteAuditWriter + ring-buffer fallback) → Akka ClusterClient forwarder → central MS SQL (ingest / reconcile / purge / partition maintenance). Rich ~25-field AuditEvent record. CLI export/verify-chain; Blazor audit UI. |
✅ IAuditWriter (matches canonical contract word-for-word); ✅ IAuditPayloadFilter (= canonical IAuditRedactor, identical signature, pure/never-throws/over-redacts). |
Not stored explicitly — derived from Status (Delivered→Success; Failed/Parked/Discarded→Failure; Kind = InboundAuthFailure→Denied). |
Not started (align, don't replace) |
See each project's current-state/<project>/CURRENT-STATE.md for code-verified detail and
adoption plan:
current-state/otopcua/CURRENT-STATE.mdcurrent-state/mxaccessgw/CURRENT-STATE.mdcurrent-state/scadabridge/CURRENT-STATE.md
Normalized vs. left per-project
Normalized (the shared ZB.MOM.WW.Audit library): the canonical AuditEvent record
(5 required fields + 4 optional common + DetailsJson extension bag); the AuditOutcome
enum (Success | Failure | Denied); the IAuditWriter seam (best-effort, never throws to
caller); the IAuditRedactor seam (pure, never throws, over-redacts on failure); shipped
helpers (NoOpAuditWriter, CompositeAuditWriter, RedactingAuditWriter,
NullAuditRedactor, TruncatingAuditRedactor). Library has no Akka / EF / SQLite / Serilog
dependency; its only non-BCL dependency is Microsoft.Extensions.DependencyInjection.Abstractions.
Left per-project (each project keeps these behind the seam): transport and storage (Akka
singleton + EF/SQL Server; SQLite; site-SQLite + central MS SQL + forwarding/reconcile
pipeline); domain vocabulary (EventType strings / API-key event-type literals / Channel +
Kind + Status enums); query, CLI, and UI surfaces (ClusterAudit.razor; ListRecentAsync;
export / verify-chain; Blazor audit pages); redaction policy (which fields/payloads are
sensitive — only the IAuditRedactor seam is shared).
Adoption is deferred this round. The
ZB.MOM.WW.Auditlibrary is being designed and the shared contract defined, but none of the three apps wire it in yet — exactly whereZB.MOM.WW.AuthandZB.MOM.WW.Themesit today. The per-project adoption backlog is inGAPS.md.