Files
scadalink-design/src/ScadaLink.Host/StoreAndForwardSiteContext.cs
Joseph Doherty 6fe23a4d9b feat(host): register SiteCallAuditActor + CachedCallTelemetry forwarder/bridge (#22, #23 M3)
M3 Bundle F (Task F1) wires the cached-call audit pipeline through the
composition roots:

- Central: register SiteCallAuditActor as a cluster singleton + proxy
  (mirrors AuditLogIngestActor and NotificationOutboxActor). Program.cs
  calls .AddSiteCallAudit() on the central role.
- Site: register ICachedCallTelemetryForwarder + CachedCallLifecycleBridge
  in AddAuditLog (lazy factory — Central nodes degrade to audit-only
  emission because IOperationTrackingStore is site-only).
- Site: bind CachedCallLifecycleBridge to ICachedCallLifecycleObserver so
  StoreAndForwardService picks it up via DI.
- Site: introduce IStoreAndForwardSiteContext + Host adapter to surface the
  site id to StoreAndForwardService without creating a
  StoreAndForward -> HealthMonitoring project-reference cycle.
- ScriptExecutionActor resolves ICachedCallTelemetryForwarder per script
  scope and threads it into ScriptRuntimeContext.

CachedCallTelemetryForwarder's IOperationTrackingStore dependency is now
nullable so Central DI validation succeeds with the lazy registration; the
forwarder's tracking-half emission is a no-op when the store is absent.

Tests:
- AkkaHostedServiceAuditWiringTests: Central host builds with
  AddSiteCallAudit and resolves ICachedCallTelemetryForwarder; Site
  resolves the forwarder + bridge + observer + IStoreAndForwardSiteContext.
- Full solution: 194 Host tests green, 241 SiteRuntime tests green, every
  other suite unchanged.
2026-05-20 15:10:47 -04:00

33 lines
1.4 KiB
C#

using Microsoft.Extensions.Options;
using ScadaLink.StoreAndForward;
namespace ScadaLink.Host;
/// <summary>
/// Audit Log #23 (M3 Bundle F): Host-side adapter implementing the
/// optional <see cref="IStoreAndForwardSiteContext"/> the Store-and-Forward
/// service consults to stamp cached-call audit telemetry with the site id.
/// </summary>
/// <remarks>
/// Forwards <see cref="NodeOptions.SiteId"/> verbatim — the same value
/// <see cref="SiteIdentityProvider"/> exposes to HealthMonitoring. Defined as
/// a separate adapter (rather than reusing <see cref="SiteIdentityProvider"/>)
/// to avoid pulling HealthMonitoring into the StoreAndForward project's
/// dependency graph, which would create a project-reference cycle.
/// </remarks>
public class StoreAndForwardSiteContext : IStoreAndForwardSiteContext
{
public string SiteId { get; }
public StoreAndForwardSiteContext(IOptions<NodeOptions> nodeOptions)
{
// NodeOptions.SiteId is nullable; SiteServiceRegistration ONLY adds
// this binding on the site role, so a non-null site id is expected
// here. Mirror SiteIdentityProvider's hard fail so a missing site id
// surfaces at composition time rather than at the first cached call.
SiteId = nodeOptions.Value.SiteId
?? throw new InvalidOperationException(
"ScadaLink:Node:SiteId is required for the site role's StoreAndForward wiring.");
}
}