fix(dashboard): copy Galaxy summary volatile fields fresh; memoize only O(N) breakdown

Resolves Server-059, Tests-041, Server-060 (2026-06-25 re-review).

DashboardSnapshotService memoized the whole Galaxy summary keyed on the cache
Sequence, but the shared library bumps Sequence only on a heavy refresh: the
steady-state tick, the refresh-failure path, and the age-based status getter all
replace the entry via 'previous with { ... }' at the SAME Sequence. The dashboard
therefore froze LastQueriedAt and, during a Galaxy SQL outage, kept showing
Healthy with no error for the whole outage.

Split DashboardGalaxySummaryProjector into ComputeBreakdown (the O(N) template/
category work, the only sequence-bound part) and BuildSummary (cheap volatile
fields copied fresh). ResolveGalaxySummary now memoizes only the breakdown by
Sequence and rebuilds the summary from the current entry each tick. Removed the
redundant DashboardGalaxyProjector wrapper.

Tests: same-sequence status/error/timestamp now reflected (the regression);
memoization-hit and sequence-invalidation guards; GatewayApplicationTests asserts
the DI container resolves IGalaxyBrowseScopeProvider to GatewayBrowseScopeProvider
(pins the registration-order invariant over the library's no-op default).
This commit is contained in:
Joseph Doherty
2026-06-25 13:42:39 -04:00
parent b062cc0440
commit 9ae6bce0c8
6 changed files with 246 additions and 68 deletions
@@ -41,6 +41,25 @@ public sealed class GatewayApplicationTests
Assert.Equal("SerilogLoggerFactory", factory.GetType().Name);
}
/// <summary>
/// Verifies that Build registers the gateway's browse-scope provider so it wins over the
/// shared library's no-op default (Server-060). The per-API-key browse-subtree scoping
/// depends on <c>AddSingleton&lt;IGalaxyBrowseScopeProvider, GatewayBrowseScopeProvider&gt;</c>
/// running before <c>AddZbGalaxyRepository</c>, whose <c>TryAddSingleton</c> default
/// (NullGalaxyBrowseScopeProvider → full hierarchy) must lose. If this registration order ever
/// regressed, every metadata-scoped key would silently see the entire Galaxy hierarchy.
/// </summary>
[Fact]
public async Task Build_RegistersGatewayBrowseScopeProviderOverLibraryDefault()
{
await using WebApplication app = GatewayApplication.Build([]);
ZB.MOM.WW.GalaxyRepository.Grpc.IGalaxyBrowseScopeProvider scopeProvider =
app.Services.GetRequiredService<ZB.MOM.WW.GalaxyRepository.Grpc.IGalaxyBrowseScopeProvider>();
Assert.IsType<ZB.MOM.WW.MxGateway.Server.Security.Authorization.GatewayBrowseScopeProvider>(scopeProvider);
}
/// <summary>Verifies that Build registers the gateway metrics service.</summary>
[Fact]
public async Task Build_RegistersGatewayMetrics()