feat(notif): NotificationOutboxActor + CentralAuditWriter wired (#23 M4)

M4 Bundle B (B1) — add the central-only ICentralAuditWriter implementation
and inject it into NotificationOutboxActor so subsequent tasks (B2/B3) can
route attempt + terminal lifecycle events through the direct-write audit path.

- CentralAuditWriter: thin wrapper around IAuditLogRepository.InsertIfNotExistsAsync;
  scope-per-call (matches AuditLogIngestActor / NotificationOutboxActor pattern);
  stamps IngestedAtUtc; swallows all internal failures (alog.md §13).
- Registered as a singleton in AddAuditLog.
- NotificationOutboxActor ctor takes ICentralAuditWriter (validated non-null).
- Host wiring resolves the writer once from the root provider and passes it
  into the singleton's Props.Create call.
- Existing TestKit fixtures updated with a NoOpCentralAuditWriter helper so
  tests that don't exercise audit emission still compile and pass.
This commit is contained in:
Joseph Doherty
2026-05-20 16:04:01 -04:00
parent e4d902753b
commit b31747a632
13 changed files with 383 additions and 4 deletions

View File

@@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using ScadaLink.AuditLog.Central;
using ScadaLink.AuditLog.Configuration;
using ScadaLink.AuditLog.Site;
using ScadaLink.AuditLog.Site.Telemetry;
@@ -155,6 +156,34 @@ public class AddAuditLogTests
Assert.IsType<NoOpSiteStreamAuditClient>(client);
}
// -- M4 Bundle B (B1) central direct-write audit writer -----------------
[Fact]
public void AddAuditLog_Registers_ICentralAuditWriter_AsCentralAuditWriter()
{
using var provider = BuildProvider(new Dictionary<string, string?>
{
["AuditLog:SiteWriter:DatabasePath"] = ":memory:",
});
var writer = provider.GetService<ICentralAuditWriter>();
Assert.NotNull(writer);
Assert.IsType<CentralAuditWriter>(writer);
}
[Fact]
public void AddAuditLog_ICentralAuditWriter_IsSingleton()
{
using var provider = BuildProvider(new Dictionary<string, string?>
{
["AuditLog:SiteWriter:DatabasePath"] = ":memory:",
});
var w1 = provider.GetService<ICentralAuditWriter>();
var w2 = provider.GetService<ICentralAuditWriter>();
Assert.Same(w1, w2);
}
[Fact]
public void AddAuditLog_Options_Bind_RoundTrip_SqliteWriter()
{