Files
ScadaBridge/src/ZB.MOM.WW.ScadaBridge.SiteCallAudit/ZB.MOM.WW.ScadaBridge.SiteCallAudit.csproj
T
Joseph Doherty e427b38fb3 feat(sitecallaudit): periodic reconciliation pull back-fills lost telemetry
Add a periodic reconciliation tick to SiteCallAuditActor that, per site,
pulls changed SiteCall rows since a per-site UpdatedAtUtc cursor and upserts
them idempotently (monotonic UpsertAsync) — the documented self-heal for lost
best-effort gRPC telemetry. Mirrors SiteAuditReconciliationActor's structure
(per-site cursor, per-site try/catch failure isolation, advance cursor by max
observed UpdatedAtUtc) minus the stalled-detection EventStream machinery.

Dependency wiring: add an acyclic SiteCallAudit -> AuditLog project reference
and resolve IPullSiteCallsClient + ISiteEnumerator (central-only singletons
registered by AddAuditLogCentralReconciliationClient) from the IServiceProvider
the production ctor already holds — no Host Props.Create change needed. The
repo-only test ctor injects neither collaborator, so the tick is gated off
there. A new public test ctor injects fake client + enumerator + repo so the
tick is unit-testable in-memory (public, not internal: Akka's ActivatorProducer
uses public-only reflection binding).

Options: ReconciliationInterval (default 5 min, clamped >= 1s so a zero config
value can't spin the scheduler) + ReconciliationBatchSize (default 500), plus a
test-only override that bypasses the clamp for millisecond cadences.

Tests (all in-memory, no live MSSQL): absent row is upserted on a tick; second
tick advances the cursor past already-pulled rows; one failing site does not
sink other sites; repo-only ctor does not start the tick.
2026-06-15 12:01:22 -04:00

48 lines
2.9 KiB
XML

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<!-- SiteCallAuditActor is an Akka actor (central singleton in Bundle F); Akka is an explicit dependency. -->
<PackageReference Include="Akka" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Options" />
<!-- BindConfiguration extension for the SiteCallAuditOptions binding. -->
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../ZB.MOM.WW.ScadaBridge.Commons/ZB.MOM.WW.ScadaBridge.Commons.csproj" />
<!-- Site Call Audit (#22) sits alongside Notification Outbox (#21) and Audit Log (#23).
ISiteCallAuditRepository is registered by ZB.MOM.WW.ScadaBridge.ConfigurationDatabase; the
project reference is documented here so the actor's scope-per-message
GetRequiredService<ISiteCallAuditRepository>() compiles. -->
<ProjectReference Include="../ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.csproj" />
<!-- Task 5 (#22): the central→site Retry/Discard relay routes RetryParkedOperation /
DiscardParkedOperation to the owning site via SiteEnvelope + CentralCommunicationActor,
the same transport every other central→site command uses. SiteEnvelope is defined
in ZB.MOM.WW.ScadaBridge.Communication (no cycle: Communication does not reference SiteCallAudit). -->
<ProjectReference Include="../ZB.MOM.WW.ScadaBridge.Communication/ZB.MOM.WW.ScadaBridge.Communication.csproj" />
<!-- Reconciliation tick (#22): the per-site PullSiteCalls self-heal pull resolves
IPullSiteCallsClient + ISiteEnumerator (both central-only singletons registered by
AddAuditLogCentralReconciliationClient) from the actor's root IServiceProvider. They live
in ZB.MOM.WW.ScadaBridge.AuditLog.Central so the SiteCall pull client reuses the shared
SiteEntry enumerator the sibling IPullAuditEventsClient already uses. No cycle: AuditLog
references only Commons / ConfigurationDatabase / Communication — none of which reference
SiteCallAudit. Preferred over moving the interfaces into Commons (Commons has no Akka /
Communication dependency and would have to carry a Communication-adjacent message). -->
<ProjectReference Include="../ZB.MOM.WW.ScadaBridge.AuditLog/ZB.MOM.WW.ScadaBridge.AuditLog.csproj" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="ZB.MOM.WW.ScadaBridge.SiteCallAudit.Tests" />
</ItemGroup>
</Project>