diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/CliArguments.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/CliArguments.cs index 5854613..860b900 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/CliArguments.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/CliArguments.cs @@ -44,6 +44,7 @@ internal sealed class CliArguments /// Returns whether the named flag was present in the arguments. /// The flag name (without '--' prefix). + /// true if the flag was present; otherwise, false. public bool HasFlag(string name) { return _flags.Contains(name); @@ -51,6 +52,7 @@ internal sealed class CliArguments /// Returns the value for a named argument, or null if absent. /// The argument name (without '--' prefix). + /// The argument value, or null if the argument is absent. public string? GetOptional(string name) { return _values.TryGetValue(name, out string? value) @@ -60,6 +62,7 @@ internal sealed class CliArguments /// Returns the value for a required named argument, or throws if absent. /// The argument name (without '--' prefix). + /// The argument value. public string GetRequired(string name) { string? value = GetOptional(name); @@ -74,6 +77,7 @@ internal sealed class CliArguments /// Parses and returns an int32 argument, or the default value if absent. /// The argument name (without '--' prefix). /// The default value if the argument is absent; if null, the argument is required. + /// The parsed int32 value. public int GetInt32(string name, int? defaultValue = null) { string? value = GetOptional(name); @@ -93,6 +97,7 @@ internal sealed class CliArguments /// Parses and returns a uint32 argument, or the default value if absent. /// The argument name (without '--' prefix). /// The default value if the argument is absent. + /// The parsed uint32 value. public uint GetUInt32(string name, uint defaultValue) { string? value = GetOptional(name); @@ -104,6 +109,7 @@ internal sealed class CliArguments /// Parses and returns a uint64 argument, or the default value if absent. /// The argument name (without '--' prefix). /// The default value if the argument is absent. + /// The parsed uint64 value. public ulong GetUInt64(string name, ulong defaultValue) { string? value = GetOptional(name); @@ -115,6 +121,7 @@ internal sealed class CliArguments /// Parses and returns a TimeSpan argument, or the default value if absent. Supports "ms", "s", and standard TimeSpan format. /// The argument name (without '--' prefix). /// The default value if the argument is absent. + /// The parsed duration. public TimeSpan GetDuration(string name, TimeSpan defaultValue) { string? value = GetOptional(name); diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/MxGatewayCliClientAdapter.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/MxGatewayCliClientAdapter.cs index 568ad8c..2db83b2 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/MxGatewayCliClientAdapter.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/MxGatewayCliClientAdapter.cs @@ -108,7 +108,8 @@ internal sealed class MxGatewayCliClientAdapter : IMxGatewayCliClient return _galaxyClient.Value.BrowseChildrenRawAsync(request, cancellationToken); } - /// + /// Disposes the adapted gateway client and, if created, the lazily-initialized Galaxy Repository client. + /// A task that represents the asynchronous operation. public async ValueTask DisposeAsync() { if (_galaxyClient.IsValueCreated) diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/MxGatewayCliSecretRedactor.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/MxGatewayCliSecretRedactor.cs index d4e3003..b692eaa 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/MxGatewayCliSecretRedactor.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/MxGatewayCliSecretRedactor.cs @@ -6,6 +6,7 @@ internal static class MxGatewayCliSecretRedactor /// Replaces occurrences of the API key in the value with a redacted placeholder. /// The message text to redact. /// The API key to remove; no redaction if null or empty. + /// The value with any occurrences of replaced by a redacted placeholder. public static string Redact(string value, string? apiKey) { if (string.IsNullOrEmpty(value) || string.IsNullOrEmpty(apiKey)) diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/MxGatewayClientCli.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/MxGatewayClientCli.cs index e8d56a8..0e47d8d 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/MxGatewayClientCli.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/MxGatewayClientCli.cs @@ -22,6 +22,7 @@ public static class MxGatewayClientCli /// Command-line arguments (command name followed by options). /// TextWriter for command output. /// TextWriter for error messages. + /// The process exit code: 0 on success, 1 on error. public static int Run( string[] args, TextWriter standardOutput, @@ -38,6 +39,7 @@ public static class MxGatewayClientCli /// TextWriter for error messages. /// Optional factory to create the gateway client; defaults to MxGatewayClient.Create. /// Optional TextReader for batch-mode stdin; defaults to . + /// A task producing the process exit code: 0 on success, 1 on error. public static Task RunAsync( string[] args, TextWriter standardOutput, @@ -155,9 +157,6 @@ public static class MxGatewayClientCli } catch (Exception exception) when (exception is not OperationCanceledException) { - // Client.Dotnet-028: redact the *effective* key — from --api-key or the - // --api-key-env environment variable — so an env-var-sourced key echoed - // in a transport error never reaches stderr unredacted. string? apiKey = TryResolveApiKey(arguments); string message = MxGatewayCliSecretRedactor.Redact(exception.Message, apiKey); @@ -303,7 +302,7 @@ public static class MxGatewayClientCli /// MXGATEWAY_API_KEY), returning when neither /// is set. Unlike this never throws, so the /// error-redaction catch block can strip the env-var-sourced key from - /// output (Client.Dotnet-028) without re-raising on the absent-key path. + /// output without re-raising on the absent-key path. /// private static string? TryResolveApiKey(CliArguments arguments) { @@ -744,7 +743,6 @@ public static class MxGatewayClientCli /// (e.g. -1, an easy copy-paste mistake for "unbounded") is /// rejected loudly rather than silently wrapped to ~49.7 days, /// which would park one worker thread per pending tag for hours. - /// Resolves Client.Dotnet-021. /// private static uint ParseTimeoutMs(CliArguments arguments, int defaultValue) { @@ -766,7 +764,6 @@ public static class MxGatewayClientCli /// its absence must not silently fall through to /// ReturnValue.Int32Value (which would be 0 for an empty /// reply, driving the rest of the bench against an invalid handle). - /// Resolves Client.Dotnet-019. /// private static int RequireRegisterServerHandle(MxCommandReply reply, string sessionId) { @@ -891,11 +888,6 @@ public static class MxGatewayClientCli } catch (Exception ex) when (ex is not OperationCanceledException) { - // Client.Dotnet-020: never swallow OperationCanceledException - // here. A bare `catch` would let Ctrl+C / parent CTS / - // wall-clock timeouts keep spinning until --duration-seconds - // elapsed, burning CPU and skewing the p99/max latency numbers - // with hundreds of immediate-OCE iterations. sw.Stop(); failedCalls++; latencyMillis.Add(sw.Elapsed.TotalMilliseconds); @@ -1122,8 +1114,6 @@ public static class MxGatewayClientCli } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { - // Client.Dotnet-017: graceful end-of-window completion mode for a - // finite-window event collector. Emit aggregate JSON below and exit 0. } if (json && !jsonLines) @@ -1193,10 +1183,6 @@ public static class MxGatewayClientCli } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { - // Mirrors stream-events (Client.Dotnet-017): the supplied token covers - // the user's --timeout wall-clock budget and external Ctrl+C / parent - // CTS cancellation. All are graceful completion modes for a - // finite-window alarm-feed collector: emit what arrived and exit 0. } if (json && !jsonLines) diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/BrowseChildrenSmokeTests.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/BrowseChildrenSmokeTests.cs index dd5f48e..943a106 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/BrowseChildrenSmokeTests.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/BrowseChildrenSmokeTests.cs @@ -14,6 +14,7 @@ public sealed class BrowseChildrenSmokeTests /// Verifies that BrowseChildren returns a non-zero cache sequence and /// a consistent children/child-has-children count from a live gateway. /// + /// A task that represents the asynchronous operation. [Fact(Skip = "Set MXGATEWAY_API_KEY and MXGATEWAY_ENDPOINT to enable.")] public async Task BrowseChildren_LiveGateway_ReturnsRootsWithCacheSequence() { diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/FakeGalaxyRepositoryTransport.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/FakeGalaxyRepositoryTransport.cs index a71d74c..4560929 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/FakeGalaxyRepositoryTransport.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/FakeGalaxyRepositoryTransport.cs @@ -8,14 +8,10 @@ namespace ZB.MOM.WW.MxGateway.Client.Tests; /// internal sealed class FakeGalaxyRepositoryTransport(MxGatewayClientOptions options) : IGalaxyRepositoryClientTransport { - /// - /// Gets the gateway client options. - /// + /// public MxGatewayClientOptions Options { get; } = options; - /// - /// Gets the raw gRPC client; always null for the fake. - /// + /// public GalaxyRepository.GalaxyRepositoryClient? RawClient => null; /// @@ -66,11 +62,7 @@ internal sealed class FakeGalaxyRepositoryTransport(MxGatewayClientOptions optio /// public Queue DiscoverHierarchyExceptions { get; } = new(); - /// - /// Records the request and either throws a queued exception or returns the configured reply. - /// - /// The TestConnectionRequest to process. - /// Call options specifying RPC behavior. + /// public Task TestConnectionAsync( TestConnectionRequest request, CallOptions callOptions) @@ -84,11 +76,7 @@ internal sealed class FakeGalaxyRepositoryTransport(MxGatewayClientOptions optio return Task.FromResult(TestConnectionReply); } - /// - /// Records the request and either throws a queued exception or returns the configured reply. - /// - /// The GetLastDeployTimeRequest to process. - /// Call options specifying RPC behavior. + /// public Task GetLastDeployTimeAsync( GetLastDeployTimeRequest request, CallOptions callOptions) @@ -102,11 +90,7 @@ internal sealed class FakeGalaxyRepositoryTransport(MxGatewayClientOptions optio return Task.FromResult(GetLastDeployTimeReply); } - /// - /// Records the request and either throws a queued exception or returns the configured reply. - /// - /// The DiscoverHierarchyRequest to process. - /// Call options specifying RPC behavior. + /// public Task DiscoverHierarchyAsync( DiscoverHierarchyRequest request, CallOptions callOptions) @@ -141,11 +125,7 @@ internal sealed class FakeGalaxyRepositoryTransport(MxGatewayClientOptions optio /// public Func? BrowseChildrenGate { get; set; } - /// - /// Records the request and either throws a queued exception or returns the configured reply. - /// - /// The BrowseChildrenRequest to process. - /// Call options specifying RPC behavior. + /// public async Task BrowseChildrenAsync( BrowseChildrenRequest request, CallOptions callOptions) @@ -187,11 +167,7 @@ internal sealed class FakeGalaxyRepositoryTransport(MxGatewayClientOptions optio /// public Func? WatchDeployEventsBeforeYield { get; set; } - /// - /// Records the request and streams events, checking for queued exceptions and calling WatchDeployEventsBeforeYield before each event. - /// - /// The WatchDeployEventsRequest to process. - /// Call options specifying RPC behavior. + /// public async IAsyncEnumerable WatchDeployEventsAsync( WatchDeployEventsRequest request, CallOptions callOptions) diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/FakeGatewayTransport.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/FakeGatewayTransport.cs index 7e81274..cb677fc 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/FakeGatewayTransport.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/FakeGatewayTransport.cs @@ -11,14 +11,10 @@ internal sealed class FakeGatewayTransport(MxGatewayClientOptions options) : IMx private readonly Queue _invokeReplies = new(); private readonly List _events = []; - /// - /// Gets the gateway client options. - /// + /// public MxGatewayClientOptions Options { get; } = options; - /// - /// Gets null, since this is a test fake without a real gRPC client. - /// + /// public MxAccessGateway.MxAccessGatewayClient? RawClient => null; /// @@ -102,11 +98,7 @@ internal sealed class FakeGatewayTransport(MxGatewayClientOptions options) : IMx /// public Queue InvokeExceptions { get; } = new(); - /// - /// Verifies that the OpenSessionAsync call is recorded and returns the configured reply. - /// - /// The OpenSessionRequest to process. - /// Call options specifying RPC behavior. + /// public Task OpenSessionAsync( OpenSessionRequest request, CallOptions callOptions) @@ -120,11 +112,7 @@ internal sealed class FakeGatewayTransport(MxGatewayClientOptions options) : IMx return Task.FromResult(OpenSessionReply); } - /// - /// Verifies that the CloseSessionAsync call is recorded and returns the configured reply. - /// - /// The CloseSessionRequest to process. - /// Call options specifying RPC behavior. + /// public Task CloseSessionAsync( CloseSessionRequest request, CallOptions callOptions) @@ -138,11 +126,7 @@ internal sealed class FakeGatewayTransport(MxGatewayClientOptions options) : IMx return Task.FromResult(CloseSessionReply); } - /// - /// Verifies that the InvokeAsync call is recorded and returns the next enqueued reply. - /// - /// The MxCommandRequest to process. - /// Call options specifying RPC behavior. + /// public Task InvokeAsync( MxCommandRequest request, CallOptions callOptions) @@ -156,11 +140,7 @@ internal sealed class FakeGatewayTransport(MxGatewayClientOptions options) : IMx return Task.FromResult(_invokeReplies.Dequeue()); } - /// - /// Verifies that the StreamEventsAsync call is recorded and yields all enqueued events. - /// - /// The StreamEventsRequest to process. - /// Call options specifying RPC behavior. + /// public async IAsyncEnumerable StreamEventsAsync( StreamEventsRequest request, CallOptions callOptions) @@ -193,11 +173,7 @@ internal sealed class FakeGatewayTransport(MxGatewayClientOptions options) : IMx _events.Add(gatewayEvent); } - /// - /// Records the acknowledge call and returns the next enqueued reply (or default). - /// - /// The acknowledge alarm request. - /// Call options specifying RPC behavior. + /// public Task AcknowledgeAlarmAsync( AcknowledgeAlarmRequest request, CallOptions callOptions) @@ -218,11 +194,7 @@ internal sealed class FakeGatewayTransport(MxGatewayClientOptions options) : IMx }); } - /// - /// Records the query call and yields each enqueued snapshot. - /// - /// The query active alarms request. - /// Call options specifying RPC behavior. + /// public async IAsyncEnumerable QueryActiveAlarmsAsync( QueryActiveAlarmsRequest request, CallOptions callOptions) @@ -251,11 +223,7 @@ internal sealed class FakeGatewayTransport(MxGatewayClientOptions options) : IMx _activeAlarmSnapshots.Add(snapshot); } - /// - /// Records the stream-alarms call and yields each enqueued feed message. - /// - /// The stream alarms request. - /// Call options specifying RPC behavior. + /// public async IAsyncEnumerable StreamAlarmsAsync( StreamAlarmsRequest request, CallOptions callOptions) diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/GalaxyRepositoryClientTests.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/GalaxyRepositoryClientTests.cs index 351a9d2..184bde7 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/GalaxyRepositoryClientTests.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/GalaxyRepositoryClientTests.cs @@ -9,6 +9,7 @@ public sealed class GalaxyRepositoryClientTests /// /// Verifies that TestConnectionAsync attaches the API key in request metadata and returns the Ok flag. /// + /// A task that represents the asynchronous operation. [Fact] public async Task TestConnectionAsync_AttachesApiKeyMetadataAndReturnsOkFlag() { @@ -27,6 +28,7 @@ public sealed class GalaxyRepositoryClientTests /// /// Verifies that TestConnectionAsync returns false when the server reports NotOk. /// + /// A task that represents the asynchronous operation. [Fact] public async Task TestConnectionAsync_ReturnsFalseWhenServerReportsNotOk() { @@ -42,6 +44,7 @@ public sealed class GalaxyRepositoryClientTests /// /// Verifies that GetLastDeployTimeAsync returns null when the server reports not present. /// + /// A task that represents the asynchronous operation. [Fact] public async Task GetLastDeployTimeAsync_ReturnsNullWhenNotPresent() { @@ -58,6 +61,7 @@ public sealed class GalaxyRepositoryClientTests /// /// Verifies that GetLastDeployTimeAsync returns the timestamp when the server reports it present. /// + /// A task that represents the asynchronous operation. [Fact] public async Task GetLastDeployTimeAsync_ReturnsTimestampWhenPresent() { @@ -79,6 +83,7 @@ public sealed class GalaxyRepositoryClientTests /// /// Verifies that DiscoverHierarchyAsync returns the objects from the server reply. /// + /// A task that represents the asynchronous operation. [Fact] public async Task DiscoverHierarchyAsync_ReturnsObjectsFromReply() { @@ -141,6 +146,7 @@ public sealed class GalaxyRepositoryClientTests /// /// Verifies that DiscoverHierarchyAsync propagates cancellation tokens to the transport. /// + /// A task that represents the asynchronous operation. [Fact] public async Task DiscoverHierarchyAsync_PropagatesCancellationToTransport() { @@ -161,6 +167,7 @@ public sealed class GalaxyRepositoryClientTests /// /// Verifies that TestConnectionAsync retries on transient gRPC failures. /// + /// A task that represents the asynchronous operation. [Fact] public async Task DiscoverHierarchyAsync_WithRepeatedPageToken_ThrowsProtocolError() { @@ -184,6 +191,7 @@ public sealed class GalaxyRepositoryClientTests /// /// Verifies that DiscoverHierarchyAsync maps typed filter options correctly to the request. /// + /// A task that represents the asynchronous operation. [Fact] public async Task DiscoverHierarchyAsync_WithOptions_MapsTypedFilters() { @@ -218,6 +226,7 @@ public sealed class GalaxyRepositoryClientTests /// /// Verifies that TestConnectionAsync retries on transient gRPC failures. /// + /// A task that represents the asynchronous operation. [Fact] public async Task TestConnectionAsync_RetriesOnTransientGrpcFailure() { @@ -235,6 +244,7 @@ public sealed class GalaxyRepositoryClientTests /// /// Verifies that DiscoverHierarchyAsync retries on transient gRPC failures. /// + /// A task that represents the asynchronous operation. [Fact] public async Task DiscoverHierarchyAsync_RetriesOnTransientGrpcFailure() { @@ -251,6 +261,7 @@ public sealed class GalaxyRepositoryClientTests /// /// Verifies that WatchDeployEventsAsync delivers the bootstrap event. /// + /// A task that represents the asynchronous operation. [Fact] public async Task WatchDeployEventsAsync_DeliversBootstrapEvent() { @@ -287,6 +298,7 @@ public sealed class GalaxyRepositoryClientTests /// /// Verifies that WatchDeployEventsAsync delivers multiple events in order. /// + /// A task that represents the asynchronous operation. [Fact] public async Task WatchDeployEventsAsync_DeliversMultipleEventsInOrder() { @@ -325,6 +337,7 @@ public sealed class GalaxyRepositoryClientTests /// /// Verifies that WatchDeployEventsAsync stops iteration cleanly when cancelled. /// + /// A task that represents the asynchronous operation. [Fact] public async Task WatchDeployEventsAsync_CancellationStopsIterationCleanly() { @@ -369,6 +382,7 @@ public sealed class GalaxyRepositoryClientTests /// /// Verifies that WatchDeployEventsAsync throws ObjectDisposedException after the client is disposed. /// + /// A task that represents the asynchronous operation. [Fact] public async Task WatchDeployEventsAsync_ThrowsAfterDisposal() { @@ -384,6 +398,7 @@ public sealed class GalaxyRepositoryClientTests /// /// Verifies that TestConnectionAsync throws ObjectDisposedException after the client is disposed. /// + /// A task that represents the asynchronous operation. [Fact] public async Task TestConnectionAsync_ThrowsAfterDisposal() { diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/LazyBrowseNodeTests.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/LazyBrowseNodeTests.cs index 875c9fa..448debf 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/LazyBrowseNodeTests.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/LazyBrowseNodeTests.cs @@ -12,6 +12,7 @@ public sealed class LazyBrowseNodeTests /// Verifies that calling BrowseAsync with no parent returns the root nodes /// from the first BrowseChildren reply and surfaces the per-child has-children hint. /// + /// A task that represents the asynchronous operation. [Fact] public async Task Browse_NoParent_ReturnsRoots() { @@ -36,6 +37,7 @@ public sealed class LazyBrowseNodeTests /// /// Verifies that ExpandAsync populates Children and marks the node expanded after one RPC. /// + /// A task that represents the asynchronous operation. [Fact] public async Task Expand_PopulatesChildrenAndMarksExpanded() { @@ -62,6 +64,7 @@ public sealed class LazyBrowseNodeTests /// /// Verifies that a second ExpandAsync call is a no-op and issues no additional RPC. /// + /// A task that represents the asynchronous operation. [Fact] public async Task Expand_CalledTwice_NoSecondRpc() { @@ -86,6 +89,7 @@ public sealed class LazyBrowseNodeTests /// /// Verifies that an RPC failure (NotFound) during expand is wrapped in MxGatewayException. /// + /// A task that represents the asynchronous operation. [Fact] public async Task Expand_UnknownParent_ThrowsMxGatewayException() { @@ -113,6 +117,7 @@ public sealed class LazyBrowseNodeTests /// /// Verifies that ExpandAsync drains multi-page sibling replies and forwards the page token. /// + /// A task that represents the asynchronous operation. [Fact] public async Task Expand_MultiPageSiblings_GathersAllPages() { @@ -147,6 +152,7 @@ public sealed class LazyBrowseNodeTests /// /// Verifies that ten concurrent ExpandAsync calls issue exactly one RPC, not ten. /// + /// A task that represents the asynchronous operation. [Fact] public async Task Expand_CalledConcurrently_OnlyFiresOneRpc() { @@ -179,8 +185,9 @@ public sealed class LazyBrowseNodeTests /// Verifies that reading Children/IsExpanded concurrently with an in-flight ExpandAsync /// never throws (no torn enumeration of a mid-append list) and, once IsExpanded flips to /// true, the published Children snapshot is fully populated. Pins the safe-publication - /// contract on the lock-free readers (Client.Dotnet-025). + /// contract on the lock-free readers. /// + /// A task that represents the asynchronous operation. [Fact] public async Task Expand_ConcurrentReadOfChildren_NeverTearsAndPublishesAtomically() { @@ -268,6 +275,7 @@ public sealed class LazyBrowseNodeTests /// /// Verifies that BrowseChildrenOptions filter fields are forwarded to the BrowseChildren request. /// + /// A task that represents the asynchronous operation. [Fact] public async Task Browse_WithFilter_ForwardsToRequest() { diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayClientAlarmsTests.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayClientAlarmsTests.cs index b178944..61582b6 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayClientAlarmsTests.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayClientAlarmsTests.cs @@ -12,6 +12,7 @@ namespace ZB.MOM.WW.MxGateway.Client.Tests; public sealed class MxGatewayClientAlarmsTests { /// AcknowledgeAlarmAsync records request and returns reply. + /// A task that represents the asynchronous operation. [Fact] public async Task AcknowledgeAlarmAsync_RecordsRequestShapeAndReturnsReply() { @@ -48,6 +49,7 @@ public sealed class MxGatewayClientAlarmsTests } /// AcknowledgeAlarmAsync honors cancellation. + /// A task that represents the asynchronous operation. [Fact] public async Task AcknowledgeAlarmAsync_HonorsCancellation() { @@ -72,6 +74,7 @@ public sealed class MxGatewayClientAlarmsTests } /// AcknowledgeAlarmAsync maps unauthenticated RPC exception to typed exception. + /// A task that represents the asynchronous operation. [Fact] public async Task AcknowledgeAlarmAsync_MapsUnauthenticated_RpcException_ToTypedException() { @@ -97,6 +100,7 @@ public sealed class MxGatewayClientAlarmsTests } /// QueryActiveAlarmsAsync streams enqueued snapshots. + /// A task that represents the asynchronous operation. [Fact] public async Task QueryActiveAlarmsAsync_StreamsEnqueuedSnapshots() { @@ -122,6 +126,7 @@ public sealed class MxGatewayClientAlarmsTests } /// QueryActiveAlarmsAsync passes filter prefix. + /// A task that represents the asynchronous operation. [Fact] public async Task QueryActiveAlarmsAsync_PassesFilterPrefix() { @@ -142,6 +147,7 @@ public sealed class MxGatewayClientAlarmsTests } /// QueryActiveAlarmsAsync honors cancellation during enumeration. + /// A task that represents the asynchronous operation. [Fact] public async Task QueryActiveAlarmsAsync_HonorsCancellationDuringEnumeration() { diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayClientCliTests.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayClientCliTests.cs index bd39120..27f6fe8 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayClientCliTests.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayClientCliTests.cs @@ -24,6 +24,7 @@ public sealed class MxGatewayClientCliTests } /// Verifies that the version command with --json flag prints JSON protocol versions. + /// A task that represents the asynchronous operation. [Fact] public async Task RunAsync_VersionJson_PrintsJsonProtocolVersions() { @@ -38,6 +39,7 @@ public sealed class MxGatewayClientCliTests } /// Verifies that the write command builds a write request and prints JSON reply. + /// A task that represents the asynchronous operation. [Fact] public async Task RunAsync_Write_BuildsWriteCommandAndPrintsJsonReply() { @@ -83,7 +85,7 @@ public sealed class MxGatewayClientCliTests } /// - /// Client.Dotnet-030: advise-supervisory was present in the command + /// advise-supervisory was present in the command /// dispatch table but absent from 's /// IsKnownGatewayCommand guard, so the guard intercepted it first and /// returned exit code 2 "Unknown command" before dispatch could run. This @@ -91,6 +93,7 @@ public sealed class MxGatewayClientCliTests /// "Unknown command") and reaches the dispatch handler (exit 0, reply written /// to stdout). /// + /// A task that represents the asynchronous operation. [Fact] public async Task RunAsync_AdviseSupervisory_IsRecognizedAndReachesDispatch() { @@ -130,6 +133,7 @@ public sealed class MxGatewayClientCliTests } /// Verifies that error output redacts sensitive API key values. + /// A task that represents the asynchronous operation. [Fact] public async Task RunAsync_ErrorOutput_RedactsApiKey() { @@ -154,12 +158,13 @@ public sealed class MxGatewayClientCliTests } /// - /// Client.Dotnet-028: when the API key is sourced from the env var + /// When the API key is sourced from the env var /// (--api-key-env path, no --api-key flag), the error-redaction /// catch block must still resolve and redact the effective key. Regression /// guard for the catch block reverting to GetOptional("api-key") only, /// which is null on the env-var path and leaves the key unredacted. /// + /// A task that represents the asynchronous operation. [Fact] public async Task RunAsync_ErrorOutput_RedactsApiKey_WhenSourcedFromEnvironmentVariable() { @@ -196,6 +201,7 @@ public sealed class MxGatewayClientCliTests } /// Verifies that stream-events with max-events limit stops output in non-JSON format. + /// A task that represents the asynchronous operation. [Fact] public async Task RunAsync_StreamEvents_WithMaxEventsStopsNonJsonOutput() { @@ -238,6 +244,7 @@ public sealed class MxGatewayClientCliTests /// Verifies that stream-alarms with --max-events stops output and distinguishes payload cases. + /// A task that represents the asynchronous operation. [Fact] public async Task RunAsync_StreamAlarms_WithMaxEventsStopsAndDistinguishesPayloadCases() { @@ -277,6 +284,7 @@ public sealed class MxGatewayClientCliTests } /// Verifies that acknowledge-alarm builds a request and prints the JSON reply. + /// A task that represents the asynchronous operation. [Fact] public async Task RunAsync_AcknowledgeAlarm_BuildsRequestAndPrintsJsonReply() { @@ -319,6 +327,7 @@ public sealed class MxGatewayClientCliTests } /// Verifies that smoke command closes opened session when a command fails. + /// A task that represents the asynchronous operation. [Fact] public async Task RunAsync_Smoke_WhenCommandFails_ClosesOpenedSession() { @@ -350,6 +359,7 @@ public sealed class MxGatewayClientCliTests } /// Verifies that galaxy-test-connection command prints JSON reply. + /// A task that represents the asynchronous operation. [Fact] public async Task RunAsync_GalaxyTestConnection_PrintsJsonReply() { @@ -380,6 +390,7 @@ public sealed class MxGatewayClientCliTests } /// Verifies that galaxy-discover command prints hierarchy summary. + /// A task that represents the asynchronous operation. [Fact] public async Task RunAsync_GalaxyDiscover_PrintsHierarchySummary() { @@ -453,6 +464,7 @@ public sealed class MxGatewayClientCliTests /// Verifies galaxy-browse walks root objects and eagerly expands one further /// level when --depth 1 is passed, printing an indented tree. /// + /// A task that represents the asynchronous operation. [Fact] public async Task RunAsync_GalaxyBrowse_TextTreeExpandsToDepth() { @@ -509,6 +521,7 @@ public sealed class MxGatewayClientCliTests /// Verifies galaxy-browse --json emits a nested JSON document and forwards /// the filter flags onto the BrowseChildren request. /// + /// A task that represents the asynchronous operation. [Fact] public async Task RunAsync_GalaxyBrowse_JsonForwardsFilters() { @@ -553,6 +566,7 @@ public sealed class MxGatewayClientCliTests /// Verifies galaxy-browse --parent fetches exactly one level of children for /// the supplied gobject id via a parent-scoped BrowseChildren request. /// + /// A task that represents the asynchronous operation. [Fact] public async Task RunAsync_GalaxyBrowse_ParentFetchesSingleLevel() { @@ -590,6 +604,7 @@ public sealed class MxGatewayClientCliTests } /// Verifies that galaxy-watch command prints text output for deploy events. + /// A task that represents the asynchronous operation. [Fact] public async Task RunAsync_GalaxyWatch_PrintsTextOutputForEvents() { @@ -644,6 +659,7 @@ public sealed class MxGatewayClientCliTests } /// Verifies that galaxy-watch with --json emits one JSON object per event. + /// A task that represents the asynchronous operation. [Fact] public async Task RunAsync_GalaxyWatch_JsonEmitsOneObjectPerEvent() { @@ -679,6 +695,7 @@ public sealed class MxGatewayClientCliTests } /// Verifies that batch mode dispatches a single version command and emits the EOR sentinel. + /// A task that represents the asynchronous operation. [Fact] public async Task RunAsync_Batch_DispatchesVersionAndWritesEndOfRecord() { @@ -705,6 +722,7 @@ public sealed class MxGatewayClientCliTests } /// Verifies that batch mode routes per-command errors to stdout as JSON between EOR markers. + /// A task that represents the asynchronous operation. [Fact] public async Task RunAsync_Batch_WritesErrorsToStdoutAsJson() { @@ -739,7 +757,7 @@ public sealed class MxGatewayClientCliTests } /// - /// Client.Dotnet-018: the README CLI examples for the alarm subcommands at + /// The README CLI examples for the alarm subcommands at /// `clients/dotnet/README.md` must drive cleanly through the production /// CLI argument parser. The previous text used non-existent flags /// (`--session-id`, `--max-messages`, `--alarm-reference`) that would @@ -749,6 +767,7 @@ public sealed class MxGatewayClientCliTests /// against exit code 0. /// /// The alarm subcommand to validate (e.g. "stream-alarms", "acknowledge-alarm"). + /// A task that represents the asynchronous operation. [Theory] [InlineData("stream-alarms")] [InlineData("acknowledge-alarm")] @@ -797,12 +816,13 @@ public sealed class MxGatewayClientCliTests } /// - /// Client.Dotnet-019: `BenchReadBulkAsync` previously fell back to + /// `BenchReadBulkAsync` previously fell back to /// reply.ReturnValue.Int32Value when the register reply had no /// typed Register payload, silently driving the rest of the bench /// against a zero server handle. The fix must fail loudly with a /// descriptive . /// + /// A task that represents the asynchronous operation. [Fact] public async Task RunAsync_BenchReadBulk_WhenRegisterReplyMissingTypedPayload_FailsLoudly() { @@ -810,9 +830,6 @@ public sealed class MxGatewayClientCliTests using var error = new StringWriter(); FakeCliClient fakeClient = new(); // Successful protocol + MX status but no typed `Register` payload. - // Before the Client.Dotnet-019 fix this silently became serverHandle=0 - // and the bench proceeded through SubscribeBulk / warmup / steady-state - // against an invalid handle, producing a misleading zero-result summary. fakeClient.InvokeReplies.Enqueue(new MxCommandReply { SessionId = "session-fixture", @@ -847,12 +864,13 @@ public sealed class MxGatewayClientCliTests } /// - /// Client.Dotnet-020: the steady-state loop in `BenchReadBulkAsync` had a + /// The steady-state loop in `BenchReadBulkAsync` had a /// bare `catch { failedCalls++; continue; }` that swallowed /// , so token-driven cancellation /// kept spinning until --duration-seconds elapsed. After the fix /// the bench must exit promptly when the supplied token cancels. /// + /// A task that represents the asynchronous operation. [Fact] public async Task RunAsync_BenchReadBulk_WhenSteadyStateLoopReceivesCancellation_ExitsPromptly() { @@ -941,12 +959,13 @@ public sealed class MxGatewayClientCliTests } /// - /// Client.Dotnet-021: both `ReadBulkAsync` and `BenchReadBulkAsync` cast + /// Both `ReadBulkAsync` and `BenchReadBulkAsync` cast /// the user-supplied --timeout-ms to without /// bounds checking, so a negative value (e.g. -1) silently wraps /// to ~49.7 days. The fix must reject negatives with a clear error. /// /// The bulk-read subcommand to validate (e.g. "read-bulk", "bench-read-bulk"). + /// A task that represents the asynchronous operation. [Theory] [InlineData("read-bulk")] [InlineData("bench-read-bulk")] @@ -1109,7 +1128,8 @@ public sealed class MxGatewayClientCliTests /// Optional per-call handler that overrides queue-based behaviour. public Func>? InvokeHandler { get; init; } - /// + /// Disposes the fake client. No-op; there are no unmanaged resources to release. + /// A task that represents the asynchronous operation. public ValueTask DisposeAsync() { return ValueTask.CompletedTask; diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayClientSessionTests.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayClientSessionTests.cs index d827be1..2d135c6 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayClientSessionTests.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayClientSessionTests.cs @@ -7,6 +7,7 @@ namespace ZB.MOM.WW.MxGateway.Client.Tests; public sealed class MxGatewayClientSessionTests { /// Verifies that open session attaches API key metadata and cancellation token. + /// A task that represents the asynchronous operation. [Fact] public async Task OpenSessionRawAsync_AttachesApiKeyMetadataAndCancellation() { @@ -22,6 +23,7 @@ public sealed class MxGatewayClientSessionTests } /// Verifies that open session returns a session with the raw open reply. + /// A task that represents the asynchronous operation. [Fact] public async Task OpenSessionAsync_ReturnsSessionWithRawOpenReply() { @@ -37,6 +39,7 @@ public sealed class MxGatewayClientSessionTests } /// Verifies that register builds a register command and returns server handle. + /// A task that represents the asynchronous operation. [Fact] public async Task RegisterAsync_BuildsRegisterCommandAndReturnsServerHandle() { @@ -62,6 +65,7 @@ public sealed class MxGatewayClientSessionTests } /// Verifies that add item 2 builds a command with the specified context. + /// A task that represents the asynchronous operation. [Fact] public async Task AddItem2Async_BuildsAddItem2CommandWithContext() { @@ -87,6 +91,7 @@ public sealed class MxGatewayClientSessionTests } /// Verifies that write raw builds a write command with the raw value. + /// A task that represents the asynchronous operation. [Fact] public async Task WriteRawAsync_BuildsWriteCommandWithRawValue() { @@ -118,6 +123,7 @@ public sealed class MxGatewayClientSessionTests } /// Verifies that write 2 raw builds a write 2 command with value and timestamp. + /// A task that represents the asynchronous operation. [Fact] public async Task Write2RawAsync_BuildsWrite2CommandWithValueAndTimestamp() { @@ -146,6 +152,7 @@ public sealed class MxGatewayClientSessionTests } /// Verifies that subscribe bulk builds one command and returns per-item results. + /// A task that represents the asynchronous operation. [Fact] public async Task SubscribeBulkAsync_BuildsOneBulkCommandAndReturnsPerItemResults() { @@ -185,6 +192,7 @@ public sealed class MxGatewayClientSessionTests } /// Verifies that stream events yields events in the order received from the gateway. + /// A task that represents the asynchronous operation. [Fact] public async Task StreamEventsAsync_YieldsEventsInGatewayOrder() { @@ -216,6 +224,7 @@ public sealed class MxGatewayClientSessionTests } /// Verifies that close is explicit and idempotent. + /// A task that represents the asynchronous operation. [Fact] public async Task CloseAsync_IsExplicitAndIdempotent() { @@ -232,6 +241,7 @@ public sealed class MxGatewayClientSessionTests } /// Verifies that invoke retries safe diagnostic commands on transient RPC failure. + /// A task that represents the asynchronous operation. [Fact] public async Task InvokeAsync_RetriesSafeDiagnosticCommandOnTransientGrpcFailure() { @@ -256,6 +266,7 @@ public sealed class MxGatewayClientSessionTests } /// Verifies that open session does not retry on transient RPC failure. + /// A task that represents the asynchronous operation. [Fact] public async Task OpenSessionAsync_DoesNotRetryTransientGrpcFailure() { @@ -269,6 +280,7 @@ public sealed class MxGatewayClientSessionTests } /// Verifies that invoke does not retry write commands on transient RPC failure. + /// A task that represents the asynchronous operation. [Fact] public async Task InvokeAsync_DoesNotRetryWriteCommand() { @@ -284,6 +296,7 @@ public sealed class MxGatewayClientSessionTests } /// Verifies that invoke helpers pass cancellation token to the transport. + /// A task that represents the asynchronous operation. [Fact] public async Task InvokeHelpers_PassCancellationTokenToTransport() { @@ -330,6 +343,7 @@ public sealed class MxGatewayClientSessionTests } /// Verifies that WriteArrayElementsAsync builds a write command whose value is a SparseArrayValue. + /// A task that represents the asynchronous operation. [Fact] public async Task WriteArrayElementsAsync_BuildsWriteCommandWithSparseArrayValue() { diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayGeneratedContractTests.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayGeneratedContractTests.cs index 95fdd89..7c1593a 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayGeneratedContractTests.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayGeneratedContractTests.cs @@ -3,6 +3,7 @@ namespace ZB.MOM.WW.MxGateway.Client.Tests; public sealed class MxGatewayGeneratedContractTests { /// Verifies that the generated gRPC client can be instantiated from the client factory. + /// A task that represents the asynchronous operation. [Fact] public async Task GeneratedGrpcClient_CanBeConstructedFromClientFactory() { diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/GalaxyRepositoryClient.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/GalaxyRepositoryClient.cs index 7d05638..ee19b21 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/GalaxyRepositoryClient.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/GalaxyRepositoryClient.cs @@ -337,6 +337,9 @@ public sealed class GalaxyRepositoryClient : IAsyncDisposable cancellationToken); } + /// Builds a BrowseChildren request from the caller-supplied filter options. + /// The browse-children filter options. + /// The populated request. internal static BrowseChildrenRequest BuildBrowseChildrenRequest(BrowseChildrenOptions options) { ArgumentNullException.ThrowIfNull(options); @@ -424,6 +427,7 @@ public sealed class GalaxyRepositoryClient : IAsyncDisposable /// /// Closes the gRPC channel and releases resources. /// + /// A task that represents the asynchronous operation. public ValueTask DisposeAsync() { if (_disposed) @@ -493,6 +497,9 @@ public sealed class GalaxyRepositoryClient : IAsyncDisposable private static HttpMessageHandler CreateHttpHandler(MxGatewayClientOptions options) => CreateHttpHandlerForTests(options); + /// Creates the used for gRPC calls, exposed for test configuration. + /// The client options supplying TLS and timeout settings. + /// The configured HTTP handler. internal static SocketsHttpHandler CreateHttpHandlerForTests(MxGatewayClientOptions options) { SocketsHttpHandler handler = new() diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/GrpcGalaxyRepositoryClientTransport.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/GrpcGalaxyRepositoryClientTransport.cs index e51337e..e533db2 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/GrpcGalaxyRepositoryClientTransport.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/GrpcGalaxyRepositoryClientTransport.cs @@ -10,9 +10,7 @@ internal sealed class GrpcGalaxyRepositoryClientTransport( MxGatewayClientOptions options, GalaxyRepository.GalaxyRepositoryClient rawClient) : IGalaxyRepositoryClientTransport { - /// - /// Gets the gateway client options. - /// + /// public MxGatewayClientOptions Options { get; } = options; /// @@ -91,7 +89,16 @@ internal sealed class GrpcGalaxyRepositoryClientTransport( } } - /// + /// + /// Watches for deployment events from the Galaxy Repository server. + /// + /// The watch deploy events request. + /// gRPC call options (timeout, cancellation, etc.). + /// + /// A cancellation token distinct from 's; when cancelable, it + /// takes precedence over the token carried by . + /// + /// An asynchronous stream of deploy events. public async IAsyncEnumerable WatchDeployEventsAsync( WatchDeployEventsRequest request, CallOptions callOptions, diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/GrpcMxGatewayClientTransport.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/GrpcMxGatewayClientTransport.cs index 5ff4e75..2198041 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/GrpcMxGatewayClientTransport.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/GrpcMxGatewayClientTransport.cs @@ -10,9 +10,7 @@ internal sealed class GrpcMxGatewayClientTransport( MxGatewayClientOptions options, MxAccessGateway.MxAccessGatewayClient rawClient) : IMxGatewayClientTransport { - /// - /// Gets the gateway client options. - /// + /// public MxGatewayClientOptions Options { get; } = options; /// @@ -74,7 +72,13 @@ internal sealed class GrpcMxGatewayClientTransport( } } - /// + /// + /// Streams events from the session. + /// + /// The stream events request. + /// gRPC call options. + /// Cancellation token; falls back to 's token when not cancelable. + /// An async enumerable of events. public async IAsyncEnumerable StreamEventsAsync( StreamEventsRequest request, CallOptions callOptions, @@ -133,7 +137,14 @@ internal sealed class GrpcMxGatewayClientTransport( } } - /// + /// + /// Streams a snapshot of all alarms currently in Active or ActiveAcked state — the + /// ConditionRefresh equivalent for the gateway. + /// + /// The query request, optionally scoped by alarm-reference prefix. + /// gRPC call options. + /// Cancellation token; falls back to 's token when not cancelable. + /// An async enumerable of active-alarm snapshots. public async IAsyncEnumerable QueryActiveAlarmsAsync( QueryActiveAlarmsRequest request, CallOptions callOptions, @@ -175,7 +186,14 @@ internal sealed class GrpcMxGatewayClientTransport( return QueryActiveAlarmsAsync(request, callOptions); } - /// + /// + /// Attaches to the gateway's central alarm feed — the current active-alarm + /// snapshot followed by live transitions. + /// + /// The stream request, optionally scoped by alarm-reference prefix. + /// gRPC call options. + /// Cancellation token; falls back to 's token when not cancelable. + /// An async enumerable of alarm feed messages. public async IAsyncEnumerable StreamAlarmsAsync( StreamAlarmsRequest request, CallOptions callOptions, diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/IGalaxyRepositoryClientTransport.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/IGalaxyRepositoryClientTransport.cs index ddc5dcc..c5454b7 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/IGalaxyRepositoryClientTransport.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/IGalaxyRepositoryClientTransport.cs @@ -15,6 +15,7 @@ internal interface IGalaxyRepositoryClientTransport /// Tests the connection to the Galaxy Repository server. /// The test connection request. /// gRPC call options (timeout, cancellation, etc.). + /// The test connection reply. Task TestConnectionAsync( TestConnectionRequest request, CallOptions callOptions); @@ -22,6 +23,7 @@ internal interface IGalaxyRepositoryClientTransport /// Gets the last deploy time from the Galaxy Repository server. /// The get last deploy time request. /// gRPC call options (timeout, cancellation, etc.). + /// The last deploy time reply. Task GetLastDeployTimeAsync( GetLastDeployTimeRequest request, CallOptions callOptions); @@ -29,6 +31,7 @@ internal interface IGalaxyRepositoryClientTransport /// Discovers the object hierarchy in the Galaxy Repository. /// The discover hierarchy request. /// gRPC call options (timeout, cancellation, etc.). + /// The discovered hierarchy reply. Task DiscoverHierarchyAsync( DiscoverHierarchyRequest request, CallOptions callOptions); @@ -36,6 +39,7 @@ internal interface IGalaxyRepositoryClientTransport /// Returns direct children of a parent in the Galaxy hierarchy. /// The browse children request. /// gRPC call options (timeout, cancellation, etc.). + /// The browse children reply. Task BrowseChildrenAsync( BrowseChildrenRequest request, CallOptions callOptions); @@ -43,6 +47,7 @@ internal interface IGalaxyRepositoryClientTransport /// Watches for deployment events from the Galaxy Repository server. /// The watch deploy events request. /// gRPC call options (timeout, cancellation, etc.). + /// An asynchronous stream of deploy events. IAsyncEnumerable WatchDeployEventsAsync( WatchDeployEventsRequest request, CallOptions callOptions); diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/LazyBrowseNode.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/LazyBrowseNode.cs index 777e13b..7b3aa33 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/LazyBrowseNode.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/LazyBrowseNode.cs @@ -12,7 +12,7 @@ public sealed class LazyBrowseNode { private readonly GalaxyRepositoryClient _client; private readonly BrowseChildrenOptions _options; - // Client.Dotnet-027 (Won't Fix): this gate is used only via WaitAsync/Release and + // Won't Fix: this gate is used only via WaitAsync/Release and // never via AvailableWaitHandle, so SemaphoreSlim allocates no kernel wait handle — // it holds no unmanaged/OS handle to leak. It is pure managed memory whose lifetime // is the node's, so the type is intentionally not IDisposable: making it disposable @@ -27,6 +27,11 @@ public sealed class LazyBrowseNode private IReadOnlyList _children = []; private volatile bool _isExpanded; + /// Initializes a new instance of the class. + /// The Galaxy Repository client used to fetch children on expansion. + /// The underlying Galaxy object for this node. + /// Whether the server reports this node has at least one matching descendant. + /// The browse options to apply when fetching children. internal LazyBrowseNode( GalaxyRepositoryClient client, GalaxyObject @object, @@ -66,6 +71,7 @@ public sealed class LazyBrowseNode /// partially-built list. /// /// Token to observe for cancellation. + /// A task that represents the asynchronous operation. public async Task ExpandAsync(CancellationToken cancellationToken = default) { if (_isExpanded) diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxCommandReplyExtensions.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxCommandReplyExtensions.cs index ef111df..5715904 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxCommandReplyExtensions.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxCommandReplyExtensions.cs @@ -7,6 +7,7 @@ public static class MxCommandReplyExtensions { /// Validates that the reply has a successful protocol status (Ok or MxAccessFailure), throwing a gateway exception if not. /// The command reply to check. + /// The same reply, for chaining. public static MxCommandReply EnsureProtocolSuccess(this MxCommandReply reply) { ArgumentNullException.ThrowIfNull(reply); @@ -24,6 +25,7 @@ public static class MxCommandReplyExtensions /// Validates that the reply indicates MXAccess success (no HResult or status failures), throwing MxAccessException if not. /// The command reply to check. + /// The same reply, for chaining. public static MxCommandReply EnsureMxAccessSuccess(this MxCommandReply reply) { ArgumentNullException.ThrowIfNull(reply); diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewayClient.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewayClient.cs index 6d9ab58..ed321b3 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewayClient.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewayClient.cs @@ -249,6 +249,7 @@ public sealed class MxGatewayClient : IAsyncDisposable /// /// Disposes the client and releases all resources. /// + /// A task that represents the asynchronous operation. public ValueTask DisposeAsync() { if (_disposed) @@ -318,6 +319,11 @@ public sealed class MxGatewayClient : IAsyncDisposable private static HttpMessageHandler CreateHttpHandler(MxGatewayClientOptions options) => CreateHttpHandlerForTests(options); + /// + /// Creates the used for gRPC calls; exposed internally so tests can inspect it. + /// + /// The client options that configure connect timeout and TLS behavior. + /// A configured . internal static SocketsHttpHandler CreateHttpHandlerForTests(MxGatewayClientOptions options) { SocketsHttpHandler handler = new() diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewayClientRetryPolicy.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewayClientRetryPolicy.cs index ff4ff7a..4d01bbb 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewayClientRetryPolicy.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewayClientRetryPolicy.cs @@ -12,6 +12,7 @@ internal static class MxGatewayClientRetryPolicy /// Creates a Polly ResiliencePipeline that retries transient gRPC failures with exponential backoff. /// Retry configuration (max attempts, delay bounds, jitter). /// Optional logger for retry diagnostics. + /// A configured resilience pipeline that retries transient gRPC failures. public static ResiliencePipeline Create( MxGatewayClientRetryOptions options, ILogger? logger) @@ -42,6 +43,7 @@ internal static class MxGatewayClientRetryPolicy /// Returns whether a command kind is eligible for automatic retry on transient failures. /// The command kind to check. + /// if the command kind may be automatically retried; otherwise, . public static bool IsRetryableCommand(MxCommandKind kind) { return kind is MxCommandKind.Ping diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewaySession.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewaySession.cs index e0c6f24..94671f8 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewaySession.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewaySession.cs @@ -211,6 +211,7 @@ public sealed class MxGatewaySession : IAsyncDisposable /// The ServerHandle from register. /// The ItemHandle from add-item. /// Cancellation token. + /// A task that represents the asynchronous operation. public async Task AdviseAsync( int serverHandle, int itemHandle, @@ -252,6 +253,7 @@ public sealed class MxGatewaySession : IAsyncDisposable /// The ServerHandle from register. /// The ItemHandle from add-item. /// Cancellation token. + /// A task that represents the asynchronous operation. public async Task UnAdviseAsync( int serverHandle, int itemHandle, @@ -293,6 +295,7 @@ public sealed class MxGatewaySession : IAsyncDisposable /// The ServerHandle from register. /// The ItemHandle from add-item. /// Cancellation token. + /// A task that represents the asynchronous operation. public async Task RemoveItemAsync( int serverHandle, int itemHandle, @@ -675,6 +678,7 @@ public sealed class MxGatewaySession : IAsyncDisposable /// The value to write. /// User ID context for the write. /// Cancellation token. + /// A task that represents the asynchronous operation. public async Task WriteAsync( int serverHandle, int itemHandle, @@ -704,6 +708,7 @@ public sealed class MxGatewaySession : IAsyncDisposable /// Map of zero-based array index to scalar . /// User ID context for the write. /// Cancellation token. + /// A task that represents the asynchronous operation. public Task WriteArrayElementsAsync( int serverHandle, int itemHandle, @@ -786,6 +791,7 @@ public sealed class MxGatewaySession : IAsyncDisposable /// The timestamp to write with the value. /// User ID context for the write. /// Cancellation token. + /// A task that represents the asynchronous operation. public async Task Write2Async( int serverHandle, int itemHandle, @@ -878,6 +884,7 @@ public sealed class MxGatewaySession : IAsyncDisposable /// /// Closes the session and releases resources. /// + /// A task that represents the asynchronous operation. public async ValueTask DisposeAsync() { await CloseAsync().ConfigureAwait(false); diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxStatusProxyExtensions.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxStatusProxyExtensions.cs index 5441431..318ad8a 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxStatusProxyExtensions.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxStatusProxyExtensions.cs @@ -7,6 +7,7 @@ public static class MxStatusProxyExtensions { /// Returns whether the status indicates success (success flag set and category is Ok). /// The status to check. + /// if the status indicates success; otherwise . public static bool IsSuccess(this MxStatusProxy status) { ArgumentNullException.ThrowIfNull(status); @@ -17,6 +18,7 @@ public static class MxStatusProxyExtensions /// Returns a formatted summary of the status for diagnostic output. /// The status to summarize. + /// A formatted diagnostic summary string. public static string ToDiagnosticSummary(this MxStatusProxy status) { ArgumentNullException.ThrowIfNull(status); diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxValueExtensions.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxValueExtensions.cs index 4f047bf..25f3caa 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxValueExtensions.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxValueExtensions.cs @@ -14,6 +14,7 @@ public static class MxValueExtensions /// Converts a boolean value to an MxValue with MxDataType.Boolean. /// /// Scalar boolean value to wrap. + /// An carrying the wrapped boolean. public static MxValue ToMxValue(this bool value) { return new MxValue @@ -28,6 +29,7 @@ public static class MxValueExtensions /// Converts a 32-bit integer value to an MxValue with MxDataType.Integer. /// /// 32-bit integer value to wrap. + /// An carrying the wrapped integer. public static MxValue ToMxValue(this int value) { return new MxValue @@ -42,6 +44,7 @@ public static class MxValueExtensions /// Converts a 64-bit integer value to an MxValue with MxDataType.Integer. /// /// 64-bit integer value to wrap. + /// An carrying the wrapped integer. public static MxValue ToMxValue(this long value) { return new MxValue @@ -56,6 +59,7 @@ public static class MxValueExtensions /// Converts a single-precision floating-point value to an MxValue with MxDataType.Float. /// /// Single-precision floating-point value to wrap. + /// An carrying the wrapped value. public static MxValue ToMxValue(this float value) { return new MxValue @@ -70,6 +74,7 @@ public static class MxValueExtensions /// Converts a double-precision floating-point value to an MxValue with MxDataType.Double. /// /// Double-precision floating-point value to wrap. + /// An carrying the wrapped value. public static MxValue ToMxValue(this double value) { return new MxValue @@ -84,6 +89,7 @@ public static class MxValueExtensions /// Converts a string value to an MxValue with MxDataType.String. /// /// String value to wrap. + /// An carrying the wrapped string. public static MxValue ToMxValue(this string value) { ArgumentNullException.ThrowIfNull(value); @@ -100,6 +106,7 @@ public static class MxValueExtensions /// Converts a DateTimeOffset value to an MxValue with MxDataType.Time. /// /// DateTimeOffset value to wrap. + /// An carrying the wrapped timestamp. public static MxValue ToMxValue(this DateTimeOffset value) { return new MxValue @@ -114,6 +121,7 @@ public static class MxValueExtensions /// Converts a DateTime value to an MxValue with MxDataType.Time. /// /// DateTime value to wrap. + /// An carrying the wrapped timestamp. public static MxValue ToMxValue(this DateTime value) { return new DateTimeOffset( @@ -127,6 +135,7 @@ public static class MxValueExtensions /// Converts a boolean array to an MxValue with MxDataType.Boolean. /// /// Array of boolean values to wrap. + /// An carrying the wrapped array. public static MxValue ToMxValue(this IReadOnlyList values) { ArgumentNullException.ThrowIfNull(values); @@ -145,6 +154,7 @@ public static class MxValueExtensions /// Converts a 32-bit integer array to an MxValue with MxDataType.Integer. /// /// Array of 32-bit integer values to wrap. + /// An carrying the wrapped array. public static MxValue ToMxValue(this IReadOnlyList values) { ArgumentNullException.ThrowIfNull(values); @@ -163,6 +173,7 @@ public static class MxValueExtensions /// Converts a 64-bit integer array to an MxValue with MxDataType.Integer. /// /// Array of 64-bit integer values to wrap. + /// An carrying the wrapped array. public static MxValue ToMxValue(this IReadOnlyList values) { ArgumentNullException.ThrowIfNull(values); @@ -181,6 +192,7 @@ public static class MxValueExtensions /// Converts a single-precision floating-point array to an MxValue with MxDataType.Float. /// /// Array of single-precision floating-point values to wrap. + /// An carrying the wrapped array. public static MxValue ToMxValue(this IReadOnlyList values) { ArgumentNullException.ThrowIfNull(values); @@ -199,6 +211,7 @@ public static class MxValueExtensions /// Converts a double-precision floating-point array to an MxValue with MxDataType.Double. /// /// Array of double-precision floating-point values to wrap. + /// An carrying the wrapped array. public static MxValue ToMxValue(this IReadOnlyList values) { ArgumentNullException.ThrowIfNull(values); @@ -217,6 +230,7 @@ public static class MxValueExtensions /// Converts a string array to an MxValue with MxDataType.String. /// /// Array of string values to wrap. + /// An carrying the wrapped array. public static MxValue ToMxValue(this IReadOnlyList values) { ArgumentNullException.ThrowIfNull(values); @@ -235,6 +249,7 @@ public static class MxValueExtensions /// Converts a DateTimeOffset array to an MxValue with MxDataType.Time. /// /// Array of DateTimeOffset values to wrap. + /// An carrying the wrapped array. public static MxValue ToMxValue(this IReadOnlyList values) { ArgumentNullException.ThrowIfNull(values); @@ -253,6 +268,7 @@ public static class MxValueExtensions /// Gets the projection kind (field name) of the given MxValue's current oneof value. /// /// The MxValue whose oneof projection kind is returned. + /// The field name of the value's current oneof projection (e.g. "boolValue"). public static string GetProjectionKind(this MxValue value) { ArgumentNullException.ThrowIfNull(value); @@ -276,6 +292,7 @@ public static class MxValueExtensions /// Converts an MxValue to a CLR object; returns the boxed value or null for null MxValues. /// /// The MxValue to convert. + /// The boxed CLR value, or if the MxValue is null. public static object? ToClrValue(this MxValue value) { ArgumentNullException.ThrowIfNull(value); @@ -299,6 +316,7 @@ public static class MxValueExtensions /// Converts an MxArray to a CLR array; returns null if the array does not have a known element type. /// /// The MxArray to convert. + /// The CLR array, or if the element type is not known. public static object? ToClrArrayValue(this MxArray array) { ArgumentNullException.ThrowIfNull(array); @@ -328,6 +346,7 @@ public static class MxValueExtensions /// Variant type string (e.g., "VT_BSTR"). /// Diagnostic string describing the raw value. /// Optional MXAccess data type override. + /// An with MxDataType.Unknown carrying the raw payload. public static MxValue ToRawMxValue( byte[] value, string variantType, diff --git a/src/ZB.MOM.WW.MxGateway.Server/Alarms/GatewayAlarmMonitor.cs b/src/ZB.MOM.WW.MxGateway.Server/Alarms/GatewayAlarmMonitor.cs index 1cd41b1..05714ea 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Alarms/GatewayAlarmMonitor.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Alarms/GatewayAlarmMonitor.cs @@ -228,7 +228,7 @@ public sealed class GatewayAlarmMonitor : BackgroundService, IGatewayAlarmServic // Consume mapped MxEvents through the session's single distributor pump (as an // internal, non-counted subscriber) rather than opening a second raw drain of the // worker event channel — a second drain would split events with the dashboard - // mirror pump and silently lose Acknowledge/mode-change transitions (GWC-01). + // mirror pump and silently lose Acknowledge/mode-change transitions. await foreach (MxEvent mxEvent in _sessionManager .ReadAlarmEventsAsync(session.SessionId, linked.Token) .ConfigureAwait(false)) diff --git a/src/ZB.MOM.WW.MxGateway.Server/Configuration/GatewayConfigurationServiceCollectionExtensions.cs b/src/ZB.MOM.WW.MxGateway.Server/Configuration/GatewayConfigurationServiceCollectionExtensions.cs index 863db3b..d2762ae 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Configuration/GatewayConfigurationServiceCollectionExtensions.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Configuration/GatewayConfigurationServiceCollectionExtensions.cs @@ -15,8 +15,8 @@ public static class GatewayConfigurationServiceCollectionExtensions public static IServiceCollection AddGatewayConfiguration( this IServiceCollection services, IConfiguration configuration) { - // GatewayOptionsValidator depends on IHostEnvironment to gate the production-only rules - // (SEC-04/06). The real host and the apikey CLI both build through WebApplicationBuilder, + // GatewayOptionsValidator depends on IHostEnvironment to gate the production-only rules. + // The real host and the apikey CLI both build through WebApplicationBuilder, // which registers IHostEnvironment before this call — so TryAdd is a no-op there. Minimal // containers (unit tests, tooling) have none; fall back to a non-production environment so // the validator still activates and the production guards stay off outside a real @@ -37,12 +37,16 @@ public static class GatewayConfigurationServiceCollectionExtensions /// private sealed class FallbackHostEnvironment : IHostEnvironment { + /// Gets or sets the name of the environment; defaults to . public string EnvironmentName { get; set; } = Environments.Development; + /// Gets or sets the name of the application. public string ApplicationName { get; set; } = typeof(FallbackHostEnvironment).Assembly.GetName().Name ?? "MxGateway"; + /// Gets or sets the absolute path to the content root directory. public string ContentRootPath { get; set; } = AppContext.BaseDirectory; + /// Gets or sets the file provider for the content root. public IFileProvider ContentRootFileProvider { get; set; } = new NullFileProvider(); } } diff --git a/src/ZB.MOM.WW.MxGateway.Server/Configuration/GatewayOptionsValidator.cs b/src/ZB.MOM.WW.MxGateway.Server/Configuration/GatewayOptionsValidator.cs index d271063..dcd60ba 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Configuration/GatewayOptionsValidator.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Configuration/GatewayOptionsValidator.cs @@ -470,7 +470,7 @@ public sealed class GatewayOptionsValidator : OptionsValidatorBase /// Security hot-path options bound from MxGateway:Security. Groups the API-key verification -/// cache and last_used write-coalescing knobs (SEC-08) with the login and API-key -/// rate-limiting knobs (SEC-11). Defaults preserve safe, low-overhead behaviour without requiring +/// cache and last_used write-coalescing knobs with the login and API-key +/// rate-limiting knobs. Defaults preserve safe, low-overhead behaviour without requiring /// operators to configure anything. /// public sealed class SecurityOptions diff --git a/src/ZB.MOM.WW.MxGateway.Server/Configuration/WorkerOptions.cs b/src/ZB.MOM.WW.MxGateway.Server/Configuration/WorkerOptions.cs index baf68ea..4a2b862 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Configuration/WorkerOptions.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Configuration/WorkerOptions.cs @@ -37,8 +37,8 @@ public sealed class WorkerOptions /// The maximum worker-frame (pipe) message size in bytes. Must stay at least /// above /// so a maximally-sized accepted gRPC payload - /// still fits one worker frame (IPC-03); the gateway conveys this value to the worker in the - /// handshake (GatewayHello.max_frame_bytes, IPC-02). Default is the 16 MB public gRPC cap + /// still fits one worker frame; the gateway conveys this value to the worker in the + /// handshake (GatewayHello.max_frame_bytes). Default is the 16 MB public gRPC cap /// plus that reserve. /// public int MaxMessageBytes { get; init; } = diff --git a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardApiKeyManagementService.cs b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardApiKeyManagementService.cs index 6215e83..3dd3447 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardApiKeyManagementService.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardApiKeyManagementService.cs @@ -101,8 +101,6 @@ public sealed class DashboardApiKeyManagementService( .RevokeKeyAsync(normalizedKeyId, RemoteAddress(), cancellationToken) .ConfigureAwait(false); - // SEC-08: drop any cached verification for this key in THIS gateway process so the revoke - // takes effect immediately rather than after the verification-cache TTL elapses. cacheInvalidator?.Invalidate(normalizedKeyId); await WriteDashboardAuditAsync( @@ -143,8 +141,6 @@ public sealed class DashboardApiKeyManagementService( .RotateKeyAsync(normalizedKeyId, RemoteAddress(), cancellationToken) .ConfigureAwait(false); - // SEC-08: the old secret's cached verification must not outlive the rotate — evict it so - // the superseded secret stops authenticating within this process immediately. cacheInvalidator?.Invalidate(normalizedKeyId); bool succeeded = rotated.Token is not null; @@ -191,8 +187,6 @@ public sealed class DashboardApiKeyManagementService( .DeleteAsync(normalizedKeyId, cancellationToken) .ConfigureAwait(false); - // SEC-08: a deleted key is only reachable after a prior revoke, but evict defensively so no - // cached verification survives the delete. cacheInvalidator?.Invalidate(normalizedKeyId); await WriteDashboardAuditAsync( diff --git a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardEndpointRouteBuilderExtensions.cs b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardEndpointRouteBuilderExtensions.cs index dea4e5f..ee9f91c 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardEndpointRouteBuilderExtensions.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardEndpointRouteBuilderExtensions.cs @@ -12,7 +12,7 @@ namespace ZB.MOM.WW.MxGateway.Server.Dashboard; public static class DashboardEndpointRouteBuilderExtensions { /// - /// The named rate-limiter policy applied to the POST /auth/login route (SEC-11). Registered + /// The named rate-limiter policy applied to the POST /auth/login route. Registered /// in GatewayApplication via . /// internal const string LoginRateLimiterPolicy = "dashboard-login"; @@ -40,8 +40,12 @@ public static class DashboardEndpointRouteBuilderExtensions }); } - // Test seam: a standalone partitioned limiter over the production partition logic, so a test can - // acquire leases and assert the (permit+1)th is rejected without an HTTP server. + /// + /// Test seam: builds a standalone partitioned limiter over the production partition logic, so a + /// test can acquire leases and assert the (permit+1)th is rejected without an HTTP server. + /// + /// The bound security options carrying the login-limit knobs. + /// A partitioned rate limiter keyed the same way as the production login policy. internal static PartitionedRateLimiter CreateLoginRateLimiter(SecurityOptions security) => PartitionedRateLimiter.Create( ctx => GetLoginRateLimitPartition(ctx, security)); @@ -76,7 +80,6 @@ public static class DashboardEndpointRouteBuilderExtensions (HttpContext httpContext, IAntiforgery antiforgery, IDashboardAuthenticator authenticator) => PostLoginAsync(httpContext, antiforgery, authenticator)) .AllowAnonymous() - // SEC-11: throttle credential stuffing before the LDAP bind is relayed to the directory. .RequireRateLimiting(LoginRateLimiterPolicy) .WithName("DashboardLoginPost"); diff --git a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/HubTokenService.cs b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/HubTokenService.cs index 168f818..bcc5842 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/HubTokenService.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/HubTokenService.cs @@ -33,7 +33,7 @@ public sealed class HubTokenService // bounds how long a stale role set survives a role change. Five minutes is transparent to // clients because DashboardHubConnectionFactory mints a fresh token on every (re)connect; // see docs/GatewayDashboardDesign.md. Heavier jti-denylist revocation is deliberately - // deferred until per-session hub ACLs land (SEC-25), when tokens gain session binding. + // deferred until per-session hub ACLs land, when tokens gain session binding. internal static readonly TimeSpan TokenLifetime = TimeSpan.FromMinutes(5); private readonly ITimeLimitedDataProtector _protector; diff --git a/src/ZB.MOM.WW.MxGateway.Server/GatewayApplication.cs b/src/ZB.MOM.WW.MxGateway.Server/GatewayApplication.cs index 131ef19..ea38dec 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/GatewayApplication.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/GatewayApplication.cs @@ -41,7 +41,7 @@ public static class GatewayApplication app.UseStaticFiles(); app.UseAuthentication(); app.UseAuthorization(); - // SEC-11: the rate limiter must run after routing has selected the endpoint (so its + // The rate limiter must run after routing has selected the endpoint (so its // RequireRateLimiting policy metadata is visible) and before the endpoint executes. app.UseRateLimiter(); app.UseAntiforgery(); @@ -105,7 +105,7 @@ public static class GatewayApplication return builder; } - // SEC-11: register the named fixed-window rate-limiter policy applied to POST /auth/login. The + // Registers the named fixed-window rate-limiter policy applied to POST /auth/login. The // limit knobs are read from MxGateway:Security at startup; the per-request partition is keyed on // the remote IP (see DashboardEndpointRouteBuilderExtensions.GetLoginRateLimitPartition). private static void AddLoginRateLimiter(WebApplicationBuilder builder) diff --git a/src/ZB.MOM.WW.MxGateway.Server/Grpc/EventStreamService.cs b/src/ZB.MOM.WW.MxGateway.Server/Grpc/EventStreamService.cs index 5afce48..9e0b284 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Grpc/EventStreamService.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Grpc/EventStreamService.cs @@ -14,43 +14,33 @@ public sealed class EventStreamService( GatewayMetrics metrics) : IEventStreamService { /// - /// - /// - /// This reads the subscriber's lease channel fed by the session's single - /// pump. The pump owns the single drain of - /// the worker event stream and the worker→public mapping (mirroring the former - /// ProduceEventsAsync); this loop is the per-subscriber boundary that - /// applies the per-RPC filter (AfterWorkerSequence), queue-depth metrics, - /// and the backpressure/overflow policy. - /// - /// - /// The dashboard mirror runs OFF this per-RPC loop. The dashboard is a - /// first-class internal subscriber on the session's - /// (see GatewaySession.StartDashboardMirror), - /// so it receives session events even when no gRPC client is streaming. This loop - /// does not mirror to the dashboard. One deliberate consequence: the dashboard sees - /// RAW session events, not the per-gRPC-subscriber AfterWorkerSequence-filtered - /// view this loop applies — the dashboard is a separate LDAP-authenticated monitoring - /// view that should see the session's full event activity. - /// - /// - /// Overflow handling: the distributor's per-subscriber channel is bounded - /// and the pump writes non-blocking. When this subscriber's channel is full the pump - /// applies the per-subscriber backpressure policy and completes this subscriber's - /// channel with a - /// (). That terminal fault - /// surfaces here when the reader's MoveNextAsync throws, and it propagates to - /// the gRPC client unchanged. The overflow metric, and (in the legacy - /// single-subscriber FailFast case) the session fault + fault metric, are recorded by - /// the distributor's overflow handler so the session, the pump, and other subscribers - /// are isolated from this subscriber's slowness. - /// - /// public async IAsyncEnumerable StreamEventsAsync( StreamEventsRequest request, string? callerKeyId, [EnumeratorCancellation] CancellationToken cancellationToken) { + // This reads the subscriber's lease channel fed by the session's single + // SessionEventDistributor pump. The pump owns the single drain of the worker event + // stream and the worker->public mapping (mirroring the former ProduceEventsAsync); this + // loop is the per-subscriber boundary that applies the per-RPC filter (AfterWorkerSequence), + // queue-depth metrics, and the backpressure/overflow policy. + // + // The dashboard mirror runs OFF this per-RPC loop. The dashboard is a first-class internal + // subscriber on the session's SessionEventDistributor (see GatewaySession.StartDashboardMirror), + // so it receives session events even when no gRPC client is streaming. This loop does not + // mirror to the dashboard. One deliberate consequence: the dashboard sees RAW session events, + // not the per-gRPC-subscriber AfterWorkerSequence-filtered view this loop applies — the + // dashboard is a separate LDAP-authenticated monitoring view that should see the session's + // full event activity. + // + // Overflow handling: the distributor's per-subscriber channel is bounded and the pump writes + // non-blocking. When this subscriber's channel is full the pump applies the per-subscriber + // backpressure policy and completes this subscriber's channel with a SessionManagerException + // (SessionManagerErrorCode.EventQueueOverflow). That terminal fault surfaces here when the + // reader's MoveNextAsync throws, and it propagates to the gRPC client unchanged. The overflow + // metric, and (in the legacy single-subscriber FailFast case) the session fault + fault metric, + // are recorded by the distributor's overflow handler so the session, the pump, and other + // subscribers are isolated from this subscriber's slowness. if (!sessionManager.TryGetSession(request.SessionId, out GatewaySession? session) || session is null) { throw new SessionManagerException( @@ -58,7 +48,7 @@ public sealed class EventStreamService( $"Session {request.SessionId} was not found."); } - // Owner-scoped attach (TST-02, security control): a session's event stream may be + // Owner-scoped attach (security control): a session's event stream may be // attached or reattached ONLY by the API key that opened the session. The detach-grace // and fan-out retention windows are on by default, so without this check any event-scoped // key that learns a session id could attach to another key's retained session and receive diff --git a/src/ZB.MOM.WW.MxGateway.Server/Grpc/MxAccessGrpcRequestValidator.cs b/src/ZB.MOM.WW.MxGateway.Server/Grpc/MxAccessGrpcRequestValidator.cs index c6bc0e6..5a53786 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Grpc/MxAccessGrpcRequestValidator.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Grpc/MxAccessGrpcRequestValidator.cs @@ -7,7 +7,7 @@ public sealed class MxAccessGrpcRequestValidator { // Upper bound on a single DrainEvents request. DrainEvents is a diagnostics RPC that returns // buffered events in one non-streaming reply, so an unbounded max_events could pack the whole - // queue into a session-killing frame (IPC-04). The worker independently caps each reply at its + // queue into a session-killing frame. The worker independently caps each reply at its // own MaxDrainEventsPerReply; this public bound rejects an obviously-abusive request loudly at // the boundary. max_events = 0 is allowed and means "the worker's default batch cap". private const uint MaxDrainEventsPerRequest = 10_000; diff --git a/src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/ApiKeyAdminCommandLineParser.cs b/src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/ApiKeyAdminCommandLineParser.cs index 6ec44da..041094f 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/ApiKeyAdminCommandLineParser.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/ApiKeyAdminCommandLineParser.cs @@ -236,7 +236,7 @@ public static class ApiKeyAdminCommandLineParser ReadHistorizedOnly: HasFlag(options, "read-historized-only")); } - // Parses the optional --expires value into an absolute UTC expiry (SEC-10). Accepts a relative + // Parses the optional --expires value into an absolute UTC expiry. Accepts a relative // "d"/"h" duration from now (operator-friendly) or an absolute ISO-8601 instant/date // (assumed UTC). Null/blank means no expiry — expiry stays opt-in, preserving prior behaviour. private static DateTimeOffset? ParseExpiry(string? value) diff --git a/src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/AuthStoreServiceCollectionExtensions.cs b/src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/AuthStoreServiceCollectionExtensions.cs index 5b3bc65..5c3d40a 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/AuthStoreServiceCollectionExtensions.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/AuthStoreServiceCollectionExtensions.cs @@ -69,7 +69,7 @@ public static class AuthStoreServiceCollectionExtensions // migrator and the migration hosted service. services.AddZbApiKeyAuth(effectiveConfig, AuthenticationSectionPath); - // SEC-08 hot-path decorators. Every gRPC call previously did a SQLite read plus a + // Hot-path decorators. Every gRPC call previously did a SQLite read plus a // last_used_utc WRITE via IApiKeyVerifier.VerifyAsync (the library verifier couples the // mark into verification). Two gateway-side decorators cut that cost without editing the // external library: @@ -138,7 +138,7 @@ public static class AuthStoreServiceCollectionExtensions /// /// Replaces the last registration of with a singleton that wraps /// it. The wrapped (inner) service is created once, preserving singleton semantics. Used to layer - /// the SEC-08 store decorator over the external library's registration without editing it. + /// the store decorator over the external library's registration without editing it. /// private static void DecorateSingleton( IServiceCollection services, diff --git a/src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/CachingApiKeyVerifier.cs b/src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/CachingApiKeyVerifier.cs index fe3ae52..ea8e446 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/CachingApiKeyVerifier.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/CachingApiKeyVerifier.cs @@ -30,7 +30,7 @@ public interface IApiKeyCacheInvalidator /// /// Only successful verifications are cached. Failures and unparseable headers always fall through /// to the inner verifier — caching a failure risks pinning a transiently-wrong negative, and the -/// SEC-11 per-peer failure counter (not this cache) is what bounds brute-force cost. +/// per-peer failure counter (not this cache) is what bounds brute-force cost. /// /// /// The cache key is the hex SHA-256 of the presented token (which embeds both the key id and the @@ -71,7 +71,11 @@ public sealed class CachingApiKeyVerifier : IApiKeyVerifier, IApiKeyCacheInvalid { } - // Test/explicit-TTL seam. + /// Initializes a new instance of the class with an explicit TTL. + /// The wrapped verifier (the library verifier) reached on a cache miss. + /// The shared memory cache. + /// The verification-cache TTL; a zero or negative value disables caching. + /// Test/explicit-TTL seam. internal CachingApiKeyVerifier(IApiKeyVerifier inner, IMemoryCache cache, TimeSpan ttl) { ArgumentNullException.ThrowIfNull(inner); @@ -81,7 +85,14 @@ public sealed class CachingApiKeyVerifier : IApiKeyVerifier, IApiKeyCacheInvalid _ttl = ttl; } - /// + /// + /// Verifies the given authorization header, returning a cached successful result when one is + /// still within TTL, or falling through to the inner verifier on a cache miss or non-cacheable + /// header. + /// + /// The raw Authorization header value to verify. + /// Token to cancel the asynchronous operation. + /// The verification result, cached or freshly computed. public async Task VerifyAsync(string authorizationHeader, CancellationToken ct) { if (_ttl <= TimeSpan.Zero || !TryComputeCacheKey(authorizationHeader, out string cacheKey)) diff --git a/src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/CoalescingMarkApiKeyStore.cs b/src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/CoalescingMarkApiKeyStore.cs index 9270530..cd5e1a3 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/CoalescingMarkApiKeyStore.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/CoalescingMarkApiKeyStore.cs @@ -41,7 +41,10 @@ public sealed class CoalescingMarkApiKeyStore : IApiKeyStore { } - // Test/explicit-window seam. + /// Initializes a new instance of the class with an explicit coalescing window (test/explicit-window seam). + /// The wrapped store. + /// The coalescing window; marks within this window of the last forwarded mark for a key are dropped. + /// The time provider. internal CoalescingMarkApiKeyStore(IApiKeyStore inner, TimeSpan window, TimeProvider clock) { ArgumentNullException.ThrowIfNull(inner); @@ -51,15 +54,25 @@ public sealed class CoalescingMarkApiKeyStore : IApiKeyStore _clock = clock; } - /// + /// Looks up an API key record by key id, delegating to the wrapped store unchanged. + /// The API key id to look up. + /// The cancellation token. + /// The matching , or if none exists. public Task FindByKeyIdAsync(string keyId, CancellationToken ct) => _inner.FindByKeyIdAsync(keyId, ct); - /// + /// Looks up an active API key record by key id, delegating to the wrapped store unchanged. + /// The API key id to look up. + /// The cancellation token. + /// The matching active , or if none exists or is inactive. public Task FindActiveByKeyIdAsync(string keyId, CancellationToken ct) => _inner.FindActiveByKeyIdAsync(keyId, ct); - /// + /// Marks the key as used, coalescing writes so at most one reaches the wrapped store per key per window. + /// The API key id being marked as used. + /// The UTC timestamp of the use. + /// The cancellation token. + /// A task that represents the asynchronous operation. public Task MarkUsedAsync(string keyId, DateTimeOffset whenUtc, CancellationToken ct) { ArgumentNullException.ThrowIfNull(keyId); diff --git a/src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/GatewayApiKeyIdentityMapper.cs b/src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/GatewayApiKeyIdentityMapper.cs index f507a79..75d9591 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/GatewayApiKeyIdentityMapper.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/GatewayApiKeyIdentityMapper.cs @@ -19,11 +19,6 @@ namespace ZB.MOM.WW.MxGateway.Server.Security.Authentication; /// public static class GatewayApiKeyIdentityMapper { - // SEC-08: memoize the constraints deserialization keyed by the raw JSON blob so the per-call - // JSON parse on the auth hot path collapses to a dictionary lookup. Distinct blobs are bounded - // by the number of API keys (small); ApiKeyConstraints is an immutable record, so a parsed - // instance is safe to share across callers. The cap is a defensive backstop against a pathological - // spread of distinct blobs — past it, we simply parse without caching rather than grow unbounded. private const int MaxCachedConstraintBlobs = 1024; private static readonly ConcurrentDictionary ConstraintCache = new(StringComparer.Ordinal); diff --git a/src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/ApiKeyFailureLimiter.cs b/src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/ApiKeyFailureLimiter.cs index 730e4c4..c5baea1 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/ApiKeyFailureLimiter.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/ApiKeyFailureLimiter.cs @@ -4,7 +4,7 @@ using ZB.MOM.WW.MxGateway.Server.Configuration; namespace ZB.MOM.WW.MxGateway.Server.Security.Authorization; /// -/// Cheap, in-process per-peer sliding-window failure counter for the gRPC auth path (SEC-11). It is +/// Cheap, in-process per-peer sliding-window failure counter for the gRPC auth path. It is /// checked BEFORE the API-key verification store read and short-circuits a peer that has exceeded /// failed attempts within /// ; a successful verification resets the @@ -45,7 +45,11 @@ public sealed class ApiKeyFailureLimiter { } - // Test/explicit seam. + /// Initializes a new instance of the class. Test/explicit seam. + /// The maximum number of failures allowed within . + /// The sliding window over which failures are counted. + /// The maximum number of tracked peers before least-recently-active eviction kicks in. + /// The time provider. internal ApiKeyFailureLimiter(int limit, TimeSpan window, int maxPeers, TimeProvider clock) { ArgumentNullException.ThrowIfNull(clock); @@ -145,6 +149,7 @@ public sealed class ApiKeyFailureLimiter private sealed class PeerState { + /// Timestamps (in ticks) of failures still within the sliding window. public Queue FailureTicks { get; } = new(); public long LastActivityTicks; diff --git a/src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayGrpcAuthorizationInterceptor.cs b/src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayGrpcAuthorizationInterceptor.cs index 0a187ef..b88fd3a 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayGrpcAuthorizationInterceptor.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayGrpcAuthorizationInterceptor.cs @@ -64,7 +64,7 @@ public sealed class GatewayGrpcAuthorizationInterceptor( string? authorizationHeader = context.RequestHeaders.GetValue("authorization"); - // SEC-11: short-circuit a peer that has already failed too many times inside the sliding + // Short-circuit a peer that has already failed too many times inside the sliding // window BEFORE the verification store read, so online guessing cannot spend a SQLite read // per attempt. The peer key prefers the presented key id over the transport address (NAT // caveat). ResourceExhausted signals throttling without revealing whether any particular diff --git a/src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GrpcAuthorizationServiceCollectionExtensions.cs b/src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GrpcAuthorizationServiceCollectionExtensions.cs index ae697f3..62e4d9a 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GrpcAuthorizationServiceCollectionExtensions.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GrpcAuthorizationServiceCollectionExtensions.cs @@ -19,7 +19,7 @@ public static class GrpcAuthorizationServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - // SEC-11 per-peer failure counter, checked before the verification store read. Bind the knobs + // Per-peer failure counter, checked before the verification store read. Bind the knobs // from IConfiguration directly (not IOptions) to avoid coupling this // registration to the whole-options validation pipeline. services.AddSingleton(sp => new ApiKeyFailureLimiter( diff --git a/src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionManager.cs b/src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionManager.cs index 43ee931..428d99f 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionManager.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionManager.cs @@ -222,11 +222,6 @@ public sealed class SessionManager : ISessionManager } /// - /// - /// Mirrors the registry/metrics cleanup that - /// performs after a successful close, but skips the WorkerClient.ShutdownAsync - /// step that would otherwise attempt. - /// public async Task KillWorkerAsync( string sessionId, string reason, diff --git a/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerClient.cs b/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerClient.cs index 342ae6d..bc65c8a 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerClient.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerClient.cs @@ -27,7 +27,7 @@ public sealed class WorkerClient : IWorkerClient // Staging hand-off between the read loop and the dedicated event writer. The read loop writes // here with a non-blocking TryWrite so a full consumer channel (_events) can never stall the read - // loop behind an event — replies and heartbeats keep flowing (GWC-04). Unbounded, but only fills + // loop behind an event — replies and heartbeats keep flowing. Unbounded, but only fills // during the bounded EventChannelFullModeTimeout window before EventWriteLoopAsync faults on a // sustained backlog, after which the read loop stops. private readonly Channel _eventStaging; @@ -208,7 +208,7 @@ public sealed class WorkerClient : IWorkerClient // Reject an oversized command at the enqueue boundary so only this correlation fails // (ResourceExhausted) rather than the frame reaching the write loop and faulting the whole - // session (IPC-03). Command envelopes are the only gateway-authored outbound payload whose + // session. Command envelopes are the only gateway-authored outbound payload whose // size the caller controls; checking here keeps a MessageTooLarge in the write loop a // genuine desync signal. int envelopeSize = commandEnvelope.CalculateSize(); @@ -269,7 +269,7 @@ public sealed class WorkerClient : IWorkerClient // The event channel is SingleReader: only one enumerator may ever drain it, otherwise // the two readers would each receive a random subset of events. Claim the reader at CALL // time (not lazily on first MoveNext) and fail loudly on a second consumer rather than - // silently splitting the stream (see GWC-01). The distributor pump is the only intended + // silently splitting the stream. The distributor pump is the only intended // caller; the alarm monitor and dashboard mirror attach to the distributor instead. if (Interlocked.CompareExchange(ref _eventsReaderClaimed, 1, 0) != 0) { @@ -519,7 +519,7 @@ public sealed class WorkerClient : IWorkerClient /// Routes a received envelope to its handler. Every branch dispatches synchronously and /// immediately — the event branch only stages the event for the dedicated writer — so a full /// event channel can never delay a command reply, heartbeat, fault, or shutdown ack behind an - /// event backlog (GWC-04). + /// event backlog. /// /// The envelope to dispatch. private void DispatchEnvelope(WorkerEnvelope envelope) @@ -559,7 +559,7 @@ public sealed class WorkerClient : IWorkerClient /// succeeds unless the channel has been completed during shutdown — in which case the event is /// safely dropped because the client is closing. Backpressure and the sustained-overflow fault /// are applied by against the bounded consumer channel, - /// off the read loop (GWC-04). + /// off the read loop. /// /// The event received from the worker. private void StageWorkerEvent(WorkerEvent workerEvent) @@ -575,7 +575,7 @@ public sealed class WorkerClient : IWorkerClient /// /// Drains staged worker events and applies the bounded-channel backpressure (and /// sustained-overflow fault) on a dedicated task, so the timed write - /// never runs on the read loop (GWC-04). Mirrors for events. + /// never runs on the read loop. Mirrors for events. /// private async Task EventWriteLoopAsync() { @@ -984,7 +984,7 @@ public sealed class WorkerClient : IWorkerClient GatewayVersion = typeof(GatewayContractInfo).Assembly.GetName().Version?.ToString() ?? GatewayVersionFallback, // Convey the negotiated worker-frame maximum so the worker adopts it instead of a - // hard-coded default (IPC-02). Sits above the public gRPC cap by the envelope reserve. + // hard-coded default. Sits above the public gRPC cap by the envelope reserve. MaxFrameBytes = (uint)_connection.FrameOptions.MaxMessageBytes, }); } diff --git a/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerClientErrorCode.cs b/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerClientErrorCode.cs index 8f32111..b16c58d 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerClientErrorCode.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerClientErrorCode.cs @@ -15,6 +15,6 @@ public enum WorkerClientErrorCode // The serialized command envelope exceeds the negotiated worker-frame maximum. Rejected at the // enqueue boundary so only the offending command fails (mapped to ResourceExhausted) instead of - // the oversized frame reaching the write loop and faulting the whole session (IPC-03). + // the oversized frame reaching the write loop and faulting the whole session. CommandTooLarge, } diff --git a/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerFrameProtocolOptions.cs b/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerFrameProtocolOptions.cs index 1b664d2..7ef2712 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerFrameProtocolOptions.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerFrameProtocolOptions.cs @@ -15,7 +15,7 @@ public sealed class WorkerFrameProtocolOptions /// gRPC payload accepted at the public boundary always fits inside one worker frame once wrapped /// in a WorkerEnvelope (correlation id, timestamps, oneof framing). Without this headroom /// the pipe max equals the gRPC max and a maximally-sized accepted request faults the whole - /// session on the outbound write (IPC-03). 64 KiB is far larger than the fixed envelope overhead. + /// session on the outbound write. 64 KiB is far larger than the fixed envelope overhead. /// public const int EnvelopeOverheadReserveBytes = 64 * 1024; diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GatewayOptionsTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GatewayOptionsTests.cs index 5a114af..94bf920 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GatewayOptionsTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GatewayOptionsTests.cs @@ -14,7 +14,7 @@ public sealed class GatewayOptionsTests GatewayOptions options = BindOptions(new Dictionary()); Assert.Equal(AuthenticationMode.ApiKey, options.Authentication.Mode); - // SEC-01: the default is derived from CommonApplicationData (C:\ProgramData on Windows, + // The default is derived from CommonApplicationData (C:\ProgramData on Windows, // /usr/share on Unix) rather than a Windows literal, so assert against the same derivation // to stay platform-correct. Assert.Equal( @@ -35,7 +35,7 @@ public sealed class GatewayOptionsTests Assert.Equal(10, options.Worker.ShutdownTimeoutSeconds); Assert.Equal(5, options.Worker.HeartbeatIntervalSeconds); Assert.Equal(15, options.Worker.HeartbeatGraceSeconds); - // 16 MiB public gRPC cap plus the 64 KiB worker-frame envelope reserve (IPC-03 headroom). + // 16 MiB public gRPC cap plus the 64 KiB worker-frame envelope reserve for headroom. Assert.Equal((16 * 1024 * 1024) + (64 * 1024), options.Worker.MaxMessageBytes); Assert.Equal(30, options.Sessions.DefaultCommandTimeoutSeconds); diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GatewayOptionsValidatorTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GatewayOptionsValidatorTests.cs index 65c60f1..57c60bb 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GatewayOptionsValidatorTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GatewayOptionsValidatorTests.cs @@ -550,10 +550,6 @@ public sealed class GatewayOptionsValidatorTests Assert.True(result.Succeeded); } - // ------------------------------------------------------------------------- - // SEC-01: security-sensitive paths must be rooted (absolute) - // ------------------------------------------------------------------------- - private static GatewayOptions CloneWithAuthentication(GatewayOptions source, AuthenticationOptions authentication) => new() { @@ -606,10 +602,6 @@ public sealed class GatewayOptionsValidatorTests f => f.Contains("MxGateway:Tls:SelfSignedCertPath") && f.Contains("rooted")); } - // ------------------------------------------------------------------------- - // SEC-04: DisableLogin production guard - // ------------------------------------------------------------------------- - private static GatewayOptions CloneWithDashboard(GatewayOptions source, DashboardOptions dashboard) => new() { @@ -649,10 +641,6 @@ public sealed class GatewayOptionsValidatorTests Assert.True(result.Succeeded); } - // ------------------------------------------------------------------------- - // SEC-06: LDAP plaintext transport production guard - // ------------------------------------------------------------------------- - /// Verifies plaintext LDAP transport (None) aborts startup in Production. [Fact] public void Validate_Fails_WhenLdapTransportNoneInProduction() @@ -684,8 +672,6 @@ public sealed class GatewayOptionsValidatorTests Assert.True(result.Succeeded); } - // ---- SEC-11: MxGateway:Security validation ---- - private static GatewayOptions WithSecurity(SecurityOptions security) => new() { Security = security }; /// Verifies the default security options pass validation. @@ -791,7 +777,7 @@ public sealed class GatewayOptionsValidatorTests /// /// Verifies the default worker-frame maximum keeps the required envelope-overhead reserve above - /// the default public gRPC cap, so a stock configuration passes the IPC-03 headroom check. + /// the default public gRPC cap, so a stock configuration passes the headroom check. /// [Fact] public void Validate_Succeeds_WhenWorkerFrameMaxHasEnvelopeHeadroom() @@ -803,7 +789,7 @@ public sealed class GatewayOptionsValidatorTests /// /// Verifies that a worker-frame maximum equal to the gRPC cap (zero headroom) fails validation: /// a maximally-sized accepted gRPC payload would not fit one worker frame once wrapped in a - /// WorkerEnvelope, faulting the whole session on the outbound write (IPC-03). + /// WorkerEnvelope, faulting the whole session on the outbound write. /// [Fact] public void Validate_Fails_WhenWorkerFrameMaxEqualsGrpcMaxWithoutHeadroom() diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Contracts/ClientProtoInputTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Contracts/ClientProtoInputTests.cs index 981b54f..b047347 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Contracts/ClientProtoInputTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Contracts/ClientProtoInputTests.cs @@ -9,7 +9,7 @@ namespace ZB.MOM.WW.MxGateway.Tests.Contracts; public sealed class ClientProtoInputTests { /// - /// Guards the published client descriptor set against silent staleness (IPC-01). Every message + /// Guards the published client descriptor set against silent staleness. Every message /// and field compiled into the in-process contract (which the build regenerates from the current /// .proto sources) must appear in the committed protoset. A missing symbol means the /// descriptor was not regenerated after a proto change; run diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardLoginRateLimitTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardLoginRateLimitTests.cs index 523496a..48a4480 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardLoginRateLimitTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardLoginRateLimitTests.cs @@ -7,7 +7,7 @@ using ZB.MOM.WW.MxGateway.Server.Dashboard; namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Dashboard; /// -/// SEC-11: the POST /auth/login fixed-window per-IP rate limiter. Exercised through the same +/// Tests the POST /auth/login fixed-window per-IP rate limiter. Exercised through the same /// partition factory the production policy registers, so the test pins the real permit limit and /// per-IP partitioning without standing up an HTTP server. /// diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardSessionAdminServiceTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardSessionAdminServiceTests.cs index f254ba1..611447b 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardSessionAdminServiceTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardSessionAdminServiceTests.cs @@ -203,7 +203,7 @@ public sealed class DashboardSessionAdminServiceTests /// /// Verifies that a successful close writes a canonical dashboard-close-session /// — actor, session-id target, and Success outcome — to the - /// audit store, not only the operational log (SEC-12). + /// audit store, not only the operational log. /// /// A task that represents the asynchronous operation. [Fact] @@ -229,7 +229,7 @@ public sealed class DashboardSessionAdminServiceTests /// /// Verifies that a successful kill writes a canonical dashboard-kill-worker - /// to the audit store (SEC-12). + /// to the audit store. /// /// A task that represents the asynchronous operation. [Fact] @@ -253,7 +253,7 @@ public sealed class DashboardSessionAdminServiceTests /// /// Verifies that an unauthorized (viewer) close attempt still writes a Denied - /// audit row, so rejected destructive attempts are durably recorded (SEC-12). + /// audit row, so rejected destructive attempts are durably recorded. /// /// A task that represents the asynchronous operation. [Fact] @@ -305,7 +305,10 @@ public sealed class DashboardSessionAdminServiceTests /// Gets the audit events written through this writer, in order. public IReadOnlyList Events => _events.ToArray(); - /// + /// Records the given audit event for later inspection by the test. + /// The audit event to record. + /// Token to cancel the asynchronous operation. + /// A task that represents the asynchronous operation. public Task WriteAsync(AuditEvent evt, CancellationToken ct = default) { _events.Enqueue(evt); diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/HubTokenServiceTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/HubTokenServiceTests.cs index 4d726fc..502cd9d 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/HubTokenServiceTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/HubTokenServiceTests.cs @@ -145,7 +145,7 @@ public sealed class HubTokenServiceTests } /// - /// The default token lifetime is the short (5-minute) window mandated by SEC-05, not the + /// The default token lifetime is the short (5-minute) window, not the /// former 30-minute window. Pins the value so a regression that widens the exposure window /// of an irrevocable, query-string-carried token is caught in CI. /// diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Grpc/EventStreamServiceTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Grpc/EventStreamServiceTests.cs index e4f7664..4943e8d 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Grpc/EventStreamServiceTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Grpc/EventStreamServiceTests.cs @@ -39,7 +39,7 @@ public sealed class EventStreamServiceTests } /// - /// TST-02 (owner-scoped attach): the API key that opened a session may attach its event + /// Owner-scoped attach: the API key that opened a session may attach its event /// stream — the caller key equals the session owner, so streaming proceeds normally. /// /// A task that represents the asynchronous operation. @@ -64,7 +64,7 @@ public sealed class EventStreamServiceTests } /// - /// TST-02 (owner-scoped attach, security control): a caller whose API key differs from + /// Owner-scoped attach, security control: a caller whose API key differs from /// the key that opened the session is rejected with a /// fault before any events are streamed — closing the reconnect/fan-out trust-boundary hole. /// diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Grpc/MxAccessGrpcRequestValidatorTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Grpc/MxAccessGrpcRequestValidatorTests.cs index 52f9908..0bc8df1 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Grpc/MxAccessGrpcRequestValidatorTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Grpc/MxAccessGrpcRequestValidatorTests.cs @@ -18,8 +18,9 @@ public sealed class MxAccessGrpcRequestValidatorTests /// /// Verifies a DrainEvents request within the per-request ceiling passes validation, including the - /// max_events = 0 "worker default cap" sentinel (IPC-04). + /// max_events = 0 "worker default cap" sentinel. /// + /// The requested drain-events ceiling to validate. [Theory] [InlineData(0u)] [InlineData(1u)] @@ -32,7 +33,7 @@ public sealed class MxAccessGrpcRequestValidatorTests /// /// Verifies a DrainEvents request above the per-request ceiling is rejected with InvalidArgument - /// so one accepted request cannot pack an unbounded reply frame (IPC-04). + /// so one accepted request cannot pack an unbounded reply frame. /// [Fact] public void ValidateInvoke_RejectsDrainEvents_AboveCeiling() diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/GatewaySessionDashboardMirrorTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/GatewaySessionDashboardMirrorTests.cs index b2df718..3b12acf 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/GatewaySessionDashboardMirrorTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/GatewaySessionDashboardMirrorTests.cs @@ -108,7 +108,7 @@ public sealed class GatewaySessionDashboardMirrorTests } /// - /// GWC-01 regression: with the internal dashboard mirror active, a second internal + /// With the internal dashboard mirror active, a second internal /// subscriber (the alarm monitor's feed, attached via /// ) receives EVERY event — /// including the alarm Acknowledge transition — rather than the two consumers diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Workers/WorkerClientTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Workers/WorkerClientTests.cs index 8300759..c4b112e 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Workers/WorkerClientTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Workers/WorkerClientTests.cs @@ -59,7 +59,7 @@ public sealed class WorkerClientTests /// /// Verifies that a command whose serialized envelope exceeds the negotiated worker-frame maximum /// fails only that command with at the enqueue - /// boundary, leaving the client ready for subsequent commands (IPC-03). Without the pre-check the + /// boundary, leaving the client ready for subsequent commands. Without the pre-check the /// oversized frame would reach the write loop and fault the whole session. /// /// A task that represents the asynchronous operation. @@ -170,7 +170,7 @@ public sealed class WorkerClientTests /// /// The worker event channel is single-reader: a second - /// enumerator must throw rather than silently split events between two consumers (GWC-01). + /// enumerator must throw rather than silently split events between two consumers. /// /// A task that represents the asynchronous operation. [Fact] @@ -221,7 +221,7 @@ public sealed class WorkerClientTests /// /// Verifies that a command reply arriving on the pipe after events is dispatched promptly even /// when the event channel is full and has no consumer — event enqueue is decoupled from the read - /// loop, so a blocked event writer cannot delay a reply (GWC-04). The event full-mode timeout is + /// loop, so a blocked event writer cannot delay a reply. The event full-mode timeout is /// set far above the command timeout: without the decoupling the read loop would block behind the /// full event channel and the in-flight InvokeAsync would hit CommandTimeout. /// diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Metrics/GatewayMetricsTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Metrics/GatewayMetricsTests.cs index 2dec8bd..3b3b0fb 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Metrics/GatewayMetricsTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Metrics/GatewayMetricsTests.cs @@ -161,7 +161,7 @@ public sealed class GatewayMetricsTests /// Verifies that increments /// mxgateway.heartbeats.failed without emitting a session_id tag: the tag is /// unbounded cardinality (every session mints a new exporter time series), so per-session - /// attribution is deliberately kept out of the exported counter (SEC-20). + /// attribution is deliberately kept out of the exported counter. /// [Fact] public void HeartbeatFailed_IncrementsCounterWithoutSessionIdTag() diff --git a/src/ZB.MOM.WW.MxGateway.Tests/ProjectStructure/GatewayTreeHygieneTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/ProjectStructure/GatewayTreeHygieneTests.cs index 4505874..ed3083c 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/ProjectStructure/GatewayTreeHygieneTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/ProjectStructure/GatewayTreeHygieneTests.cs @@ -1,7 +1,7 @@ namespace ZB.MOM.WW.MxGateway.Tests.ProjectStructure; /// -/// Repo-hygiene guards over the source tree. See SEC-01: a Windows-absolute default path that +/// Repo-hygiene guards over the source tree. A Windows-absolute default path that /// resolves relative to the launch working directory silently materializes a SQLite credential /// store inside the tree. This test fails if any such *.db file appears under src/. /// diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Security/Authentication/ApiKeyAdminCliRunnerTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Security/Authentication/ApiKeyAdminCliRunnerTests.cs index 6890a75..7d517cc 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Security/Authentication/ApiKeyAdminCliRunnerTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Security/Authentication/ApiKeyAdminCliRunnerTests.cs @@ -54,7 +54,7 @@ public sealed class ApiKeyAdminCliRunnerTests : IDisposable /// /// Verifies that a key created with an already-past --expires is rejected by the verifier - /// — the CLI expiry wiring reaches the store and the library enforces it end-to-end (SEC-10). + /// — the CLI expiry wiring reaches the store and the library enforces it end-to-end. /// /// A task that represents the asynchronous operation. [Fact] diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Security/Authentication/ApiKeyAdminCommandLineParserTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Security/Authentication/ApiKeyAdminCommandLineParserTests.cs index 9fe0eba..f7b94bb 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Security/Authentication/ApiKeyAdminCommandLineParserTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Security/Authentication/ApiKeyAdminCommandLineParserTests.cs @@ -52,7 +52,7 @@ public sealed class ApiKeyAdminCommandLineParserTests Assert.Contains("events:read", result.Command.Scopes); } - /// A create-key command without --expires leaves the key non-expiring (opt-in, SEC-10). + /// A create-key command without --expires leaves the key non-expiring (opt-in). [Fact] public void Parse_CreateKeyCommand_WithoutExpires_HasNoExpiry() { @@ -63,7 +63,7 @@ public sealed class ApiKeyAdminCommandLineParserTests Assert.Null(result.Command.ExpiresUtc); } - /// An absolute ISO-8601 --expires value is parsed to that exact UTC instant (SEC-10). + /// An absolute ISO-8601 --expires value is parsed to that exact UTC instant. [Fact] public void Parse_CreateKeyCommand_WithAbsoluteExpires_ParsesInstant() { @@ -77,7 +77,7 @@ public sealed class ApiKeyAdminCommandLineParserTests result.Command.ExpiresUtc); } - /// A relative "<N>d" --expires value resolves to a future UTC instant (SEC-10). + /// A relative "<N>d" --expires value resolves to a future UTC instant. [Fact] public void Parse_CreateKeyCommand_WithRelativeExpires_ResolvesToFuture() { @@ -94,7 +94,7 @@ public sealed class ApiKeyAdminCommandLineParserTests DateTimeOffset.UtcNow + TimeSpan.FromDays(30) + TimeSpan.FromMinutes(1)); } - /// An unparseable --expires value fails at parse time (SEC-10). + /// An unparseable --expires value fails at parse time. [Fact] public void Parse_CreateKeyCommand_WithInvalidExpires_Fails() { diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Security/Authentication/CachingApiKeyVerifierTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Security/Authentication/CachingApiKeyVerifierTests.cs index 5429448..a658eec 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Security/Authentication/CachingApiKeyVerifierTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Security/Authentication/CachingApiKeyVerifierTests.cs @@ -8,7 +8,7 @@ using LibApiKeyIdentity = ZB.MOM.WW.Auth.Abstractions.ApiKeys.ApiKeyIdentity; namespace ZB.MOM.WW.MxGateway.Tests.Security.Authentication; /// -/// SEC-08 hot-path decorators. Covers both mechanisms: +/// Hot-path decorators. Covers both mechanisms: /// (read/verification coalescing plus revoke/rotate invalidation) and /// (the last_used write coalescing that keeps the /// per-RPC database write off the throughput ceiling). @@ -18,6 +18,7 @@ public sealed class CachingApiKeyVerifierTests private const string Header = "Bearer mxgw_operator01_super-secret"; /// A cache hit within the TTL returns the cached result and never calls the inner verifier. + /// A task that represents the asynchronous operation. [Fact] public async Task VerifyAsync_RepeatedWithinTtl_CallsInnerOnce() { @@ -34,6 +35,7 @@ public sealed class CachingApiKeyVerifierTests } /// Different presented secrets are cached under distinct keys (no cross-secret aliasing). + /// A task that represents the asynchronous operation. [Fact] public async Task VerifyAsync_DifferentTokens_NotAliased() { @@ -48,6 +50,7 @@ public sealed class CachingApiKeyVerifierTests } /// Failed verifications are never cached; every attempt reaches the inner verifier. + /// A task that represents the asynchronous operation. [Fact] public async Task VerifyAsync_FailedVerification_NotCached() { @@ -62,6 +65,7 @@ public sealed class CachingApiKeyVerifierTests } /// A TTL of zero disables caching: the inner verifier is called on every request. + /// A task that represents the asynchronous operation. [Fact] public async Task VerifyAsync_ZeroTtl_DisablesCache() { @@ -76,6 +80,7 @@ public sealed class CachingApiKeyVerifierTests } /// Invalidating a key id (revoke/rotate) drops its cached verification, forcing a re-verify. + /// A task that represents the asynchronous operation. [Fact] public async Task Invalidate_DropsCachedEntry_ForcesReverify() { @@ -96,6 +101,7 @@ public sealed class CachingApiKeyVerifierTests /// The store decorator coalesces repeated MarkUsed writes for the same key inside the /// window down to a single forwarded write — the ≤1/min guarantee for last_used_utc. /// + /// A task that represents the asynchronous operation. [Fact] public async Task CoalescingStore_RepeatedMarksWithinWindow_ForwardsOnce() { @@ -114,6 +120,7 @@ public sealed class CachingApiKeyVerifierTests } /// After the window elapses the next mark is forwarded again (staleness is bounded, not frozen). + /// A task that represents the asynchronous operation. [Fact] public async Task CoalescingStore_AfterWindow_ForwardsAgain() { @@ -129,6 +136,7 @@ public sealed class CachingApiKeyVerifierTests } /// Distinct keys are coalesced independently. + /// A task that represents the asynchronous operation. [Fact] public async Task CoalescingStore_DistinctKeys_TrackedSeparately() { @@ -144,6 +152,7 @@ public sealed class CachingApiKeyVerifierTests } /// A zero window disables coalescing: every mark is forwarded. + /// A task that represents the asynchronous operation. [Fact] public async Task CoalescingStore_ZeroWindow_ForwardsEveryMark() { @@ -173,8 +182,13 @@ public sealed class CachingApiKeyVerifierTests private sealed class FakeVerifier(ApiKeyVerification result) : IApiKeyVerifier { + /// Gets the number of times has been called. public int CallCount { get; private set; } + /// Records the call and returns the fixed supplied at construction. + /// The authorization header presented by the caller. + /// A token to observe for cancellation. + /// The fixed verification result. public Task VerifyAsync(string authorizationHeader, CancellationToken ct) { CallCount++; @@ -184,14 +198,28 @@ public sealed class CachingApiKeyVerifierTests private sealed class FakeStore : IApiKeyStore { + /// Gets the number of times has been called. public int MarkUsedCount { get; private set; } + /// Always returns ; not exercised by these tests. + /// The key id to look up. + /// A token to observe for cancellation. + /// . public Task FindByKeyIdAsync(string keyId, CancellationToken ct) => Task.FromResult(null); + /// Always returns ; not exercised by these tests. + /// The key id to look up. + /// A token to observe for cancellation. + /// . public Task FindActiveByKeyIdAsync(string keyId, CancellationToken ct) => Task.FromResult(null); + /// Records the call by incrementing . + /// The key id that was used. + /// The UTC timestamp of use. + /// A token to observe for cancellation. + /// A task that represents the asynchronous operation. public Task MarkUsedAsync(string keyId, DateTimeOffset whenUtc, CancellationToken ct) { MarkUsedCount++; diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Security/Authorization/GatewayGrpcAuthorizationInterceptorTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Security/Authorization/GatewayGrpcAuthorizationInterceptorTests.cs index 796739b..189b8fa 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Security/Authorization/GatewayGrpcAuthorizationInterceptorTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Security/Authorization/GatewayGrpcAuthorizationInterceptorTests.cs @@ -359,7 +359,7 @@ public sealed class GatewayGrpcAuthorizationInterceptorTests } /// - /// SEC-11: once a peer has exceeded the failure limit, the interceptor short-circuits with + /// Once a peer has exceeded the failure limit, the interceptor short-circuits with /// BEFORE calling the verifier, so an online guessing /// loop stops spending a store read per attempt. A verifier that always fails is used; after the /// limit is reached the verifier is no longer invoked. @@ -404,7 +404,7 @@ public sealed class GatewayGrpcAuthorizationInterceptorTests } /// - /// SEC-11: a successful verification resets the peer's failure counter, so accumulated failures + /// A successful verification resets the peer's failure counter, so accumulated failures /// from a fat-fingered secret do not lock out a client that subsequently authenticates. /// /// A task that represents the asynchronous operation. diff --git a/src/ZB.MOM.WW.MxGateway.Tests/TestSupport/TestHostEnvironmentInitializer.cs b/src/ZB.MOM.WW.MxGateway.Tests/TestSupport/TestHostEnvironmentInitializer.cs index 5c00e3c..300f933 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/TestSupport/TestHostEnvironmentInitializer.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/TestSupport/TestHostEnvironmentInitializer.cs @@ -9,7 +9,7 @@ namespace ZB.MOM.WW.MxGateway.Tests.TestSupport; /// /// Many tests build the full gateway host through GatewayApplication.Build against the dev /// appsettings.json (which ships Ldap:Transport=None and other dev-only defaults). -/// An unset environment resolves to Production, where the SEC-04/06 production guards fail startup +/// An unset environment resolves to Production, where the production guards fail startup /// by design — so those host-building tests would trip the guards. Setting the environment to /// Development once, before any test runs, keeps that suite exercising app wiring rather than /// production-config validation. Tests that specifically assert production behavior construct @@ -28,6 +28,10 @@ namespace ZB.MOM.WW.MxGateway.Tests.TestSupport; /// internal static class TestHostEnvironmentInitializer { + /// + /// Applies the Development environment and isolated self-signed-cert path defaults described + /// on this type, run once by the runtime before any test in the assembly executes. + /// [ModuleInitializer] internal static void SetDevelopmentEnvironmentDefault() { diff --git a/src/ZB.MOM.WW.MxGateway.Worker.Tests/Ipc/WorkerFrameProtocolTests.cs b/src/ZB.MOM.WW.MxGateway.Worker.Tests/Ipc/WorkerFrameProtocolTests.cs index 8921762..b60ebf6 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker.Tests/Ipc/WorkerFrameProtocolTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker.Tests/Ipc/WorkerFrameProtocolTests.cs @@ -310,7 +310,7 @@ public sealed class WorkerFrameProtocolTests /// /// Verifies that under concurrent writers every frame receives a distinct, gap-free sequence in /// strictly increasing on-wire order — the sequence is stamped by the writer at write time, so the - /// wire order and the stamped sequence always agree (WRK-04). + /// wire order and the stamped sequence always agree. /// /// A task that represents the asynchronous operation. [Fact] @@ -339,7 +339,7 @@ public sealed class WorkerFrameProtocolTests /// /// Verifies that when a control frame and an event frame are both queued behind an in-progress /// write, the draining lock-holder writes the control frame first even though the event was queued - /// earlier (WRK-07). + /// earlier. /// /// A task that represents the asynchronous operation. [Fact] @@ -373,7 +373,7 @@ public sealed class WorkerFrameProtocolTests Assert.Equal(WorkerEnvelope.BodyOneofCase.WorkerEvent, frame3.BodyCase); } - /// Verifies a zero negotiated frame maximum keeps the constructor default (IPC-02). + /// Verifies a zero negotiated frame maximum keeps the constructor default. [Fact] public void AdoptNegotiatedMaxMessageBytes_WithZero_KeepsDefault() { @@ -383,7 +383,7 @@ public sealed class WorkerFrameProtocolTests Assert.Equal(original, options.MaxMessageBytes); } - /// Verifies an in-range negotiated frame maximum is adopted (IPC-02). + /// Verifies an in-range negotiated frame maximum is adopted. [Fact] public void AdoptNegotiatedMaxMessageBytes_WithInRangeValue_Adopts() { @@ -392,7 +392,7 @@ public sealed class WorkerFrameProtocolTests Assert.Equal(4 * 1024 * 1024, options.MaxMessageBytes); } - /// Verifies a negotiated frame maximum above the worker ceiling is rejected (IPC-02). + /// Verifies a negotiated frame maximum above the worker ceiling is rejected. [Fact] public void AdoptNegotiatedMaxMessageBytes_AboveCeiling_Throws() { @@ -452,10 +452,13 @@ public sealed class WorkerFrameProtocolTests new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); private int _writeCount; + /// Gets a task that completes once the first call has started blocking. public Task FirstWriteStarted => _firstWriteStarted.Task; + /// Releases the first blocked write so it can complete. public void ReleaseFirstWrite() => _release.Release(); + /// public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { if (Interlocked.Increment(ref _writeCount) == 1) @@ -467,6 +470,7 @@ public sealed class WorkerFrameProtocolTests await base.WriteAsync(buffer, offset, count, cancellationToken); } + /// protected override void Dispose(bool disposing) { if (disposing) diff --git a/src/ZB.MOM.WW.MxGateway.Worker.Tests/Ipc/WorkerPipeSessionTests.cs b/src/ZB.MOM.WW.MxGateway.Worker.Tests/Ipc/WorkerPipeSessionTests.cs index 3533efe..4b71b16 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker.Tests/Ipc/WorkerPipeSessionTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker.Tests/Ipc/WorkerPipeSessionTests.cs @@ -452,7 +452,7 @@ public sealed class WorkerPipeSessionTests /// /// Verifies that a DrainEvents control command with max_events = 0 is bounded by the /// worker rather than draining the entire queue into one reply frame: the session passes a - /// capped, non-zero maximum to the runtime session (IPC-04). + /// capped, non-zero maximum to the runtime session. /// /// A task that represents the asynchronous operation. [Fact] @@ -743,7 +743,7 @@ public sealed class WorkerPipeSessionTests } /// - /// WRK-01 regression: a long in-flight STA command that keeps pumping + /// Regression test: a long in-flight STA command that keeps pumping /// must NOT self-fault as StaHung, and its reply must still be /// delivered. The real fix makes StaRuntime.PumpPendingMessages /// refresh LastActivityUtc on every wait iteration, so a healthy diff --git a/src/ZB.MOM.WW.MxGateway.Worker.Tests/Sta/StaRuntimeTests.cs b/src/ZB.MOM.WW.MxGateway.Worker.Tests/Sta/StaRuntimeTests.cs index f143324..7173434 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker.Tests/Sta/StaRuntimeTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker.Tests/Sta/StaRuntimeTests.cs @@ -92,7 +92,7 @@ public sealed class StaRuntimeTests /// the first OnDataChange) invokes the pump step on every wait /// iteration while it legitimately holds the STA thread; refreshing /// activity here keeps the watchdog from mistaking a busy STA for a hung - /// one (WRK-01). The runtime is deliberately left unstarted so the only + /// one. The runtime is deliberately left unstarted so the only /// source of activity is the pump call under test, not the idle loop. /// [Fact] diff --git a/src/ZB.MOM.WW.MxGateway.Worker.Tests/TestSupport/FakeRuntimeSession.cs b/src/ZB.MOM.WW.MxGateway.Worker.Tests/TestSupport/FakeRuntimeSession.cs index 776c708..fb0d611 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker.Tests/TestSupport/FakeRuntimeSession.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker.Tests/TestSupport/FakeRuntimeSession.cs @@ -128,7 +128,7 @@ internal sealed class FakeRuntimeSession : IWorkerRuntimeSession /// /// Records the maxEvents argument of the most recent non-suppressed /// call — i.e. the effective cap the session passed for an explicit - /// DrainEvents control command. Lets a test assert the worker bounds the drain (IPC-04) rather + /// DrainEvents control command. Lets a test assert the worker bounds the drain rather /// than forwarding the client's raw max_events = 0. /// public uint? LastDrainMaxEvents { get; private set; } diff --git a/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerFrameProtocolOptions.cs b/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerFrameProtocolOptions.cs index b9e016e..96fdcde 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerFrameProtocolOptions.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerFrameProtocolOptions.cs @@ -12,7 +12,7 @@ public sealed class WorkerFrameProtocolOptions /// /// Upper ceiling the worker will accept for a gateway-negotiated frame maximum - /// (GatewayHello.max_frame_bytes, IPC-02). Matches the gateway's own configuration ceiling + /// (GatewayHello.max_frame_bytes). Matches the gateway's own configuration ceiling /// so a nonsensical negotiated value is rejected at the handshake rather than driving an absurd /// per-frame allocation. 256 MiB. /// @@ -109,15 +109,15 @@ public sealed class WorkerFrameProtocolOptions /// /// Gets the maximum worker-frame message size in bytes. Initialized from the constructor and /// then adopted once from the gateway-negotiated value during the startup handshake - /// (GatewayHello.max_frame_bytes, IPC-02) via , + /// (GatewayHello.max_frame_bytes) via , /// before the message loop starts. Not mutated afterwards, so the single-threaded handshake write /// is safe for the reader/writer that share this instance. /// public int MaxMessageBytes { get; private set; } /// - /// Adopts the gateway-negotiated frame maximum conveyed in GatewayHello.max_frame_bytes - /// (IPC-02). A value of 0 (an older gateway that never set the field) is ignored and the + /// Adopts the gateway-negotiated frame maximum conveyed in GatewayHello.max_frame_bytes. + /// A value of 0 (an older gateway that never set the field) is ignored and the /// constructor default is kept. A value above is rejected. /// /// The gateway-negotiated maximum, or 0 for "keep default". diff --git a/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerFrameWritePriority.cs b/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerFrameWritePriority.cs index 46c60f8..1241586 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerFrameWritePriority.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerFrameWritePriority.cs @@ -3,9 +3,9 @@ namespace ZB.MOM.WW.MxGateway.Worker.Ipc; /// /// Relative scheduling priority for an outbound worker frame. The single writer task drains all /// pending frames before any frame, so a command reply, -/// fault, heartbeat, or shutdown acknowledgement is not delayed behind a backlog of queued events -/// (WRK-07). Priority only reorders the write; the frame sequence is stamped at actual write time, -/// so the on-wire order and the envelope Sequence always agree (WRK-04). +/// fault, heartbeat, or shutdown acknowledgement is not delayed behind a backlog of queued events. +/// Priority only reorders the write; the frame sequence is stamped at actual write time, +/// so the on-wire order and the envelope Sequence always agree. /// public enum WorkerFrameWritePriority { diff --git a/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerFrameWriter.cs b/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerFrameWriter.cs index 5ae2e58..dff6da9 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerFrameWriter.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerFrameWriter.cs @@ -12,22 +12,26 @@ namespace ZB.MOM.WW.MxGateway.Worker.Ipc; /// Writes worker frames to a stream with length-prefixed protobuf serialization. Callers enqueue a /// frame at a and then contend for a single write lock; whoever /// holds the lock drains every queued frame, control frames first, so a reply, fault, or heartbeat is -/// never delayed behind an event backlog (WRK-07). The envelope Sequence is stamped by the +/// never delayed behind an event backlog. The envelope Sequence is stamped by the /// draining lock-holder at the moment of writing, so the on-wire order and the stamped sequence always -/// agree even under concurrent callers and priority reordering (WRK-04). +/// agree even under concurrent callers and priority reordering. /// public sealed class WorkerFrameWriter { private sealed class PendingFrame { + /// Initializes a new instance of the PendingFrame class. + /// Worker envelope awaiting write. public PendingFrame(WorkerEnvelope envelope) { Envelope = envelope; Completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); } + /// Gets the worker envelope awaiting write. public WorkerEnvelope Envelope { get; } + /// Gets the completion source signaled once the frame has been written or has failed. public TaskCompletionSource Completion { get; } } @@ -191,7 +195,7 @@ public sealed class WorkerFrameWriter WorkerEnvelopeValidator.Validate(envelope, _options); // Stamp the sequence at the actual point of writing, under the write lock, so the wire order - // and the stamped sequence agree regardless of caller concurrency or priority (WRK-04). + // and the stamped sequence agree regardless of caller concurrency or priority. envelope.Sequence = unchecked(++_nextSequence); int payloadLength = envelope.CalculateSize(); diff --git a/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerPipeSession.cs b/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerPipeSession.cs index fea8594..b9629ab 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerPipeSession.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerPipeSession.cs @@ -20,7 +20,7 @@ public sealed class WorkerPipeSession // Hard cap on how many events a single DrainEvents diagnostic reply may carry. DrainEvents is a // non-streaming control command, so an unbounded drain (including the max_events = 0 "as many as - // available" request) could pack the whole queue into one session-killing reply frame (IPC-04). + // available" request) could pack the whole queue into one session-killing reply frame. // The gateway request validator rejects requests above its public ceiling; this worker-side cap is // the backstop and defines the effective per-reply maximum. Kept in step with that public ceiling. private const uint MaxDrainEventsPerReply = 10_000; @@ -233,7 +233,7 @@ public sealed class WorkerPipeSession } // Adopt the gateway-negotiated frame maximum so both ends frame to the same limit instead of - // matched compile-time defaults (IPC-02). Applied here, before the message loop, so every + // matched compile-time defaults. Applied here, before the message loop, so every // post-handshake frame is validated against the negotiated value; the reader and writer share // this options instance. The hello frame itself was small and already read under the default. _options.AdoptNegotiatedMaxMessageBytes(gatewayHello.MaxFrameBytes); @@ -375,7 +375,7 @@ public sealed class WorkerPipeSession { // Events are the low-priority frame class: the writer holds them behind any pending // control frame (reply, fault, heartbeat, shutdown ack) so those are not delayed - // behind an event backlog (WRK-07). + // behind an event backlog. await _writer .WriteAsync(CreateEnvelope(workerEvent), WorkerFrameWritePriority.Event, cancellationToken) .ConfigureAwait(false); @@ -543,7 +543,7 @@ public sealed class WorkerPipeSession if (runtimeSession is not null) { // Bound the diagnostic drain so max_events = 0 ("as many as available") or an over-large - // request cannot pack the whole queue into one session-killing reply frame (IPC-04). + // request cannot pack the whole queue into one session-killing reply frame. uint requested = command.DrainEvents?.MaxEvents ?? 0; uint maxEvents = requested == 0 || requested > MaxDrainEventsPerReply ? MaxDrainEventsPerReply @@ -1026,7 +1026,7 @@ public sealed class WorkerPipeSession { // Sequence is deliberately left unset here: the frame writer stamps it at the actual point of // writing, under its single drain task, so the on-wire order and the stamped sequence agree - // even under concurrent producers and priority reordering (WRK-04). + // even under concurrent producers and priority reordering. return new WorkerEnvelope { ProtocolVersion = _options.ProtocolVersion, diff --git a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/SubtagAlarmConsumer.cs b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/SubtagAlarmConsumer.cs index 9ef6ab8..c02b519 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/SubtagAlarmConsumer.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/SubtagAlarmConsumer.cs @@ -78,13 +78,6 @@ public sealed class SubtagAlarmConsumer : IMxAccessAlarmConsumer } /// - /// - /// The expression is ignored — the subtag - /// set is fixed by the watch list. Also advises the ack-comment subtag so - /// it is an active MXAccess item by the time - /// writes it; MXAccess rejects a write to an added-but-not-advised item - /// with E_INVALIDARG. - /// public void Subscribe(string subscription) { if (disposed) @@ -111,11 +104,6 @@ public sealed class SubtagAlarmConsumer : IMxAccessAlarmConsumer } /// - /// - /// Resolves the synthetic GUID back to its alarm full reference and - /// delegates to the by-name write path; operator-identity arguments are - /// not surfaced through the subtag write. - /// public int AcknowledgeByGuid( Guid alarmGuid, string ackComment, @@ -139,11 +127,6 @@ public sealed class SubtagAlarmConsumer : IMxAccessAlarmConsumer } /// - /// - /// In subtag mode the comment is written to the target's writable - /// ack-comment subtag; the operator-identity arguments are not - /// surfaced through the subtag write. - /// public int AcknowledgeByName( string alarmName, string providerName, @@ -169,10 +152,6 @@ public sealed class SubtagAlarmConsumer : IMxAccessAlarmConsumer } /// - /// - /// Each returned record is stamped - /// and assigned its synthetic GUID. - /// public IReadOnlyList SnapshotActiveAlarms() { IReadOnlyList records = stateMachine.SnapshotActive(); @@ -185,7 +164,6 @@ public sealed class SubtagAlarmConsumer : IMxAccessAlarmConsumer } /// - /// No-op: the subtag path is event-driven and owns no poll cadence. public void PollOnce() { // Subtag mode is event-driven; value changes arrive via the source's diff --git a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WnWrapAlarmConsumer.cs b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WnWrapAlarmConsumer.cs index e41f371..8dd78b9 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WnWrapAlarmConsumer.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WnWrapAlarmConsumer.cs @@ -301,14 +301,6 @@ public sealed class WnWrapAlarmConsumer : IMxAccessAlarmConsumer } /// - /// - /// STA-bound hosts drive polling by calling this from - /// the thread that owns the COM object. The consumer deliberately - /// owns no internal timer: a thread-pool timer would call the - /// apartment-threaded COM object off its owning STA and can block - /// indefinitely on cross-apartment marshaling when the STA is not - /// pumping messages. - /// public void PollOnce() { wwAlarmConsumerClass? com;