feat(audit)!: ScadaBridge C3 — swap to canonical ZB.MOM.WW.Audit.AuditEvent across seams/emitters/DTO/redactor wiring; transitional 24-col storage shim (Task 2.5)

This commit is contained in:
Joseph Doherty
2026-06-02 12:37:50 -04:00
parent 5aaf9e2923
commit db707bb0de
127 changed files with 2240 additions and 3886 deletions
@@ -1,7 +1,8 @@
using Microsoft.Extensions.Logging;
using ZB.MOM.WW.ScadaBridge.AuditLog.Payload;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
using ZB.MOM.WW.Audit;
using ZB.MOM.WW.ScadaBridge.AuditLog.Redaction;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
using IAuditWriter = ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services.IAuditWriter;
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Site;
@@ -31,7 +32,7 @@ public sealed class FallbackAuditWriter : IAuditWriter
private readonly RingBufferFallback _ring;
private readonly IAuditWriteFailureCounter _failureCounter;
private readonly ILogger<FallbackAuditWriter> _logger;
private readonly IAuditPayloadFilter _filter;
private readonly IAuditRedactor _redactor;
private readonly SemaphoreSlim _drainGate = new(1, 1);
/// <summary>
@@ -48,26 +49,28 @@ public sealed class FallbackAuditWriter : IAuditWriter
/// <param name="ring">Drop-oldest ring buffer used to stash events when the primary fails.</param>
/// <param name="failureCounter">Counter incremented on each primary failure for health reporting.</param>
/// <param name="logger">Logger for diagnostics.</param>
/// <param name="filter">Optional payload filter applied before writing; null means no filtering.</param>
/// <param name="redactor">Optional canonical redactor applied before writing; null means the always-safe default.</param>
public FallbackAuditWriter(
IAuditWriter primary,
RingBufferFallback ring,
IAuditWriteFailureCounter failureCounter,
ILogger<FallbackAuditWriter> logger,
IAuditPayloadFilter? filter = null)
IAuditRedactor? redactor = null)
{
_primary = primary ?? throw new ArgumentNullException(nameof(primary));
_ring = ring ?? throw new ArgumentNullException(nameof(ring));
_failureCounter = failureCounter ?? throw new ArgumentNullException(nameof(failureCounter));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
// AuditLog-008: never default to a null filter — over-redact instead.
// SafeDefaultAuditPayloadFilter.Instance performs HTTP header
// AuditLog-008: never default to a null redactor — over-redact instead.
// C3 (Task 2.5): the canonical IAuditRedactor replaces the legacy
// IAuditPayloadFilter. SafeDefaultAuditRedactor performs HTTP header
// redaction with the hard-coded sensitive defaults (Authorization,
// X-Api-Key, Cookie, Set-Cookie) so a test composition root that
// doesn't bind the real options never persists those headers
// verbatim. The real DefaultAuditPayloadFilter (truncation + body /
// SQL-param redaction) is wired by AddAuditLog and takes precedence.
_filter = filter ?? Payload.SafeDefaultAuditPayloadFilter.Instance;
// X-Api-Key, Cookie, Set-Cookie) on the DetailsJson summaries so a test
// composition root that doesn't bind the real options never persists
// those headers verbatim. The full ScadaBridgeAuditRedactor (truncation
// + body / SQL-param redaction) is wired by AddAuditLog and takes
// precedence.
_redactor = redactor ?? SafeDefaultAuditRedactor.Instance;
}
/// <inheritdoc />
@@ -75,14 +78,14 @@ public sealed class FallbackAuditWriter : IAuditWriter
{
ArgumentNullException.ThrowIfNull(evt);
// Filter once, up-front. The filtered event flows BOTH to the primary
// Redact once, up-front. The redacted event flows BOTH to the primary
// and (on failure) to the ring buffer — so a primary outage that
// drains later still hands the SqliteAuditWriter a row that has
// already been truncated and redacted. The filter contract is
// "MUST NOT throw". AuditLog-008: _filter is now non-null (defaults
// to SafeDefaultAuditPayloadFilter so header redaction is always
// applied even in composition roots that don't wire the real filter).
var filtered = _filter.Apply(evt);
// already been truncated and redacted. The redactor contract is
// "MUST NOT throw". AuditLog-008: _redactor is now non-null (defaults
// to SafeDefaultAuditRedactor so header redaction is always applied
// even in composition roots that don't wire the real redactor).
var filtered = _redactor.Apply(evt);
try
{