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

@@ -8,8 +8,10 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using NSubstitute;
using ScadaLink.Commons.Entities.Audit;
using ScadaLink.Commons.Entities.Notifications;
using ScadaLink.Commons.Interfaces.Repositories;
using ScadaLink.Commons.Interfaces.Services;
using ScadaLink.Commons.Messages.Notification;
using ScadaLink.Commons.Types.Enums;
using ScadaLink.ConfigurationDatabase;
@@ -172,7 +174,20 @@ public class NotificationOutboxFlowTests : TestKit
};
return Sys.ActorOf(Props.Create(() => new NotificationOutboxActor(
services, options, NullLogger<NotificationOutboxActor>.Instance)));
services,
options,
(ICentralAuditWriter)new NoOpCentralAuditWriter(),
NullLogger<NotificationOutboxActor>.Instance)));
}
/// <summary>
/// Test-only no-op <see cref="ICentralAuditWriter"/>. The integration tests
/// in this file pre-date M4 Bundle B's audit-writer injection; they do not
/// assert on emission, just need a non-null collaborator.
/// </summary>
private sealed class NoOpCentralAuditWriter : ICentralAuditWriter
{
public Task WriteAsync(AuditEvent evt, CancellationToken ct = default) => Task.CompletedTask;
}
private static NotificationSubmit MakeSubmit(string notificationId)