perf(comms+audit): close phase-2 residuals — direct ingest path, monotonic timeouts, synthetic probe, not-reporting set, cursor-exact audit pull

This commit is contained in:
Joseph Doherty
2026-08-14 21:38:23 -04:00
parent 4cd1441984
commit a5882753dd
38 changed files with 1254 additions and 443 deletions
@@ -81,7 +81,7 @@ public class SiteAlarmLiveCacheServiceTests : TestKit
}
private SiteAlarmLiveCacheService CreateService(TimeSpan linger, out CountingFactory factory,
int maxSubscribersPerSite = 200)
int maxSubscribersPerSite = 200, IReadOnlyList<Instance>? enabledInstances = null)
{
// Site with gRPC addresses, and NO enabled instances → the seed fan-out returns
// empty immediately (so IsLive flips true fast without any snapshot Asks).
@@ -97,7 +97,7 @@ public class SiteAlarmLiveCacheServiceTests : TestKit
var instanceRepo = Substitute.For<ITemplateEngineRepository>();
instanceRepo.GetInstancesBySiteIdAsync(SiteId, Arg.Any<CancellationToken>())
.Returns(new List<Instance>());
.Returns((IReadOnlyList<Instance>)(enabledInstances ?? new List<Instance>()));
var services = new ServiceCollection();
services.AddScoped(_ => siteRepo);
@@ -121,6 +121,45 @@ public class SiteAlarmLiveCacheServiceTests : TestKit
return service;
}
[Fact]
public void Seed_FanOut_Publishes_The_Instances_That_Failed_To_Answer()
{
// Arch-review phase-2 residual #4: the seed/reconcile fan-out already knows which
// Enabled instances failed to answer and used to discard it, forcing the Alarm Summary
// page to run a SECOND identical fan-out purely to rebuild that list. It is now
// published alongside the snapshot.
var instances = new List<Instance>
{
new("inst-b") { Id = 2, SiteId = SiteId, State = InstanceState.Enabled },
new("inst-a") { Id = 1, SiteId = SiteId, State = InstanceState.Enabled },
// Disabled instances are never fanned out, so they can never be "not reporting".
new("inst-off") { Id = 3, SiteId = SiteId, State = InstanceState.Disabled },
};
// The CommunicationService has no site actor wired, so every snapshot Ask faults —
// which is exactly the "instance did not answer" case.
var service = CreateService(TimeSpan.FromMilliseconds(200), out _, enabledInstances: instances);
using var sub = service.Subscribe(SiteId, () => { });
AwaitCondition(() => service.IsLive(SiteId), TimeSpan.FromSeconds(5));
AwaitCondition(
() => service.GetNotReportingInstances(SiteId).Count == 2,
TimeSpan.FromSeconds(5));
// Ordered by name (ordinal-ignore-case), matching AlarmSummaryService's poll output so
// the page renders identically whichever source supplied the list.
Assert.Equal(new[] { "inst-a", "inst-b" }, service.GetNotReportingInstances(SiteId));
}
[Fact]
public void NotReporting_Is_Empty_For_An_Unknown_Site()
{
var service = CreateService(TimeSpan.FromMilliseconds(200), out _);
Assert.Empty(service.GetNotReportingInstances(SiteId));
}
[Fact]
public void First_Subscriber_Starts_One_Aggregator_Shared_By_Multiple_Viewers()
{