Files
scadalink-design/src/ScadaLink.SiteCallAudit/ServiceCollectionExtensions.cs
Joseph Doherty de110f8b42 feat(scaudit): SiteCallAuditActor minimum surface (#22, #23 M3)
Bundle C of Audit Log #23 M3.

Adds the ScadaLink.SiteCallAudit project + matching tests project, mirroring
the ScadaLink.AuditLog scaffolding pattern (net10.0, central package
management, InternalsVisibleTo to the tests assembly).

SiteCallAuditActor is the central singleton entry point for Site Call Audit
(#22): it receives UpsertSiteCallCommand and persists the SiteCall via
ISiteCallAuditRepository.UpsertAsync (monotonic, idempotent — out-of-order
or duplicate updates are silent no-ops at the repo). Audit-write failures
NEVER abort the user-facing action (CLAUDE.md): repository throws are
caught + logged, the actor replies Accepted=false, and the singleton stays
alive (Resume supervisor strategy as defence in depth).

Two constructors mirror AuditLogIngestActor:
- IServiceProvider production constructor resolves the scoped EF repository
  from a fresh DI scope per message.
- ISiteCallAuditRepository test constructor injects a concrete repository so
  the TestKit tests exercise the real monotonic-upsert SQL end to end.

UpsertSiteCallCommand + UpsertSiteCallReply live in ScadaLink.Commons (same
home as IngestAuditEventsCommand) so Bundle D's gRPC server can construct
them without taking a project reference on the actor's host project.

AddSiteCallAudit() is a placeholder for symmetry with AddAuditLog /
AddNotificationOutbox; Bundle F will populate it with the actor's Props
factory + options bindings.

Tests (Akka.TestKit.Xunit2 + MsSqlMigrationFixture via project ref to
ScadaLink.ConfigurationDatabase.Tests, mirroring Bundle D2):
- Receive_UpsertSiteCallCommand_Persists_Replies_Accepted
- Receive_DuplicateUpsert_OlderStatus_NoOp_StillRepliesAccepted (idempotency)
- Receive_RepoThrowsTransient_RepliesAccepted_False_ActorStaysAlive

Reconciliation, KPIs, and the central->site Retry/Discard relay are
deferred per CLAUDE.md scope discipline.

ScadaLink.slnx updated to include both new projects.

All 3 new tests pass against the running infra/mssql container; full suite
(2683 tests across 27 projects) passes with no regressions.
2026-05-20 14:18:49 -04:00

40 lines
1.7 KiB
C#

using Microsoft.Extensions.DependencyInjection;
namespace ScadaLink.SiteCallAudit;
/// <summary>
/// Composition root for the Site Call Audit (#22) component.
/// </summary>
/// <remarks>
/// <para>
/// M3 Bundle C ships the ingest-only minimum surface (the actor itself); the
/// full DI surface — reconciliation puller, KPI projector, central→site
/// Retry/Discard relay, options + validators — is deferred to a follow-up.
/// </para>
/// <para>
/// The repository (<c>ISiteCallAuditRepository</c>) is registered by
/// <c>ScadaLink.ConfigurationDatabase.ServiceCollectionExtensions.AddConfigurationDatabase</c>,
/// so callers (the Host on the central node) must also call that. The actor's
/// <c>Props</c> are wired up in Host registration (Bundle F); this extension
/// is currently a no-op placeholder kept for symmetry with the AuditLog and
/// NotificationOutbox composition roots — adding it now means consumers can
/// reference the method without re-touching the Host project later.
/// </para>
/// </remarks>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Registers Site Call Audit (#22) services. Currently a no-op
/// placeholder — Bundle F will populate this with the actor's Props
/// factory + options bindings. The method is exposed now so the Host
/// wiring call already exists at the API boundary.
/// </summary>
public static IServiceCollection AddSiteCallAudit(this IServiceCollection services)
{
ArgumentNullException.ThrowIfNull(services);
// Actor props are constructed in Host wiring (Bundle F). This
// extension is a placeholder for future config + DI.
return services;
}
}