fix(high-severity): close 9 of 10 open High findings across 8 modules
Comm-016: delete dead HandleConnectionStateChanged + _debugSubscriptions / _inProgressDeployments tracking + ConnectionStateChanged message record. Disconnect detection is owned by the transport layers (gRPC keepalive PING ~25s; Ask-timeout at CommunicationService). Updates the Component-Communication.md design doc to make that explicit. SnF-018: NotificationForwarder.DeliverAsync now discards a corrupt buffered payload (Warning log + return true) instead of returning false and parking the row — honoring the design's "notifications do not park" invariant. DM-018: reconciliation no longer force-sets Enabled, preserving an intentional Disabled state after central failover. ESG-018: DeliverBufferedAsync (both ExternalSystemClient + DatabaseGateway) catches JsonException and returns false, turning a corrupt buffered row into a parked operation instead of a retry-forever poison message. InboundAPI-022: register ActiveNodeGate as IActiveNodeGate in the Central DI branch so standby-node gating is actually wired up in production. NS-019: remove orphaned NotificationDeliveryService / INotificationDeliveryService / NotificationResult; central notification delivery now lives entirely in NotificationOutbox. SEL-016: normalise From/To filters to UTC before ISO-string compare so non-UTC DateTimeOffset clients no longer get spuriously excluded events. TE-017: include Description on attributes/alarms and a HashableConnections projection (protocol, endpoint JSON, failover count) in the revision hash and DiffService; staleness detection now catches description-only and connection-endpoint edits. Transport-001 and Transport-002 (also High) remain Open — they're being handled in a follow-up batch because both touch BundleImporter.cs and must serialise.
This commit is contained in:
@@ -17,6 +17,7 @@ using ScadaLink.ExternalSystemGateway;
|
||||
using ScadaLink.HealthMonitoring;
|
||||
using ScadaLink.Host;
|
||||
using ScadaLink.Host.Actors;
|
||||
using ScadaLink.Host.Health;
|
||||
using ScadaLink.InboundAPI;
|
||||
using ScadaLink.ManagementService;
|
||||
using ScadaLink.NotificationService;
|
||||
@@ -204,9 +205,13 @@ public class CentralCompositionRootTests : IDisposable
|
||||
new object[] { typeof(IExternalSystemClient) },
|
||||
new object[] { typeof(DatabaseGateway) },
|
||||
new object[] { typeof(IDatabaseGateway) },
|
||||
// NotificationService
|
||||
new object[] { typeof(NotificationDeliveryService) },
|
||||
new object[] { typeof(INotificationDeliveryService) },
|
||||
// NotificationService — central-only SMTP primitives. The orphan
|
||||
// NotificationDeliveryService + INotificationDeliveryService were removed
|
||||
// (NS-019) when sites stopped delivering notifications; the central
|
||||
// EmailNotificationDeliveryAdapter is now the only resolver of these
|
||||
// primitives.
|
||||
new object[] { typeof(Func<ISmtpClientWrapper>) },
|
||||
new object[] { typeof(OAuth2TokenService) },
|
||||
// ConfigurationDatabase repositories
|
||||
new object[] { typeof(ScadaLinkDbContext) },
|
||||
new object[] { typeof(ISecurityRepository) },
|
||||
@@ -277,6 +282,30 @@ public class CentralCompositionRootTests : IDisposable
|
||||
var hostedServices = _factory.Services.GetServices<IHostedService>();
|
||||
Assert.Contains(hostedServices, s => s.GetType() == typeof(CentralHealthAggregator));
|
||||
}
|
||||
|
||||
// --- InboundAPI-022 regression ---
|
||||
|
||||
/// <summary>
|
||||
/// InboundAPI-022 regression: the Central composition root MUST register a
|
||||
/// concrete <see cref="IActiveNodeGate"/> implementation. Without it,
|
||||
/// <see cref="InboundApiEndpointFilter"/> falls through to "allow" and a
|
||||
/// standby central node continues to serve the inbound API, racing the
|
||||
/// active node and executing scripts against stale singleton state. The
|
||||
/// design's "central cluster only (active node)" guarantee is enforced only
|
||||
/// when the production gate is wired here.
|
||||
///
|
||||
/// Structural check on the built provider (not just <see cref="IServiceCollection"/>)
|
||||
/// — a registration the framework cannot resolve to a concrete instance is
|
||||
/// indistinguishable from "missing" at runtime, which is the failure mode
|
||||
/// the finding describes.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Central_IActiveNodeGate_IsRegisteredAsActiveNodeGate()
|
||||
{
|
||||
var gate = _factory.Services.GetService<IActiveNodeGate>();
|
||||
Assert.NotNull(gate);
|
||||
Assert.IsType<ActiveNodeGate>(gate);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user