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:
@@ -1,9 +1,10 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
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.Repositories;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Central;
|
||||
|
||||
@@ -41,7 +42,7 @@ public sealed class CentralAuditWriter : ICentralAuditWriter
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger<CentralAuditWriter> _logger;
|
||||
private readonly IAuditPayloadFilter _filter;
|
||||
private readonly IAuditRedactor _redactor;
|
||||
private readonly ICentralAuditWriteFailureCounter _failureCounter;
|
||||
private readonly INodeIdentityProvider? _nodeIdentity;
|
||||
|
||||
@@ -68,24 +69,25 @@ public sealed class CentralAuditWriter : ICentralAuditWriter
|
||||
/// </summary>
|
||||
/// <param name="services">Service provider used to open a per-call scope for the scoped repository.</param>
|
||||
/// <param name="logger">Logger for swallowed write-failure diagnostics.</param>
|
||||
/// <param name="filter">Optional payload filter for truncation and redaction; defaults to a pass-through.</param>
|
||||
/// <param name="redactor">Optional canonical redactor for truncation and redaction; defaults to the always-safe default.</param>
|
||||
/// <param name="failureCounter">Optional counter incremented on swallowed repository failures; defaults to a no-op.</param>
|
||||
/// <param name="nodeIdentity">Optional node identity provider for stamping <c>SourceNode</c> on central-origin rows.</param>
|
||||
public CentralAuditWriter(
|
||||
IServiceProvider services,
|
||||
ILogger<CentralAuditWriter> logger,
|
||||
IAuditPayloadFilter? filter = null,
|
||||
IAuditRedactor? redactor = null,
|
||||
ICentralAuditWriteFailureCounter? failureCounter = null,
|
||||
INodeIdentityProvider? nodeIdentity = null)
|
||||
{
|
||||
_services = services ?? throw new ArgumentNullException(nameof(services));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
// AuditLog-008: never default to null — over-redact instead.
|
||||
// SafeDefaultAuditPayloadFilter applies HTTP header redaction with
|
||||
// hard-coded sensitive defaults so a composition root that omits the
|
||||
// real filter still scrubs Authorization / X-Api-Key / Cookie /
|
||||
// Set-Cookie before persistence.
|
||||
_filter = filter ?? Payload.SafeDefaultAuditPayloadFilter.Instance;
|
||||
// C3 (Task 2.5): the canonical IAuditRedactor replaces the legacy
|
||||
// IAuditPayloadFilter. SafeDefaultAuditRedactor applies HTTP header
|
||||
// redaction with hard-coded sensitive defaults so a composition root
|
||||
// that omits the real redactor still scrubs Authorization / X-Api-Key /
|
||||
// Cookie / Set-Cookie before persistence.
|
||||
_redactor = redactor ?? SafeDefaultAuditRedactor.Instance;
|
||||
_failureCounter = failureCounter ?? new NoOpCentralAuditWriteFailureCounter();
|
||||
_nodeIdentity = nodeIdentity;
|
||||
}
|
||||
@@ -103,12 +105,12 @@ public sealed class CentralAuditWriter : ICentralAuditWriter
|
||||
|
||||
try
|
||||
{
|
||||
// Filter BEFORE stamping IngestedAtUtc + handing to the repo. The
|
||||
// filter contract is "never throws". AuditLog-008: _filter is now
|
||||
// non-null (SafeDefaultAuditPayloadFilter fallback) so header
|
||||
// Redact BEFORE stamping IngestedAtUtc + handing to the repo. The
|
||||
// redactor contract is "never throws". AuditLog-008: _redactor is
|
||||
// now non-null (SafeDefaultAuditRedactor fallback) so header
|
||||
// redaction always runs even in composition roots that omit the
|
||||
// real filter.
|
||||
var filtered = _filter.Apply(evt);
|
||||
// real redactor.
|
||||
var filtered = _redactor.Apply(evt);
|
||||
|
||||
// SourceNode-stamping (Task 12): caller-provided value wins
|
||||
// (supports any future direct-write callsite that already has its
|
||||
@@ -124,7 +126,9 @@ public sealed class CentralAuditWriter : ICentralAuditWriter
|
||||
|
||||
await using var scope = _services.CreateAsyncScope();
|
||||
var repo = scope.ServiceProvider.GetRequiredService<IAuditLogRepository>();
|
||||
var stamped = filtered with { IngestedAtUtc = DateTime.UtcNow };
|
||||
// C3 transitional shim: IngestedAtUtc is a DetailsJson field on the
|
||||
// canonical record, so stamp it via the projection helper.
|
||||
var stamped = AuditRowProjection.WithIngestedAtUtc(filtered, DateTime.UtcNow);
|
||||
await repo.InsertIfNotExistsAsync(stamped, ct).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -143,17 +147,17 @@ public sealed class CentralAuditWriter : ICentralAuditWriter
|
||||
// misbehaving custom counter does, swallowing here keeps the
|
||||
// best-effort contract intact.
|
||||
}
|
||||
// Log the input event's identifying fields. These three (EventId,
|
||||
// Kind, Status) are immutable across the filter+stamp chain — the
|
||||
// `with` clones above touch only SourceNode and IngestedAtUtc — so
|
||||
// referencing `evt` here is intentional and equivalent to the
|
||||
// stamped record for diagnostics. If you add a field here that the
|
||||
// stamp chain DOES mutate (e.g., SourceNode), reference the latest
|
||||
// post-stamp record name instead, not `evt`.
|
||||
// Log the input event's identifying fields. EventId + Action are
|
||||
// immutable across the redact+stamp chain — the `with` clones above
|
||||
// touch only SourceNode and DetailsJson — so referencing `evt` here
|
||||
// is intentional and equivalent to the stamped record for
|
||||
// diagnostics. Action = "{Channel}.{Kind}" carries the kind; the
|
||||
// canonical Outcome carries the coarse status (fine-grained Status
|
||||
// lives in DetailsJson).
|
||||
_logger.LogWarning(
|
||||
ex,
|
||||
"CentralAuditWriter failed for EventId {EventId} (Kind={Kind}, Status={Status})",
|
||||
evt.EventId, evt.Kind, evt.Status);
|
||||
"CentralAuditWriter failed for EventId {EventId} (Action={Action}, Outcome={Outcome})",
|
||||
evt.EventId, evt.Action, evt.Outcome);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user