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:
@@ -111,12 +111,14 @@ public class DiffService
|
||||
a.CanonicalName == b.CanonicalName &&
|
||||
a.Value == b.Value &&
|
||||
a.DataType == b.DataType &&
|
||||
a.Description == b.Description &&
|
||||
a.IsLocked == b.IsLocked &&
|
||||
a.DataSourceReference == b.DataSourceReference &&
|
||||
a.BoundDataConnectionId == b.BoundDataConnectionId;
|
||||
|
||||
private static bool AlarmsEqual(ResolvedAlarm a, ResolvedAlarm b) =>
|
||||
a.CanonicalName == b.CanonicalName &&
|
||||
a.Description == b.Description &&
|
||||
a.PriorityLevel == b.PriorityLevel &&
|
||||
a.IsLocked == b.IsLocked &&
|
||||
a.TriggerType == b.TriggerType &&
|
||||
@@ -132,4 +134,27 @@ public class DiffService
|
||||
a.ParameterDefinitions == b.ParameterDefinitions &&
|
||||
a.ReturnDefinition == b.ReturnDefinition &&
|
||||
a.MinTimeBetweenRuns == b.MinTimeBetweenRuns;
|
||||
|
||||
/// <summary>
|
||||
/// Compares two <see cref="ConnectionConfig"/> instances for equality across
|
||||
/// the fields that travel in the deployment package: protocol, primary and
|
||||
/// backup configuration JSON, and failover retry count. Used by callers that
|
||||
/// need to detect connection-endpoint drift; the public diff shape only
|
||||
/// exposes attribute / alarm / script changes today (see TemplateEngine-018
|
||||
/// for the diff-shape extension that surfaces added / removed / changed
|
||||
/// connections in the UI).
|
||||
/// </summary>
|
||||
/// <param name="a">First connection configuration.</param>
|
||||
/// <param name="b">Second connection configuration.</param>
|
||||
/// <returns>True when both configurations are equal.</returns>
|
||||
public static bool ConnectionsEqual(ConnectionConfig a, ConnectionConfig b)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(a);
|
||||
ArgumentNullException.ThrowIfNull(b);
|
||||
|
||||
return a.Protocol == b.Protocol &&
|
||||
a.ConfigurationJson == b.ConfigurationJson &&
|
||||
a.BackupConfigurationJson == b.BackupConfigurationJson &&
|
||||
a.FailoverRetryCount == b.FailoverRetryCount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ public class RevisionHashService
|
||||
CanonicalName = a.CanonicalName,
|
||||
Value = a.Value,
|
||||
DataType = a.DataType,
|
||||
Description = a.Description,
|
||||
IsLocked = a.IsLocked,
|
||||
DataSourceReference = a.DataSourceReference,
|
||||
BoundDataConnectionId = a.BoundDataConnectionId
|
||||
@@ -62,6 +63,7 @@ public class RevisionHashService
|
||||
.Select(a => new HashableAlarm
|
||||
{
|
||||
CanonicalName = a.CanonicalName,
|
||||
Description = a.Description,
|
||||
PriorityLevel = a.PriorityLevel,
|
||||
IsLocked = a.IsLocked,
|
||||
TriggerType = a.TriggerType,
|
||||
@@ -82,7 +84,20 @@ public class RevisionHashService
|
||||
ReturnDefinition = s.ReturnDefinition,
|
||||
MinTimeBetweenRunsTicks = s.MinTimeBetweenRuns?.Ticks
|
||||
})
|
||||
.ToList()
|
||||
.ToList(),
|
||||
Connections = configuration.Connections is { Count: > 0 }
|
||||
? new SortedDictionary<string, HashableConnection>(
|
||||
configuration.Connections.ToDictionary(
|
||||
kvp => kvp.Key,
|
||||
kvp => new HashableConnection
|
||||
{
|
||||
BackupConfigurationJson = kvp.Value.BackupConfigurationJson,
|
||||
ConfigurationJson = kvp.Value.ConfigurationJson,
|
||||
FailoverRetryCount = kvp.Value.FailoverRetryCount,
|
||||
Protocol = kvp.Value.Protocol
|
||||
}),
|
||||
StringComparer.Ordinal)
|
||||
: null
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(hashInput, CanonicalJsonOptions);
|
||||
@@ -108,6 +123,12 @@ public class RevisionHashService
|
||||
/// </summary>
|
||||
public List<HashableAttribute> Attributes { get; init; } = [];
|
||||
/// <summary>
|
||||
/// Data connection configurations keyed by connection name. Sorted by key
|
||||
/// (ordinal) to keep serialization deterministic. Null when the deployment
|
||||
/// package carries no connections.
|
||||
/// </summary>
|
||||
public SortedDictionary<string, HashableConnection>? Connections { get; init; }
|
||||
/// <summary>
|
||||
/// The unique instance name.
|
||||
/// </summary>
|
||||
public string InstanceUniqueName { get; init; } = string.Empty;
|
||||
@@ -144,6 +165,11 @@ public class RevisionHashService
|
||||
/// </summary>
|
||||
public string DataType { get; init; } = string.Empty;
|
||||
/// <summary>
|
||||
/// The attribute description (authoring-time documentation that still
|
||||
/// travels with the deployed payload).
|
||||
/// </summary>
|
||||
public string? Description { get; init; }
|
||||
/// <summary>
|
||||
/// Whether the attribute is locked.
|
||||
/// </summary>
|
||||
public bool IsLocked { get; init; }
|
||||
@@ -160,6 +186,11 @@ public class RevisionHashService
|
||||
/// </summary>
|
||||
public string CanonicalName { get; init; } = string.Empty;
|
||||
/// <summary>
|
||||
/// The alarm description (authoring-time documentation that still
|
||||
/// travels with the deployed payload).
|
||||
/// </summary>
|
||||
public string? Description { get; init; }
|
||||
/// <summary>
|
||||
/// Whether the alarm is locked.
|
||||
/// </summary>
|
||||
public bool IsLocked { get; init; }
|
||||
@@ -181,6 +212,26 @@ public class RevisionHashService
|
||||
public string TriggerType { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
private sealed record HashableConnection
|
||||
{
|
||||
/// <summary>
|
||||
/// Backup connection configuration JSON, if any.
|
||||
/// </summary>
|
||||
public string? BackupConfigurationJson { get; init; }
|
||||
/// <summary>
|
||||
/// Primary connection configuration JSON.
|
||||
/// </summary>
|
||||
public string? ConfigurationJson { get; init; }
|
||||
/// <summary>
|
||||
/// Number of failover retries before giving up.
|
||||
/// </summary>
|
||||
public int FailoverRetryCount { get; init; }
|
||||
/// <summary>
|
||||
/// Protocol name (e.g. "OpcUa").
|
||||
/// </summary>
|
||||
public string Protocol { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
private sealed record HashableScript
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user