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 a84a324..f84fb72 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/MxGatewayClientCli.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/MxGatewayClientCli.cs @@ -347,13 +347,13 @@ public static class MxGatewayClientCli /// /// Canonical CLI credential environment variable, shared by every official - /// client CLI (CLI-45) so one exported variable drives the same operator + /// client CLI so one exported variable drives the same operator /// workflow in all five languages. /// private const string DefaultVerifyPasswordEnvironmentName = "MXGATEWAY_VERIFY_PASSWORD"; /// - /// Pre-CLI-45 environment variable, still honoured as a deprecated fallback + /// Legacy environment variable, still honoured as a deprecated fallback /// for one release so existing scripts keep working. /// private const string LegacyVerifyPasswordEnvironmentName = "MXGATEWAY_VERIFY_USER_PASSWORD"; @@ -379,14 +379,14 @@ public static class MxGatewayClientCli } /// - /// Resolves the effective MXAccess verify-user credential in the CLI-45 + /// Resolves the effective MXAccess verify-user credential in this /// order: --password, the deprecated --verify-user-password /// alias, the environment variable named by --password-env (default /// MXGATEWAY_VERIFY_PASSWORD), then the deprecated /// MXGATEWAY_VERIFY_USER_PASSWORD. An empty value from any source is /// treated as absent. The credential is never echoed; this resolver exists so - /// the error-redaction catch block can strip it from any surfaced error - /// (CLI-04), mirroring . + /// the error-redaction catch block can strip it from any surfaced error, + /// mirroring . /// private static string? TryResolveVerifyUserPassword(CliArguments arguments) { @@ -416,7 +416,7 @@ public static class MxGatewayClientCli /// Resolves the verify-user credential for authenticate-user, throwing /// a redaction-safe error when no source yields a non-empty value. Failing /// fast keeps a misconfigured environment from becoming a real MXAccess - /// authentication attempt with an empty credential (CLI-45); the thrown + /// authentication attempt with an empty credential; the thrown /// message names only the option/env var, never the value. /// private static string ResolveVerifyUserPassword(CliArguments arguments) @@ -758,9 +758,9 @@ public static class MxGatewayClientCli { // The credential is resolved from --password or its env var (default // MXGATEWAY_VERIFY_PASSWORD) and is never echoed; a missing or empty value - // fails fast before the invoke rather than reaching the wire (CLI-45). + // fails fast before the invoke rather than reaching the wire. // On any surfaced error the RunCoreAsync catch block routes - // it through MxGatewayCliSecretRedactor so it cannot reach stderr (CLI-04). + // it through MxGatewayCliSecretRedactor so it cannot reach stderr. return InvokeAndWriteAsync( arguments, client, @@ -1425,7 +1425,7 @@ public static class MxGatewayClientCli else if (gatewayEvent.ReplayGap is { } replayGap) { // Render the ReplayGap sentinel as the typed cross-CLI row instead of the raw - // sentinel MxEvent (NEXT-02, mirroring the Go/Python/Rust CLIs). + // sentinel MxEvent, mirroring the Go/Python/Rust CLIs. output.WriteLine(FormatReplayGapRow(replayGap)); } else @@ -1849,7 +1849,7 @@ public static class MxGatewayClientCli } /// - /// Formats the typed ReplayGap row shared by the CLIs (NEXT-02). Hand-built so the + /// Formats the typed ReplayGap row shared by the CLIs. Hand-built so the /// cursors are JSON numbers like the Go/Python/Rust rows, not the protobuf JSON /// formatter's quoted uint64 strings. /// 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 b0b4f3e..43da1f1 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayClientCliTests.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayClientCliTests.cs @@ -134,6 +134,7 @@ public sealed class MxGatewayClientCliTests } /// Verifies that write-secured builds a WriteSecured command with the value and user ids. + /// A task that represents the asynchronous operation. [Fact] public async Task RunAsync_WriteSecured_BuildsWriteSecuredCommand() { @@ -178,6 +179,7 @@ public sealed class MxGatewayClientCliTests /// Verifies that authenticate-user builds an AuthenticateUser command sourcing the /// credential from the flag, and that the credential never appears in stdout/stderr. /// + /// A task that represents the asynchronous operation. [Fact] public async Task RunAsync_AuthenticateUser_BuildsCommandAndDoesNotEchoCredential() { @@ -218,9 +220,10 @@ public sealed class MxGatewayClientCliTests } /// - /// CLI-04: a surfaced error for authenticate-user must have the credential + /// Verifies that a surfaced error for authenticate-user must have the credential /// redacted (never echoed to stderr), mirroring the API-key redaction seam. /// + /// A task that represents the asynchronous operation. [Fact] public async Task RunAsync_AuthenticateUser_ErrorOutput_RedactsCredential() { @@ -248,7 +251,7 @@ public sealed class MxGatewayClientCliTests } /// - /// CLI-45: --password is the primary credential flag, matching the other + /// --password is the primary credential flag, matching the other /// four CLIs. The credential reaches the wire but never stdout/stderr. /// /// A task that represents the asynchronous operation. @@ -290,10 +293,12 @@ public sealed class MxGatewayClientCliTests } /// - /// CLI-45: the credential is read from the environment variable named by + /// the credential is read from the environment variable named by /// --password-env, whose default is the canonical /// MXGATEWAY_VERIFY_PASSWORD shared by all five CLIs. /// + /// The value passed to --password-env, or null to use the default. + /// The environment variable that holds the credential. /// A task that represents the asynchronous operation. [Theory] [InlineData(null, "MXGATEWAY_VERIFY_PASSWORD")] @@ -340,7 +345,7 @@ public sealed class MxGatewayClientCliTests } /// - /// CLI-45: the pre-rename names stay usable for one release — the deprecated + /// the pre-rename names stay usable for one release — the deprecated /// --verify-user-password flag and the deprecated /// MXGATEWAY_VERIFY_USER_PASSWORD environment variable both still resolve. /// @@ -413,7 +418,7 @@ public sealed class MxGatewayClientCliTests } /// - /// CLI-45: a missing or empty credential fails fast before the invoke — the CLI + /// a missing or empty credential fails fast before the invoke — the CLI /// never sends a fabricated empty password to the wire. The error names the flag /// and the environment variable, never a value. /// @@ -463,6 +468,9 @@ public sealed class MxGatewayClientCliTests private readonly string _name; private readonly string? _original; + /// Sets the environment variable and remembers its original value. + /// The environment variable name. + /// The temporary value, or null to clear it. public EnvironmentVariableScope(string name, string? value) { _name = name; @@ -470,6 +478,7 @@ public sealed class MxGatewayClientCliTests Environment.SetEnvironmentVariable(name, value); } + /// Restores the original environment variable value. public void Dispose() { Environment.SetEnvironmentVariable(_name, _original); @@ -588,7 +597,7 @@ public sealed class MxGatewayClientCliTests /// /// Verifies stream-events renders the ReplayGap sentinel as the typed cross-CLI row — - /// numeric cursors under a replayGap key — instead of the raw sentinel MxEvent (NEXT-02). + /// numeric cursors under a replayGap key — instead of the raw sentinel MxEvent. /// /// A task that represents the asynchronous operation. [Fact] 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 8fb4f89..9cf14e7 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayClientSessionTests.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayClientSessionTests.cs @@ -228,6 +228,7 @@ public sealed class MxGatewayClientSessionTests /// with the gap populated, while normal /// events pass through unchanged with IsReplayGap false. /// + /// A task that represents the asynchronous operation. [Fact] public async Task StreamEventItemsAsync_SurfacesReplayGapSentinel() { @@ -430,6 +431,7 @@ public sealed class MxGatewayClientSessionTests } /// Verifies that unregister builds an unregister command with the server handle. + /// A task that represents the asynchronous operation. [Fact] public async Task UnregisterAsync_BuildsUnregisterCommand() { @@ -451,6 +453,7 @@ public sealed class MxGatewayClientSessionTests } /// Verifies that advise-supervisory builds the supervisory advise command. + /// A task that represents the asynchronous operation. [Fact] public async Task AdviseSupervisoryAsync_BuildsAdviseSupervisoryCommand() { @@ -473,6 +476,7 @@ public sealed class MxGatewayClientSessionTests } /// Verifies that add-buffered-item returns the item handle from the typed reply. + /// A task that represents the asynchronous operation. [Fact] public async Task AddBufferedItemAsync_BuildsCommandAndReturnsItemHandle() { @@ -498,6 +502,7 @@ public sealed class MxGatewayClientSessionTests } /// Verifies that set-buffered-update-interval builds the command with the interval. + /// A task that represents the asynchronous operation. [Fact] public async Task SetBufferedUpdateIntervalAsync_BuildsCommandWithInterval() { @@ -520,6 +525,7 @@ public sealed class MxGatewayClientSessionTests } /// Verifies that suspend builds the command and returns the reply status. + /// A task that represents the asynchronous operation. [Fact] public async Task SuspendAsync_BuildsCommandAndReturnsStatus() { @@ -545,6 +551,7 @@ public sealed class MxGatewayClientSessionTests } /// Verifies that activate builds the command and returns the reply status. + /// A task that represents the asynchronous operation. [Fact] public async Task ActivateAsync_BuildsCommandAndReturnsStatus() { @@ -568,6 +575,7 @@ public sealed class MxGatewayClientSessionTests } /// Verifies that write-secured builds a WriteSecured command with value and user ids. + /// A task that represents the asynchronous operation. [Fact] public async Task WriteSecuredAsync_BuildsWriteSecuredCommand() { @@ -598,6 +606,7 @@ public sealed class MxGatewayClientSessionTests /// natively. The client must surface that scripted failure (as an /// ) rather than pre-validating it away. /// + /// A task that represents the asynchronous operation. [Fact] public async Task WriteSecuredAsync_SurfacesNativeFailureWhenNotAuthenticated() { @@ -621,6 +630,7 @@ public sealed class MxGatewayClientSessionTests } /// Verifies that write-secured2 builds a WriteSecured2 command with value and timestamp. + /// A task that represents the asynchronous operation. [Fact] public async Task WriteSecured2Async_BuildsWriteSecured2Command() { @@ -647,6 +657,7 @@ public sealed class MxGatewayClientSessionTests } /// Verifies that authenticate-user builds the command and returns the resolved user id. + /// A task that represents the asynchronous operation. [Fact] public async Task AuthenticateUserAsync_BuildsCommandAndReturnsUserId() { @@ -675,6 +686,7 @@ public sealed class MxGatewayClientSessionTests /// must never contain the credential — the error path is built only from /// reply-derived diagnostics, not the request payload. /// + /// A task that represents the asynchronous operation. [Fact] public async Task AuthenticateUserAsync_FailureDoesNotLeakCredentialInErrorMessage() { @@ -698,6 +710,7 @@ public sealed class MxGatewayClientSessionTests } /// Verifies that archestra-user-to-id builds the command and returns the resolved user id. + /// A task that represents the asynchronous operation. [Fact] public async Task ArchestraUserToIdAsync_BuildsCommandAndReturnsUserId() { diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewaySecretRedactionTests.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewaySecretRedactionTests.cs index ba04c89..1ea5527 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewaySecretRedactionTests.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewaySecretRedactionTests.cs @@ -8,6 +8,7 @@ namespace ZB.MOM.WW.MxGateway.Client.Tests; /// public sealed class MxGatewaySecretRedactionTests { + /// Test: Redact replaces every occurrence of secret. [Fact] public void Redact_ReplacesEveryOccurrenceOfSecret() { @@ -19,6 +20,7 @@ public sealed class MxGatewaySecretRedactionTests Assert.Equal("pw= retry pw= again ", result); } + /// Test: Redact scrubs both secrets when one is substring of the other. [Fact] public void Redact_ScrubsBothSecretsWhenOneIsSubstringOfTheOther() { @@ -33,6 +35,7 @@ public sealed class MxGatewaySecretRedactionTests Assert.DoesNotContain("secret", result, StringComparison.Ordinal); } + /// Test: Redact with null secrets array returns message unchanged. [Fact] public void Redact_WithNullSecretsArray_ReturnsMessageUnchanged() { @@ -43,6 +46,7 @@ public sealed class MxGatewaySecretRedactionTests Assert.Equal(message, result); } + /// Test: Redact with empty secrets array returns message unchanged. [Fact] public void Redact_WithEmptySecretsArray_ReturnsMessageUnchanged() { @@ -53,6 +57,7 @@ public sealed class MxGatewaySecretRedactionTests Assert.Equal(message, result); } + /// Test: Redact ignores whitespace only secret. [Fact] public void Redact_IgnoresWhitespaceOnlySecret() { @@ -64,6 +69,7 @@ public sealed class MxGatewaySecretRedactionTests Assert.Equal(message, result); } + /// Test: Redacted preserves concrete subtype and does not chain secret bearing original. [Fact] public void Redacted_PreservesConcreteSubtypeAndDoesNotChainSecretBearingOriginal() { diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewaySessionReplyContractTests.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewaySessionReplyContractTests.cs index f9c71d0..7e2faba 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewaySessionReplyContractTests.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewaySessionReplyContractTests.cs @@ -4,15 +4,16 @@ using ZB.MOM.WW.MxGateway.Contracts.Proto; namespace ZB.MOM.WW.MxGateway.Client.Tests; /// -/// Tests for the credential-scrub (CLI-40) and malformed-reply (CLI-41) contracts on the +/// Tests for the credential-scrub and malformed-reply contracts on the /// credential and id-returning session helpers, driven from shared behavior fixtures. /// public sealed class MxGatewaySessionReplyContractTests { /// - /// CLI-40: when MXAccess echoes the submitted credential back in its failure diagnostic, + /// Verifies that when MXAccess echoes the submitted credential back in its failure diagnostic, /// the surfaced exception message must scrub it to the library redaction marker. /// + /// A task that represents the asynchronous operation. [Fact] public async Task AuthenticateUserAsync_RedactsEchoedCredentialInFailureMessage() { @@ -33,13 +34,14 @@ public sealed class MxGatewaySessionReplyContractTests } /// - /// CLI-40: the redacted exception must not leak the echoed credential through any structured + /// Verifies that the redacted exception must not leak the echoed credential through any structured /// accessor either — (protocol message, diagnostic /// message, and each MXSTATUS_PROXY diagnostic text) and /// all carry the server-echoed credential verbatim before the fix. Both the OK+negative-HRESULT /// and the MXACCESS_FAILURE reply route to , so both must scrub. /// /// The echoed-credential reply fixture to drive. + /// A task that represents the asynchronous operation. [Theory] [InlineData("authenticate-user.echoed-credential.reply.json")] [InlineData("authenticate-user.echoed-credential-mxaccess-failure.reply.json")] @@ -71,9 +73,10 @@ public sealed class MxGatewaySessionReplyContractTests } /// - /// CLI-41: an OK reply that carries neither the typed AuthenticateUser payload nor an + /// Verifies that an OK reply that carries neither the typed AuthenticateUser payload nor an /// int32 return_value is a malformed reply, surfaced as a typed exception rather than an NRE. /// + /// A task that represents the asynchronous operation. [Fact] public async Task AuthenticateUserAsync_MissingPayloadAndReturnValue_ThrowsMalformedReply() { @@ -87,9 +90,10 @@ public sealed class MxGatewaySessionReplyContractTests } /// - /// CLI-41: an OK reply that omits the typed payload but carries an int32 return_value + /// Verifies that an OK reply that omits the typed payload but carries an int32 return_value /// resolves to that return value. /// + /// A task that represents the asynchronous operation. [Fact] public async Task AuthenticateUserAsync_ReturnValueOnly_ResolvesReturnValue() { @@ -104,9 +108,10 @@ public sealed class MxGatewaySessionReplyContractTests } /// - /// CLI-41: the AddBufferedItem fallback shares the malformed-reply contract — an OK reply + /// Verifies that the AddBufferedItem fallback shares the malformed-reply contract — an OK reply /// with neither a typed item handle nor an int32 return_value throws the typed exception. /// + /// A task that represents the asynchronous operation. [Fact] public async Task AddBufferedItemAsync_MissingPayloadAndReturnValue_ThrowsMalformedReply() { diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxStatusProxyExtensionsTests.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxStatusProxyExtensionsTests.cs index 5c54950..5d62434 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxStatusProxyExtensionsTests.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxStatusProxyExtensionsTests.cs @@ -32,6 +32,9 @@ public sealed class MxStatusProxyExtensionsTests } /// Verifies that the raw success member never overrides the authoritative category. + /// The status category under test. + /// The raw success member, which must not influence the result. + /// The expected IsSuccess result. [Theory] [InlineData(MxStatusCategory.Ok, 0, true)] [InlineData(MxStatusCategory.Ok, 1, true)] diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/WireFakeGatewayServer.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/WireFakeGatewayServer.cs index ce10b50..3cfc9bd 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/WireFakeGatewayServer.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/WireFakeGatewayServer.cs @@ -96,7 +96,8 @@ internal sealed class WireFakeGatewayServer : IAsyncDisposable DefaultCallTimeout = TimeSpan.FromSeconds(30), }); - /// + /// Stops and disposes the underlying web application. + /// A task that represents the asynchronous operation. public async ValueTask DisposeAsync() { await _app.StopAsync().ConfigureAwait(false); diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewaySession.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewaySession.cs index b24512c..8a9e570 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewaySession.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewaySession.cs @@ -854,6 +854,7 @@ public sealed class MxGatewaySession : IAsyncDisposable /// /// The ServerHandle from register. /// Cancellation token. + /// A task that represents the asynchronous operation. public async Task UnregisterAsync( int serverHandle, CancellationToken cancellationToken = default) @@ -888,6 +889,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 AdviseSupervisoryAsync( int serverHandle, int itemHandle, @@ -985,6 +987,7 @@ public sealed class MxGatewaySession : IAsyncDisposable /// The ServerHandle from register. /// The buffered update interval, in milliseconds. /// Cancellation token. + /// A task that represents the asynchronous operation. public async Task SetBufferedUpdateIntervalAsync( int serverHandle, int updateIntervalMilliseconds, @@ -1125,6 +1128,7 @@ public sealed class MxGatewaySession : IAsyncDisposable /// The current operator user id. /// The verifier (secondary approver) user id. /// Cancellation token. + /// A task that represents the asynchronous operation. public async Task WriteSecuredAsync( int serverHandle, int itemHandle, @@ -1204,6 +1208,7 @@ public sealed class MxGatewaySession : IAsyncDisposable /// The current operator user id. /// The verifier (secondary approver) user id. /// Cancellation token. + /// A task that represents the asynchronous operation. public async Task WriteSecured2Async( int serverHandle, int itemHandle, diff --git a/src/ZB.MOM.WW.MxGateway.Contracts/GatewayContractInfo.cs b/src/ZB.MOM.WW.MxGateway.Contracts/GatewayContractInfo.cs index 0d070c1..b459059 100644 --- a/src/ZB.MOM.WW.MxGateway.Contracts/GatewayContractInfo.cs +++ b/src/ZB.MOM.WW.MxGateway.Contracts/GatewayContractInfo.cs @@ -25,7 +25,7 @@ public static class GatewayContractInfo /// (WorkerPipeSession.CreateDrainEventsReply, the backstop that also interprets /// max_events = 0) cannot drift apart. A count cap alone is necessary but not /// sufficient: the worker additionally caps the reply by serialized bytes against the - /// negotiated frame maximum (WRK-21), so a reply may carry fewer events than this ceiling + /// negotiated frame maximum, so a reply may carry fewer events than this ceiling /// and fewer than are queued. Callers drain iteratively until an empty reply. /// This is a documented behavioral bound, not wire schema — it is deliberately a C# /// constant and not a .proto field. diff --git a/src/ZB.MOM.WW.MxGateway.IntegrationTests/DashboardLdapLiveTests.cs b/src/ZB.MOM.WW.MxGateway.IntegrationTests/DashboardLdapLiveTests.cs index fef196f..1220118 100644 --- a/src/ZB.MOM.WW.MxGateway.IntegrationTests/DashboardLdapLiveTests.cs +++ b/src/ZB.MOM.WW.MxGateway.IntegrationTests/DashboardLdapLiveTests.cs @@ -27,7 +27,7 @@ public sealed class DashboardLdapLiveTests private const string SharedDirectoryPassword = "password"; /// - /// Dashboard visibility tags (SEC-25) used by the ACL scenarios below. They are operator-chosen + /// Dashboard visibility tags used by the ACL scenarios below. They are operator-chosen /// labels that exist only in this fixture's configuration and in the fake sessions' owner-tag /// list — nothing in the shared directory carries them. /// @@ -168,7 +168,7 @@ public sealed class DashboardLdapLiveTests } /// - /// Verifies the SEC-25 tag grant end-to-end from a real LDAP bind: gw-viewer's only + /// Verifies the tag grant end-to-end from a real LDAP bind: gw-viewer's only /// group (GwReader) is mapped to the team-a visibility tag by Dashboard:GroupToTag, /// and the principal that bind produces is admitted by for a /// team-a-tagged session but refused for a team-b-tagged one. @@ -275,7 +275,7 @@ public sealed class DashboardLdapLiveTests }; /// - /// The SEC-25 fixture map: GwReader is admitted as a Viewer and granted team-a. Both keys + /// The tagged fixture map: GwReader is admitted as a Viewer and granted team-a. Both keys /// name groups that already exist in the shared directory — the tag layer is config-only. /// private static DashboardOptions TaggedDashboardOptions() => new() diff --git a/src/ZB.MOM.WW.MxGateway.IntegrationTests/WorkerLiveMxAccessSmokeTests.cs b/src/ZB.MOM.WW.MxGateway.IntegrationTests/WorkerLiveMxAccessSmokeTests.cs index a9c2b66..fcc2506 100644 --- a/src/ZB.MOM.WW.MxGateway.IntegrationTests/WorkerLiveMxAccessSmokeTests.cs +++ b/src/ZB.MOM.WW.MxGateway.IntegrationTests/WorkerLiveMxAccessSmokeTests.cs @@ -526,7 +526,7 @@ public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output) // Assert. But a silent fall-through to "operator only" (currentUserId 0) // quietly demotes the rest of this test from an authenticated round-trip // to a bare failure-reply-shape check, which a stale rig credential (e.g. - // TST-31) would otherwise hide indefinitely in nightly runs. Log the + // one that has expired) would otherwise hide indefinitely in nightly runs. Log the // outcome loudly — never the password (verifyUser only) — so that // degradation is visible in the test output even though it never fails // the test by itself. diff --git a/src/ZB.MOM.WW.MxGateway.Server/Alarms/AlarmProviderReasons.cs b/src/ZB.MOM.WW.MxGateway.Server/Alarms/AlarmProviderReasons.cs index 46359b2..d35cacf 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Alarms/AlarmProviderReasons.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Alarms/AlarmProviderReasons.cs @@ -19,7 +19,7 @@ public static class AlarmProviderReasons /// /// Reason prefix set when the effective mode can need the subtag watch-list /// (Auto or ForceSubtag) but the list resolved to zero targets, so the - /// subtag provider covers no alarms (GWC-37). In Auto this is a warning about the + /// subtag provider covers no alarms. In Auto this is a warning about the /// standby, not about the currently-serving alarm manager, which is why the dashboard /// renders it against the ALARMMGR mode as a coverage badge rather than a failover. /// @@ -29,7 +29,7 @@ public static class AlarmProviderReasons /// /// Reason prefix set when Galaxy Repository alarm discovery was enabled but failed, so /// the watch-list carries only what configuration contributed and may be incomplete - /// (GWC-37). Distinct from : discovery failing with a + /// . Distinct from : discovery failing with a /// usable config-only list is a completeness warning, not a total loss of coverage. /// public const string SubtagDiscoveryFailed = @@ -37,7 +37,7 @@ public static class AlarmProviderReasons /// /// Reason prefix set when a re-resolution at the failover edge produced a different - /// watch-list than the one sent at SubscribeAlarms (GWC-37). The worker keeps the + /// watch-list than the one sent at SubscribeAlarms. The worker keeps the /// list it was given for the life of the subscription, so the difference is reported /// rather than applied; a monitor restart re-resolves and re-sends. /// diff --git a/src/ZB.MOM.WW.MxGateway.Server/Alarms/AlarmWatchListResolution.cs b/src/ZB.MOM.WW.MxGateway.Server/Alarms/AlarmWatchListResolution.cs index 8c19eef..47469e6 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Alarms/AlarmWatchListResolution.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Alarms/AlarmWatchListResolution.cs @@ -5,8 +5,8 @@ namespace ZB.MOM.WW.MxGateway.Server.Alarms; /// /// The outcome of one subtag watch-list resolution: the composed targets plus enough /// provenance for the caller to tell an empty list that means "nothing was configured" -/// from one that means "discovery was configured and silently produced nothing" -/// (GWC-37). deliberately never throws +/// from one that means "discovery was configured and silently produced nothing". +/// deliberately never throws /// on a discovery failure, so without this the two are indistinguishable at the call site /// and a Galaxy Repository outage at startup can only be found in a log line. /// diff --git a/src/ZB.MOM.WW.MxGateway.Server/Alarms/AlarmWatchListResolver.cs b/src/ZB.MOM.WW.MxGateway.Server/Alarms/AlarmWatchListResolver.cs index b17c053..4c48017 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Alarms/AlarmWatchListResolver.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Alarms/AlarmWatchListResolver.cs @@ -39,7 +39,7 @@ public sealed class AlarmWatchListResolver : IAlarmWatchListResolver AlarmDiscoveryOptions discovery = options.Fallback.Discovery; - // GWC-37: the caller cannot grade an empty watch-list without knowing whether discovery + // The caller cannot grade an empty watch-list without knowing whether discovery // was asked to contribute and whether it failed while doing so, because the failure is // deliberately swallowed below. string? discoveryFailureMessage = null; diff --git a/src/ZB.MOM.WW.MxGateway.Server/Alarms/GatewayAlarmMonitor.cs b/src/ZB.MOM.WW.MxGateway.Server/Alarms/GatewayAlarmMonitor.cs index 55ee66b..b271c8f 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Alarms/GatewayAlarmMonitor.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Alarms/GatewayAlarmMonitor.cs @@ -41,7 +41,7 @@ public sealed class GatewayAlarmMonitor : BackgroundService, IGatewayAlarmServic // every path that writes _alarms must null this under _sync, or readers keep a stale set. private ActiveAlarmSnapshot[]? _currentAlarmsProjection; - // NEXT-03 dedup tombstones, guarded by _sync: alarm instances whose Clear was synthesized by + // Dedup tombstones, guarded by _sync: alarm instances whose Clear was synthesized by // the most recent reconcile pass, keyed by reference with the instance's original raise // timestamp as the identity marker. A buffered live Clear for the same instance is a duplicate // of the repair and is suppressed. One generation deep: each reconcile pass replaces the map, @@ -57,7 +57,7 @@ public sealed class GatewayAlarmMonitor : BackgroundService, IGatewayAlarmServic private string _providerReason = string.Empty; private DateTimeOffset _providerSince = DateTimeOffset.UtcNow; - // GWC-37 subtag-coverage verdict, guarded by _sync: whether the watch-list the worker is + // Subtag-coverage verdict, guarded by _sync: whether the watch-list the worker is // using can actually cover anything, and why not. Independent of _providerDegraded (which // means "the subtag provider is the one serving"): in Auto mode the alarm manager can be // serving perfectly while the standby behind it covers zero alarms, and that is invisible @@ -258,7 +258,7 @@ public sealed class GatewayAlarmMonitor : BackgroundService, IGatewayAlarmServic try { - // Attach the internal distributor subscriber BEFORE subscribing (GWC-26). The pump + // Attach the internal distributor subscriber BEFORE subscribing. The pump // has been running since MarkReady started the dashboard mirror, and the distributor // only fans to subscribers registered at the time of the fan-out, so a subscriber // taken after SubscribeAlarms + the first reconcile would silently lose every @@ -351,7 +351,7 @@ public sealed class GatewayAlarmMonitor : BackgroundService, IGatewayAlarmServic AlarmProviderMode forcedMode = MapForcedMode(_options.Fallback.Mode); - // GWC-37: grade the coverage before the command goes out, so a Galaxy Repository outage at + // Grade the coverage before the command goes out, so a Galaxy Repository outage at // startup is a standing status on the feed instead of one log line an operator reads weeks // later — when the failover that needed the watch-list finds it empty. Broadcast because // feed subscribers outlive the monitor's worker session and may already be attached. @@ -507,7 +507,7 @@ public sealed class GatewayAlarmMonitor : BackgroundService, IGatewayAlarmServic } } - // NEXT-03: best-effort dedup of the reconcile/live race. A reconcile that already synthesized + // Best-effort dedup of the reconcile/live race. A reconcile that already synthesized // this transition as a feed repair left the cache carrying the worker's transition timestamp // and resulting state — both derived from the same worker-side value the live transition // carries — so an exact (timestamp, state) match means this live transition's outcome has @@ -521,7 +521,7 @@ public sealed class GatewayAlarmMonitor : BackgroundService, IGatewayAlarmServic && existing.CurrentState == incoming.CurrentState; } - // NEXT-03, the Clear leg. A reconcile Clear repair removes the cache entry before the buffered + // The Clear leg of the dedup. A reconcile Clear repair removes the cache entry before the buffered // live Clear drains, so there is no cached state to compare against; the tombstone recorded by // ApplyReconcile identifies the cleared instance by its original raise timestamp instead. The // match consumes the tombstone, so a genuinely new raise/clear cycle (which carries a newer @@ -803,7 +803,7 @@ public sealed class GatewayAlarmMonitor : BackgroundService, IGatewayAlarmServic // Caller holds _sync. Builds an AlarmProviderStatus snapshot of the current state. // - // GWC-37: degraded is the OR of "the subtag provider is the one serving" and "the subtag + // Degraded is the OR of "the subtag provider is the one serving" and "the subtag // watch-list cannot cover anything". The second is what makes an empty watch-list visible in // Auto mode, where the alarm manager is serving happily and the standby behind it is empty. // The reasons are concatenated when both apply, which is the worst case worth naming loudly: @@ -857,7 +857,7 @@ public sealed class GatewayAlarmMonitor : BackgroundService, IGatewayAlarmServic // Replaces the cache with the worker's authoritative snapshot, broadcasting // a synthetic transition for any alarm the live stream missed. // - // Repair scope (GWC-26): presence deltas (Clear/Raise) plus acked-state deltas. These are + // Repair scope: presence deltas (Clear/Raise) plus acked-state deltas. These are // ALARM FEED transitions (AlarmFeedMessage on the StreamAlarms/dashboard surface), rebuilt // from the worker's own authoritative snapshot to repair what the live feed missed. They are // not MxEvents and never reach the gRPC StreamEvents path, so this feed-level repair does not @@ -867,7 +867,7 @@ public sealed class GatewayAlarmMonitor : BackgroundService, IGatewayAlarmServic // reads the worker's current state while the corresponding live transition may still be // buffered in the alarm lease's channel; both would then broadcast, and the two are // indistinguishable on the feed, since nothing serializes a reconcile against the in-flight - // live stream. ApplyTransition narrows that window with a best-effort dedup (NEXT-03): a live + // live stream. ApplyTransition narrows that window with a best-effort dedup: a live // transition whose worker timestamp and resulting state the cache already carries — or whose // Clear matches a tombstone recorded below — was already broadcast as a repair and is // suppressed. The dedup fires only on a positive marker match, so the contract stays diff --git a/src/ZB.MOM.WW.MxGateway.Server/Configuration/GalaxyRepositoryOptionsValidator.cs b/src/ZB.MOM.WW.MxGateway.Server/Configuration/GalaxyRepositoryOptionsValidator.cs index feb632a..7d7b79d 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Configuration/GalaxyRepositoryOptionsValidator.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Configuration/GalaxyRepositoryOptionsValidator.cs @@ -8,7 +8,7 @@ namespace ZB.MOM.WW.MxGateway.Server.Configuration; /// ZB.MOM.WW.GalaxyRepository package binds the options but deliberately ships no validator /// (see A2-galaxyrepository-adoption-handoff.md); the gateway owns the rule because it is the /// process that writes the snapshot. When persistence is on, the snapshot path must be a valid, -/// rooted path on the running host for the same reason the auth DB path must be (SEC-33): a +/// rooted path on the running host for the same reason the auth DB path must be: a /// non-rooted value silently resolves against the launch working directory. /// public sealed class GalaxyRepositoryOptionsValidator : OptionsValidatorBase diff --git a/src/ZB.MOM.WW.MxGateway.Server/Configuration/GatewayConfigPathRules.cs b/src/ZB.MOM.WW.MxGateway.Server/Configuration/GatewayConfigPathRules.cs index 38e72b1..43d17fb 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Configuration/GatewayConfigPathRules.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Configuration/GatewayConfigPathRules.cs @@ -18,7 +18,7 @@ internal static class GatewayConfigPathRules /// against the launch working directory, so the store moves with the CWD and can leak into the /// source tree. Rooting is checked with — the current /// OS — so a Windows drive/UNC literal on a Unix host fails fast at startup rather than being - /// blessed and then written as a junk-named relative file (the SEC-01/SEC-33 mechanism). Reject + /// blessed and then written as a junk-named relative file. Reject /// rather than auto-root; silent relocation of a credential store is worse than a boot error. /// Blank is handled by the caller's required-field check and is not treated as non-rooted here. /// diff --git a/src/ZB.MOM.WW.MxGateway.Server/Configuration/GatewayOptionsValidator.cs b/src/ZB.MOM.WW.MxGateway.Server/Configuration/GatewayOptionsValidator.cs index a0d8496..f103bdc 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Configuration/GatewayOptionsValidator.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Configuration/GatewayOptionsValidator.cs @@ -113,7 +113,7 @@ public sealed class GatewayOptionsValidator : OptionsValidatorBase GatewayConfigPathRules.AddIfNotRooted(value, message, builder); diff --git a/src/ZB.MOM.WW.MxGateway.Server/Configuration/WorkerOptions.cs b/src/ZB.MOM.WW.MxGateway.Server/Configuration/WorkerOptions.cs index 140f2e9..f7c27a5 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Configuration/WorkerOptions.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Configuration/WorkerOptions.cs @@ -61,7 +61,7 @@ public sealed class WorkerOptions /// still fits one worker frame, and at most twice that reserve above it so a /// DrainEvents reply budgeted against this maximum (frame max less two reserves) still /// fits the gRPC send cap it is returned through — otherwise the worker can pack a reply the - /// pipe carries and the gRPC layer refuses, losing the already-drained events (IPC-33). The + /// pipe carries and the gRPC layer refuses, losing the already-drained events. The /// gateway conveys this value to the worker in the handshake /// (GatewayHello.max_frame_bytes). Default is the 16 MB public gRPC cap plus one reserve. /// diff --git a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/AuthenticatedDashboardRoleHandler.cs b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/AuthenticatedDashboardRoleHandler.cs index 8d77bdc..2b7d0c6 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/AuthenticatedDashboardRoleHandler.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/AuthenticatedDashboardRoleHandler.cs @@ -5,7 +5,7 @@ namespace ZB.MOM.WW.MxGateway.Server.Dashboard; /// /// Handler for : succeeds only when the caller /// is authenticated AND holds one of the requirement's roles. It reads no options at all, which -/// is the point — there is no configuration value that can turn this into a bypass (SEC-40). +/// is the point — there is no configuration value that can turn this into a bypass. /// public sealed class AuthenticatedDashboardRoleHandler : AuthorizationHandler diff --git a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/AuthenticatedDashboardRoleRequirement.cs b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/AuthenticatedDashboardRoleRequirement.cs index 8364ec6..fe87d36 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/AuthenticatedDashboardRoleRequirement.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/AuthenticatedDashboardRoleRequirement.cs @@ -13,9 +13,9 @@ namespace ZB.MOM.WW.MxGateway.Server.Dashboard; /// into this one by accident. /// /// -/// Introduced for SEC-40 to gate the alarm feed (AlarmsHub and the /alarms page), +/// Introduced to gate the alarm feed (AlarmsHub and the /alarms page), /// which is gateway-central and session-less and therefore carries no per-session provenance the -/// SEC-25 tag ACL could scope it by. Requiring a real dashboard role claim is the narrowing that +/// tag ACL could scope it by. Requiring a real dashboard role claim is the narrowing that /// is actually available: it keeps galaxy-wide alarm metadata — alarm reference (a full tag path), /// state, severity, category, area, acknowledging operator — away from the read-only environment /// bypasses, which exist for local development and unauthenticated deployments rather than as a diff --git a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAlarmProviderStatus.cs b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAlarmProviderStatus.cs index 19e01b9..176f330 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAlarmProviderStatus.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAlarmProviderStatus.cs @@ -32,7 +32,7 @@ public sealed record DashboardAlarmProviderStatus( /// /// Badge label shown when the alarm manager is serving normally but the subtag standby - /// behind it covers nothing — an empty or undiscoverable watch-list (GWC-37). The feed is + /// behind it covers nothing — an empty or undiscoverable watch-list. The feed is /// healthy today; what is degraded is the fallback that would take over tomorrow, so the /// badge names the standby rather than claiming the feed has failed over. /// @@ -77,7 +77,7 @@ public sealed record DashboardAlarmProviderStatus( && status.Mode == AlarmProviderMode.Subtag && string.Equals(reason, AlarmProviderReasons.ForcedSubtag, StringComparison.Ordinal); - // GWC-37: degraded WITHOUT subtag mode means the alarm manager is still serving and the + // Degraded WITHOUT subtag mode means the alarm manager is still serving and the // standby is the unsound half. "Subtag monitoring (degraded)" would misreport which // provider is live, so this state gets its own label; the amber badge is shared because // the operator action — fix discovery — is equally overdue in both. diff --git a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAuthenticationDefaults.cs b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAuthenticationDefaults.cs index 1a74af2..407a187 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAuthenticationDefaults.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAuthenticationDefaults.cs @@ -35,7 +35,7 @@ public static class DashboardAuthenticationDefaults /// /// Policy guarding the alarm feed — the AlarmsHub SignalR surface and the - /// /alarms page that reads the same central monitor in process (SEC-40). Accepts the + /// /alarms page that reads the same central monitor in process. Accepts the /// same two schemes as but carries /// , a requirement type /// does not handle, so the environment bypasses @@ -49,10 +49,10 @@ public static class DashboardAuthenticationDefaults public const string KeyPrefixClaimType = "mxgateway:key_prefix"; /// - /// Claim carrying one dashboard event-visibility tag the caller is granted (SEC-25). Stamped + /// Claim carrying one dashboard event-visibility tag the caller is granted. Stamped /// at cookie login by and at hub-token mint by /// , both resolving the caller's LDAP groups through - /// MxGateway:Dashboard:GroupToTag. Since SEC-41 reads + /// MxGateway:Dashboard:GroupToTag. reads /// this claim only as a FALLBACK, for a principal that carries no /// claims to re-resolve from (the hub-token principal, whose /// payload carries tags but not groups); a cookie principal is re-resolved against current diff --git a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAuthenticator.cs b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAuthenticator.cs index 8250990..765f89a 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAuthenticator.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAuthenticator.cs @@ -21,7 +21,7 @@ namespace ZB.MOM.WW.MxGateway.Server.Dashboard; /// Maps LDAP groups to dashboard roles. /// /// Gateway options supplying MxGateway:Dashboard:GroupToTag, the map that turns the user's -/// LDAP groups into the dashboard visibility tags stamped on the cookie principal (SEC-25). +/// LDAP groups into the dashboard visibility tags stamped on the cookie principal. /// /// Logger for diagnostic, credential-free login outcomes. public sealed class DashboardAuthenticator( @@ -142,7 +142,7 @@ public sealed class DashboardAuthenticator( claims.AddRange(groupNames.Select(group => new Claim( DashboardAuthenticationDefaults.LdapGroupClaimType, group))); - // Dashboard event-visibility tags (SEC-25). Visibility only — never a data-access grant — + // Dashboard event-visibility tags. Visibility only — never a data-access grant — // and never logged: only the decision, never the tag values, reaches diagnostics. claims.AddRange(DashboardGroupTagMapping .MapGroupsToTags(groupNames, groupToTag) diff --git a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardEndpointRouteBuilderExtensions.cs b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardEndpointRouteBuilderExtensions.cs index b0e4c24..28f8358 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardEndpointRouteBuilderExtensions.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardEndpointRouteBuilderExtensions.cs @@ -107,7 +107,7 @@ public static class DashboardEndpointRouteBuilderExtensions // SignalR hubs. Authorization is enforced on the hub class via [Authorize], so both // policies below accept either the dashboard cookie or a HubToken bearer. Snapshot and // events use HubClientsPolicy; AlarmsHub uses AlarmsPolicy, which additionally requires a - // real authenticated dashboard role (SEC-40) — see AlarmsHub for why the alarm feed is + // real authenticated dashboard role — see AlarmsHub for why the alarm feed is // gated harder than the session-scoped ones. endpoints.MapHub("/hubs/snapshot"); endpoints.MapHub("/hubs/alarms"); diff --git a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardServiceCollectionExtensions.cs b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardServiceCollectionExtensions.cs index 5c753e7..e8632aa 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardServiceCollectionExtensions.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardServiceCollectionExtensions.cs @@ -177,7 +177,7 @@ public static class DashboardServiceCollectionExtensions DashboardAuthenticationDefaults.AuthenticationScheme, DashboardAuthenticationDefaults.HubAuthenticationScheme) .AddRequirements(DashboardAuthorizationRequirement.AnyDashboardRole)); - // SEC-40. Same two schemes as HubClientsPolicy (the alarm feed has an in-page reader + // Same two schemes as HubClientsPolicy (the alarm feed has an in-page reader // and an external hub reader), but a requirement type DashboardAuthorizationHandler // does not handle, so neither Authentication:Mode=Disabled nor anonymous localhost // can satisfy it. Galaxy-wide alarm metadata therefore needs a real dashboard role. diff --git a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardSessionAcl.cs b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardSessionAcl.cs index 5d45c98..7941d64 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardSessionAcl.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardSessionAcl.cs @@ -26,7 +26,7 @@ namespace ZB.MOM.WW.MxGateway.Server.Dashboard; /// (ordinal-ignore-case). /// /// -/// Where the grant comes from (SEC-41). The grant is re-resolved on every call +/// Where the grant comes from. The grant is re-resolved on every call /// from the caller's claims /// through MxGateway:Dashboard:GroupToTag as it stands in the currently bound options — /// the same single call @@ -120,7 +120,7 @@ public sealed class DashboardSessionAcl( if (groups is not null) { - // SEC-41: current options, not the login-time snapshot. MapGroupsToTags returns an + // Current options, not the login-time snapshot. MapGroupsToTags returns an // OrdinalIgnoreCase set, so probing it with the session's tags carries the // case-insensitive comparison in the same direction the claim scan below does. IReadOnlySet granted = DashboardGroupTagMapping.MapGroupsToTags( diff --git a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/HubTokenService.cs b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/HubTokenService.cs index d05d09c..ac41b38 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/HubTokenService.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/HubTokenService.cs @@ -39,7 +39,7 @@ public sealed class HubTokenService // revocable. A short lifetime bounds the exposure window of a token captured from a proxy // or log after logout (the cookie is cleared on logout, but outstanding tokens are not), and // bounds how long a stale role set survives a role change. It now bounds a stale *tag* grant - // the same way (SEC-25): the token carries the tags resolved from the caller's LDAP groups at + // the same way: the token carries the tags resolved from the caller's LDAP groups at // mint time, so revoking a GroupToTag entry takes effect for token-authenticated hub // connections within one lifetime — the natural place the deferred "tokens gain session // binding" note landed. Five minutes is transparent to clients that re-fetch from /hubs/token diff --git a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/Hubs/AlarmsHub.cs b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/Hubs/AlarmsHub.cs index e0e68b2..d48f997 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/Hubs/AlarmsHub.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/Hubs/AlarmsHub.cs @@ -11,9 +11,9 @@ namespace ZB.MOM.WW.MxGateway.Server.Dashboard.Hubs; /// /// /// The feed is galaxy-wide: the alarm monitor is gateway-central and its messages carry no -/// session provenance, so the SEC-25 per-session tag ACL that scopes EventsHub has nothing +/// session provenance, so the per-session tag ACL that scopes EventsHub has nothing /// to intersect here and is deliberately not applied — every authenticated Viewer sees the whole -/// subscription expression's alarm metadata. What SEC-40 tightened instead is the entry gate: +/// subscription expression's alarm metadata. What is tightened instead is the entry gate: /// rather than the /// the other hubs use, so the /// read-only environment bypasses (Authentication:Mode=Disabled, anonymous localhost) no diff --git a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/Hubs/AlarmsHubPublisher.cs b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/Hubs/AlarmsHubPublisher.cs index db8368b..c280d73 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/Hubs/AlarmsHubPublisher.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/Hubs/AlarmsHubPublisher.cs @@ -23,7 +23,7 @@ namespace ZB.MOM.WW.MxGateway.Server.Dashboard.Hubs; /// StreamAlarms subscribers and the alarms page as well, and none of /// those audiences is subject to this dashboard-display flag. /// -/// The broadcast stays deliberately unfiltered (SEC-40). Audience is decided once, at hub +/// The broadcast stays deliberately unfiltered. Audience is decided once, at hub /// admission — carries /// , so only an authenticated caller /// with a real dashboard role is ever in — and the alarm diff --git a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/Hubs/EventsHub.cs b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/Hubs/EventsHub.cs index 700a279..60c3269 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/Hubs/EventsHub.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/Hubs/EventsHub.cs @@ -39,8 +39,8 @@ public sealed class EventsHub( /// The hub-level (HubClientsPolicy) /// only checks that the caller carries one of the dashboard roles, which by /// itself would let any Viewer subscribe to any session id they name. The - /// per-session decision is 's - /// (SEC-25 / TST-15). The admin bypass is evaluated first, so an + /// per-session decision is 's. + /// The admin bypass is evaluated first, so an /// Administrator joins any session id they name; every check below it /// applies to non-Admin callers only. For those: a Viewer sees a session /// only when its tags intersect their granted tags, an untagged session @@ -95,16 +95,12 @@ public sealed class EventsHub( viewerRegistry.RemoveViewer(Context.ConnectionId, sessionId); } - /// - /// Releases every session subscription the dropped connection held. A browser - /// tab that closes never calls , so without - /// this the session would look watched forever and the mirror would keep - /// cloning and sending events to an empty group. - /// - /// The exception that terminated the connection, if any. - /// A task representing the disconnect handling. + /// public override Task OnDisconnectedAsync(Exception? exception) { + // Release every session subscription the dropped connection held. A browser tab that + // closes never calls UnsubscribeSession, so without this the session would look watched + // forever and the mirror would keep cloning and sending events to an empty group. viewerRegistry.ReleaseConnection(Context.ConnectionId); return base.OnDisconnectedAsync(exception); diff --git a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/IDashboardSessionAcl.cs b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/IDashboardSessionAcl.cs index 4d62553..3136653 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/IDashboardSessionAcl.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/IDashboardSessionAcl.cs @@ -4,7 +4,7 @@ namespace ZB.MOM.WW.MxGateway.Server.Dashboard; /// /// Decides whether a dashboard principal may observe one session's mirrored -/// event stream (SEC-25 / TST-15). Consulted at every subscribe seam: the +/// event stream. Consulted at every subscribe seam: the /// SignalR EventsHub.SubscribeSession join and the in-process /// IDashboardSessionEventSubscriber.Subscribe used by the /// session-details page. diff --git a/src/ZB.MOM.WW.MxGateway.Server/GatewayApplication.cs b/src/ZB.MOM.WW.MxGateway.Server/GatewayApplication.cs index e075983..af44744 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/GatewayApplication.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/GatewayApplication.cs @@ -154,8 +154,8 @@ public static class GatewayApplication // having pushed the ambient identity before the library's galaxy service runs. That // ordering is still required, but it is no longer trusted silently: with // Authentication:Mode=ApiKey the provider denies with PermissionDenied and logs the broken - // invariant when the identity is absent, instead of resolving to the full hierarchy - // (GWC-38). Under Mode=Disabled identity-absent is expected and stays unscoped. + // invariant when the identity is absent, instead of resolving to the full hierarchy. + // Under Mode=Disabled identity-absent is expected and stays unscoped. builder.Services.AddSingleton(); @@ -164,7 +164,7 @@ public static class GatewayApplication // snapshot. GalaxyRepositoryOptions.SnapshotCachePath is init-only, so the default cannot be // applied via PostConfigure — supply it as a configuration value (before the bind) when the // shipped config leaves it blank. It resolves to the per-OS CommonApplicationData location, - // byte-identical to the removed appsettings literal on Windows (SEC-33). + // byte-identical to the removed appsettings literal on Windows. if (string.IsNullOrWhiteSpace(builder.Configuration["MxGateway:Galaxy:SnapshotCachePath"])) { builder.Configuration["MxGateway:Galaxy:SnapshotCachePath"] = Path.Combine( @@ -175,7 +175,7 @@ public static class GatewayApplication builder.Services.AddZbGalaxyRepository(builder.Configuration, "MxGateway:Galaxy"); - // Validate that persistence has a valid, host-rooted snapshot path (SEC-33). + // Validate that persistence has a valid, host-rooted snapshot path. builder.Services.AddSingleton< Microsoft.Extensions.Options.IValidateOptions, Configuration.GalaxyRepositoryOptionsValidator>(); @@ -224,7 +224,7 @@ public static class GatewayApplication /// /// /// The location mirrors AuthenticationOptions.SqlitePath so both gateway stores sit - /// together, and the mechanism is the one SEC-33 already used for + /// together, and the mechanism is the same mechanism already used for /// MxGateway:Galaxy:SnapshotCachePath below — same problem, same fix, same file. It also /// matches what docs/GatewayConfiguration.md already tells operators to /// pass to the secret CLI — an absolute default also removes the CLI/gateway divergence diff --git a/src/ZB.MOM.WW.MxGateway.Server/Grpc/EventStreamService.cs b/src/ZB.MOM.WW.MxGateway.Server/Grpc/EventStreamService.cs index ea1cf48..671e933 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Grpc/EventStreamService.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Grpc/EventStreamService.cs @@ -116,7 +116,7 @@ public sealed class EventStreamService( // its `yield return` is what feeds the gRPC writer. ChannelReader reader = subscriber.Reader; - // GWC-15: register this subscriber's channel as a live backlog source instead of + // Register this subscriber's channel as a live backlog source instead of // reconciling the queue-depth gauge on every event. The gauge previously read the // bounded channel's Count (which takes the channel's internal lock) and adjusted the // metric under its own lock on every streamed event. Now the metric reads Count only @@ -197,7 +197,7 @@ public sealed class EventStreamService( } // The queue-depth gauge is maintained lazily via the backlog registration - // above (GWC-15): the metric reads this subscriber's channel Count only when + // above: the metric reads this subscriber's channel Count only when // scraped, so there is no per-event gauge bookkeeping on this hot path. yield return mxEvent; } diff --git a/src/ZB.MOM.WW.MxGateway.Server/Grpc/MxAccessGatewayService.cs b/src/ZB.MOM.WW.MxGateway.Server/Grpc/MxAccessGatewayService.cs index a10699c..1a326d5 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Grpc/MxAccessGatewayService.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Grpc/MxAccessGatewayService.cs @@ -35,7 +35,7 @@ public sealed class MxAccessGatewayService( // The session's owner id and its dashboard-visibility tags both come from the resolved // API key identity, never from the request: the key is the tenant principal, so a - // client cannot label its own session with another tenant's tag (SEC-25). + // client cannot label its own session with another tenant's tag. ApiKeyIdentity? owner = identityAccessor.Current; GatewaySession session = await sessionManager .OpenSessionAsync( @@ -130,7 +130,7 @@ public sealed class MxAccessGatewayService( // Map from the command alone: cloning the whole request only to overwrite its command with // commandToInvoke deep-cloned the (potentially large) original payload for nothing, since - // MapCommand reads nothing but the command (GWC-29). The one clone that matters still + // MapCommand reads nothing but the command. The one clone that matters still // happens inside MapCommand, which is what keeps the worker-bound graph unaliased from // commandToInvoke — the caller still reads it below via TrackCommandReply. WorkerCommand workerCommand = mapper.MapCommand(commandToInvoke); @@ -167,7 +167,7 @@ public sealed class MxAccessGatewayService( .WithCancellation(context.CancellationToken) .ConfigureAwait(false)) { - // GWC-06: measure send latency with the allocation-free timestamp API rather + // Measure send latency with the allocation-free timestamp API rather // than allocating a Stopwatch object per event per subscriber on the highest- // volume gateway path. Stopwatch.GetTimestamp/GetElapsedTime measure the exact // same wall-clock span the former Stopwatch.StartNew()/.Elapsed did. @@ -185,15 +185,6 @@ public sealed class MxAccessGatewayService( } /// - /// - /// Surfaces the public AcknowledgeAlarm RPC. Acknowledgement is - /// session-less: the gateway routes it through the always-on - /// monitor session. An - /// alarm_full_reference that parses as a canonical GUID forwards - /// to AcknowledgeAlarmCommand; a Provider!Group.Tag - /// reference forwards to AcknowledgeAlarmByNameCommand; anything - /// else returns an InvalidRequest diagnostic in the reply. - /// public override async Task AcknowledgeAlarm( AcknowledgeAlarmRequest request, ServerCallContext context) diff --git a/src/ZB.MOM.WW.MxGateway.Server/Grpc/MxAccessGrpcMapper.cs b/src/ZB.MOM.WW.MxGateway.Server/Grpc/MxAccessGrpcMapper.cs index 8ff862b..a035552 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Grpc/MxAccessGrpcMapper.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Grpc/MxAccessGrpcMapper.cs @@ -35,7 +35,7 @@ public sealed class MxAccessGrpcMapper /// /// Maps a gRPC MX command to a worker command. Callers that already hold the command — including /// the constraint pipeline, whose rewritten command is not the one on the request — use this - /// overload rather than cloning a whole request to carry a single field (GWC-29); nothing outside + /// overload rather than cloning a whole request to carry a single field; nothing outside /// the command is read here. /// /// Command payload. @@ -71,7 +71,7 @@ public sealed class MxAccessGrpcMapper }; } - // GWC-07 / IPC-05: ownership transfer, not a deep clone — the same rule MapEvent follows, + // ownership transfer, not a deep clone — the same rule MapEvent follows, // applied to the other (and larger, on bulk reads) hot-path message. The enclosing // WorkerCommandReply is parsed fresh from a single pipe frame in WorkerClient's read loop // and is single-consumer by construction: CompleteCommand's TryRemove hands it to exactly @@ -93,10 +93,10 @@ public sealed class MxAccessGrpcMapper { ArgumentNullException.ThrowIfNull(workerEvent); - // GWC-07 / IPC-05: ownership transfer, not a deep clone. The enclosing WorkerEvent is + // ownership transfer, not a deep clone. The enclosing WorkerEvent is // parsed fresh from a single pipe frame in WorkerClient's read loop and is discarded // immediately after this mapping — the SessionEventDistributor pump is its single - // consumer (GWC-01 claims the worker event channel as single-reader), so nothing else + // consumer (it claims the worker event channel as single-reader), so nothing else // aliases or mutates workerEvent.Event. We therefore move the inner MxEvent into the // outbound graph instead of cloning it. Downstream the pump fans this one MxEvent to // every subscriber and retains it in the replay ring, but that sharing is READ-ONLY diff --git a/src/ZB.MOM.WW.MxGateway.Server/Metrics/GatewayMetrics.cs b/src/ZB.MOM.WW.MxGateway.Server/Metrics/GatewayMetrics.cs index 3f1a7dc..c806121 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Metrics/GatewayMetrics.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Metrics/GatewayMetrics.cs @@ -35,7 +35,7 @@ public sealed class GatewayMetrics : IDisposable private readonly ConcurrentDictionary _eventsBySession = new(StringComparer.Ordinal); private readonly Dictionary _retryAttemptsByArea = new(StringComparer.OrdinalIgnoreCase); - // GWC-15: live backlog sources for the gRPC event-stream queue-depth gauge. Each active + // live backlog sources for the gRPC event-stream queue-depth gauge. Each active // StreamEvents subscriber registers a delegate returning its channel's current backlog; the // gauge sums them ONLY when scraped or projected (GetGrpcEventStreamQueueDepth), so nothing is // read on the per-event streaming hot path. Concurrent because subscribers register/unregister @@ -43,7 +43,7 @@ public sealed class GatewayMetrics : IDisposable private readonly ConcurrentDictionary> _eventStreamBacklogSources = new(); private long _nextEventStreamBacklogSourceId; - // PERF-02: the same pull model for the worker event queue depth. It replaces a pushed scalar that + // the same pull model for the worker event queue depth. It replaces a pushed scalar that // every WorkerClient wrote twice per event (staged, consumed) under _syncRoot — a process-wide // lock on the hottest path, and last-writer-wins across sessions, so the gauge reported one // arbitrary session's backlog instead of the gateway's. Each client registers a source returning @@ -210,7 +210,7 @@ public sealed class GatewayMetrics : IDisposable /// Name of the command method. public void CommandStarted(string method) { - // PERF-02: the three command counters run two-to-three times per gRPC call, so they use + // the three command counters run two-to-three times per gRPC call, so they use // Interlocked rather than _syncRoot — the same idiom as EventReceived. Nothing here needs a // consistent multi-field view; GetSnapshot reads each with Interlocked.Read. Interlocked.Increment(ref _commandsStarted); @@ -281,7 +281,7 @@ public sealed class GatewayMetrics : IDisposable /// Registers a live depth source for the worker event queue-depth gauge and returns a handle /// that removes it when disposed. Each WorkerClient registers once and reports its own /// undelivered (staged + queued) event count, so the gauge is the gateway-wide sum instead of - /// the last value any one session happened to push (PERF-02). + /// the last value any one session happened to push. /// /// /// Returns this worker client's current undelivered event count. Invoked only at collection @@ -303,7 +303,7 @@ public sealed class GatewayMetrics : IDisposable /// returns a handle that removes it when disposed. The gauge sums every registered /// source's current value on demand (when scraped or via ), /// rather than tracking a pushed running total, so the streaming hot path does no - /// per-event gauge bookkeeping (GWC-15). + /// per-event gauge bookkeeping. /// /// /// Returns this subscriber's current channel backlog. Invoked only at collection time; @@ -351,7 +351,7 @@ public sealed class GatewayMetrics : IDisposable { // Tagged by stage only. Neither the key id nor the peer address may become a tag: key ids // are credential material and peer addresses are unbounded cardinality, and /metrics is not - // authenticated (open SEC-14), so anything tagged here is world-readable. + // authenticated, so anything tagged here is world-readable. _authThrottledCounter.Add(1, new KeyValuePair("stage", stage)); } @@ -460,7 +460,7 @@ public sealed class GatewayMetrics : IDisposable { // Compute the live queue depths outside _syncRoot: the sources are the subscriber channels' // Count (their own locks) and the worker clients' interlocked counters, neither of which may - // run under this lock. GWC-15, PERF-02. + // run under this lock. int workerEventQueueDepth = GetWorkerEventQueueDepth(); int grpcEventStreamQueueDepth = GetGrpcEventStreamQueueDepth(); lock (_syncRoot) @@ -521,7 +521,7 @@ public sealed class GatewayMetrics : IDisposable } } - // Sums the undelivered event backlog across every live worker client (PERF-02). + // Sums the undelivered event backlog across every live worker client. private int GetWorkerEventQueueDepth() => SumSources(_workerEventQueueDepthSources); // Sums the live backlog across every registered event-stream subscriber. @@ -571,6 +571,7 @@ public sealed class GatewayMetrics : IDisposable { private int _disposed; + /// Removes the source from its gauge; subsequent calls are no-ops. public void Dispose() { if (Interlocked.Exchange(ref _disposed, 1) == 0) diff --git a/src/ZB.MOM.WW.MxGateway.Server/Security/Audit/AuditDrainService.cs b/src/ZB.MOM.WW.MxGateway.Server/Security/Audit/AuditDrainService.cs index 0d9570f..5bba303 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Security/Audit/AuditDrainService.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Security/Audit/AuditDrainService.cs @@ -39,12 +39,7 @@ public sealed class AuditDrainService( /// Upper bound on how long shutdown waits for the remaining buffered events. private static readonly TimeSpan ShutdownDrainCap = TimeSpan.FromSeconds(2); - /// - /// Bootstraps the audit table, runs one retention sweep, then attaches the drain so the - /// writer switches from synchronous write-through to enqueueing. - /// - /// Token to observe for cancellation. - /// A task that represents the asynchronous operation. + /// public override async Task StartAsync(CancellationToken cancellationToken) { try @@ -64,12 +59,7 @@ public sealed class AuditDrainService( await base.StartAsync(cancellationToken).ConfigureAwait(false); } - /// - /// Detaches the drain (so late writes go straight to the sink) and gives the buffered - /// events a bounded window to reach the store. - /// - /// Token to observe for cancellation. - /// A task that represents the asynchronous operation. + /// public override async Task StopAsync(CancellationToken cancellationToken) { writer.DetachDrain(); diff --git a/src/ZB.MOM.WW.MxGateway.Server/Security/Audit/SqliteCanonicalAuditStore.cs b/src/ZB.MOM.WW.MxGateway.Server/Security/Audit/SqliteCanonicalAuditStore.cs index 67f4bb8..11170ab 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Security/Audit/SqliteCanonicalAuditStore.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Security/Audit/SqliteCanonicalAuditStore.cs @@ -87,11 +87,9 @@ public sealed class SqliteCanonicalAuditStore( } /// - /// - /// One connection, one transaction and one prepared command for the whole batch: the drain - /// pays a single commit for up to events rather - /// than one round-trip per event. - /// + // One connection, one transaction and one prepared command for the whole batch: the drain + // pays a single commit for up to AuditDrainService.MaxBatchSize events rather + // than one round-trip per event. public async Task InsertBatchAsync(IReadOnlyList auditEvents, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(auditEvents); @@ -148,17 +146,15 @@ public sealed class SqliteCanonicalAuditStore( } /// - /// - /// The comparison goes through SQLite's datetime() rather than comparing the stored - /// ISO-8601 text directly. Text comparison is only correct while every row is UTC-normalized - /// ISO-8601 — which guarantees for rows written through - /// this store, but not for rows that entered the table any other way (a repair script, an - /// older schema, a future producer). On a mixed-format column a text comparison silently - /// deletes live audit: 2026-05-17T09:00:00-05:00 is two hours AFTER a - /// 2026-05-17T12:00:00+00:00 cutoff yet sorts before it. Comparing instants is correct - /// regardless of how the text got there, and anything datetime() cannot parse yields - /// NULL and is therefore never deleted — audit that cannot be dated is kept, not swept. - /// + // The comparison goes through SQLite's datetime() rather than comparing the stored + // ISO-8601 text directly. Text comparison is only correct while every row is UTC-normalized + // ISO-8601 — which AuditEvent.OccurredAtUtc guarantees for rows written through + // this store, but not for rows that entered the table any other way (a repair script, an + // older schema, a future producer). On a mixed-format column a text comparison silently + // deletes live audit: 2026-05-17T09:00:00-05:00 is two hours AFTER a + // 2026-05-17T12:00:00+00:00 cutoff yet sorts before it. Comparing instants is correct + // regardless of how the text got there, and anything datetime() cannot parse yields + // NULL and is therefore never deleted — audit that cannot be dated is kept, not swept. public async Task DeleteOlderThanAsync(DateTimeOffset cutoffUtc, CancellationToken cancellationToken) { await using SqliteConnection connection = diff --git a/src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/ApiKeyConstraints.cs b/src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/ApiKeyConstraints.cs index 1cf8418..e01265a 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/ApiKeyConstraints.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/ApiKeyConstraints.cs @@ -13,7 +13,7 @@ public sealed record ApiKeyConstraints( private readonly IReadOnlyList _dashboardTags = Array.Empty(); /// - /// Gets the dashboard event-visibility tags granted to this key (SEC-25). + /// Gets the dashboard event-visibility tags granted to this key. /// /// /// 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 4dcbb6f..2e66215 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/CachingApiKeyVerifier.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/CachingApiKeyVerifier.cs @@ -44,7 +44,7 @@ public interface IApiKeyCacheInvalidator /// evicting so an in-flight verification that started under the old generation discards its own /// repopulation (set-then-recheck in VerifyAsync) — making the "gateway-initiated mutations /// take effect immediately" contract true even against a verify that was already in the inner library -/// when the revoke landed (SEC-34 window 3). +/// when the revoke landed. /// /// /// The short TTL is the backstop for two remaining bounded-staleness windows. (1) Out-of-band @@ -141,7 +141,7 @@ public sealed class CachingApiKeyVerifier : IApiKeyVerifier, IApiKeyCacheInvalid } // Caches a successful verification unless the key was invalidated while the inner verify was in - // flight (SEC-34 window 3, detected via the generation snapshot). The entry lifetime stays the + // flight (detected via the generation snapshot). The entry lifetime stays the // TTL: the library verification identity carries no expiry, so the cache cannot cap at the key's // ExpiresUtc — that window remains TTL-bounded and documented in the class remarks. private void TryCacheSuccess( @@ -202,11 +202,14 @@ public sealed class CachingApiKeyVerifier : IApiKeyVerifier, IApiKeyCacheInvalid ? generation : 0; - // Parses the key id out of a "Bearer mxgw__" header without any store access — - // the same split the authorization interceptor does. Returns null for a header this cache cannot - // attribute to a key id (in which case the generation race-guard is simply not applied). - // - // Correctness of the SEC-34 generation guard rests on parts[1] being the FULL key id: the '_' + /// + /// Parses the key id out of a Bearer mxgw_<keyId>_<secret> header without any store access, + /// using the same split the authorization interceptor does. + /// + /// The raw authorization header value, or . + /// The full key id, or for a header this cache cannot attribute to a key id (the generation race-guard is then not applied). + // Implementation note: + // Correctness of the generation guard rests on parts[1] being the FULL key id: the '_' // split would truncate a key id that itself contained '_', silently disarming the guard for that // key. This is safe because '_' is the token's field delimiter and both — and the only — key // creation paths in the gateway forbid it: ApiKeyAdminCommandLineParser.IsValidKeyId and 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 6269f66..7fbc887 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/ApiKeyFailureLimiter.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/ApiKeyFailureLimiter.cs @@ -19,7 +19,7 @@ namespace ZB.MOM.WW.MxGateway.Server.Security.Authorization; /// source-rotating sprayer that never trips any single partition. /// /// -/// An over-limit state is a valve, not a wall (SEC-31): at most one request per +/// An over-limit state is a valve, not a wall: at most one request per /// is admitted through to the real /// verifier, and a successful verification resets both layers. The reset path therefore stays /// reachable while throttled — the earlier design keyed solely on the attacker-supplied key id and @@ -29,8 +29,8 @@ namespace ZB.MOM.WW.MxGateway.Server.Security.Authorization; /// The tracked partition set is a bounded LRU (). /// Because only a validly shaped token mints a key-id partition and each transport peer may mint at /// most of them (the peer-layer overflow collapses onto that -/// address's fallback partition), the map bounds memory and resists a junk-token flush -/// (SEC-32). A validly shaped presented key id still feeds its cross-peer aggregate when its peer +/// address's fallback partition), the map bounds memory and resists a junk-token flush. +/// A validly shaped presented key id still feeds its cross-peer aggregate when its peer /// layer uses the fallback, so exhausting the per-peer cap cannot bypass distributed-spray /// protection; the aggregate map remains bounded by the same capped eviction policy: /// eviction prefers fully expired windows, never removes an over-limit partition below a transient diff --git a/src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayBrowseScopeProvider.cs b/src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayBrowseScopeProvider.cs index 94186bd..038aed6 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayBrowseScopeProvider.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayBrowseScopeProvider.cs @@ -8,7 +8,7 @@ namespace ZB.MOM.WW.MxGateway.Server.Security.Authorization; /// Scopes Galaxy browse results to the calling API key's BrowseSubtrees constraint. /// -/// Distinguishes two states the pre-GWC-38 code collapsed into one: "authenticated, no +/// Distinguishes two states the earlier code collapsed into one: "authenticated, no /// BrowseSubtrees constraint" (legitimately unscoped — the common case) and "no ambient identity /// at all" (the interceptor-ordering invariant below is broken). The second used to resolve to the /// full hierarchy silently, which made a scoping bypass invisible; it now denies loudly. 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 feaeaaa..345ed4d 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayGrpcAuthorizationInterceptor.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayGrpcAuthorizationInterceptor.cs @@ -140,12 +140,17 @@ public sealed class GatewayGrpcAuthorizationInterceptor( // Token shape mxgw__: the key id is the second underscore-delimited segment (the // secret may itself contain underscores, which does not affect the key id). The shape is checked // before a key-id partition is minted so a spray of invented tokens cannot mint one tracked - // partition each and flush the limiter's bounded map (SEC-32). Anything that fails the check + // partition each and flush the limiter's bounded map. Anything that fails the check // falls back to the sender's transport-peer partition. // // Internal rather than private so the parse rules can be pinned directly by test; the shape - // check is a security boundary (SEC-32) and is worth asserting without routing every case + // check is a security boundary and is worth asserting without routing every case // through a full RPC. + /// + /// Extracts the API key id from a bearer authorization header. + /// + /// The raw authorization header value, or . + /// The key id when the header has the mxgw_<keyId>_<secret> shape; otherwise . internal static string? TryResolveKeyId(string? authorizationHeader) { if (string.IsNullOrWhiteSpace(authorizationHeader)) diff --git a/src/ZB.MOM.WW.MxGateway.Server/Sessions/GatewaySession.cs b/src/ZB.MOM.WW.MxGateway.Server/Sessions/GatewaySession.cs index 5331a81..1de4054 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Sessions/GatewaySession.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Sessions/GatewaySession.cs @@ -39,7 +39,7 @@ public sealed class GatewaySession private bool _everHadEventSubscriber; private SessionEventDistributor? _eventDistributor; private bool _eventDistributorStarted; - // GWC-34: set under _syncRoot when DisposeAsync nulls _eventDistributor, making the + // Set under _syncRoot when DisposeAsync nulls _eventDistributor, making the // "one distributor per session, never reset" lifecycle symmetric at both ends. Without it // EnsureDistributorCreated reads the null as "not created yet" and builds a REPLACEMENT // distributor after teardown — one whose pump can never start (_eventDistributorStarted is a @@ -163,7 +163,7 @@ public sealed class GatewaySession /// is unit-testable. /// /// - /// Dashboard event-visibility tags inherited from the owning API key (SEC-25). Copied into + /// Dashboard event-visibility tags inherited from the owning API key. Copied into /// the immutable set; or empty means untagged. /// The tags come from the owner key, never from the client's wire request, so a client /// cannot label its own session with another tenant's tag. @@ -265,8 +265,8 @@ public sealed class GatewaySession public string? OwnerKeyId { get; } /// - /// Gets the dashboard event-visibility tags this session inherited from its owning API key - /// (SEC-25). An empty set means untagged. + /// Gets the dashboard event-visibility tags this session inherited from its owning API key. + /// An empty set means untagged. /// /// /// Immutable for the session's life — assigned once at construction from the owner key's @@ -541,7 +541,7 @@ public sealed class GatewaySession // concurrent callers (e.g. MarkReady's dashboard mirror and a racing first // AttachEventSubscriber) agree on a single distributor and a single start. // - // GWC-34: after DisposeAsync the null distributor means "gone", not "not built yet", so this + // After DisposeAsync the null distributor means "gone", not "not built yet", so this // throws instead of constructing a replacement nothing will ever pump. The attach paths already // roll back and surface the exception, which is the same SessionNotReady the readiness gates // raise — a caller that loses the race to teardown now fails immediately instead of hanging. @@ -610,7 +610,7 @@ public sealed class GatewaySession /// public IEventSubscriberLease AttachInternalEventSubscriber() { - // Readiness gate, mirroring AttachEventSubscriber (GWC-27). It must run BEFORE + // Readiness gate, mirroring AttachEventSubscriber. It must run BEFORE // EnsureDistributorCreated: a premature attach would construct the distributor and start // its pump against a not-yet-Ready worker, the pump source would throw SessionNotReady, // PumpAsync would complete every subscriber with that error and latch _completed, and @@ -694,7 +694,7 @@ public sealed class GatewaySession // These three calls are OUTSIDE _syncRoot to avoid holding it across // EnsureDistributorCreated's own lock and StartAsync's Task.Run. // - // GWC-34: the state check above can be overtaken by a close+dispose before we get here, and + // The state check above can be overtaken by a close+dispose before we get here, and // EnsureDistributorCreated now says so instead of building an orphan distributor. The // dashboard mirror is a best-effort observability attachment on a session that is already // gone, so it is abandoned here rather than propagated out of MarkReady — the mirror-started @@ -1918,7 +1918,7 @@ public sealed class GatewaySession { distributor = _eventDistributor; _eventDistributor = null; - // GWC-34: latch the null-out so EnsureDistributorCreated cannot read it as + // Latch the null-out so EnsureDistributorCreated cannot read it as // "not created yet" and hand a racing attach a distributor that will never pump. _eventDistributorDisposed = true; } diff --git a/src/ZB.MOM.WW.MxGateway.Server/Sessions/ISessionManager.cs b/src/ZB.MOM.WW.MxGateway.Server/Sessions/ISessionManager.cs index 0470f5e..29dd222 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Sessions/ISessionManager.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Sessions/ISessionManager.cs @@ -19,7 +19,7 @@ public interface ISessionManager /// /// Opens a new gateway session, stamping the owning API key's dashboard event-visibility - /// tags onto it (SEC-25). + /// tags onto it. /// /// Request payload. /// Client identity string. diff --git a/src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionEventDistributor.cs b/src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionEventDistributor.cs index 04212ab..72050a6 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionEventDistributor.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionEventDistributor.cs @@ -520,7 +520,7 @@ public sealed class SessionEventDistributor : IAsyncDisposable { gap = _anyEventSeen && afterSequence < _highestSequenceSeen; - // GWC-25: nothing is retained, but a gapped client still needs a usable resume + // Nothing is retained, but a gapped client still needs a usable resume // anchor. The documented client formula is // after_worker_sequence = oldest_available_sequence - 1, so reporting 0 here made // an unsigned client compute ulong.MaxValue: the follow-up resume then replayed diff --git a/src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionManager.cs b/src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionManager.cs index f4cc712..0ded52e 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionManager.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Sessions/SessionManager.cs @@ -532,7 +532,7 @@ public sealed class SessionManager : ISessionManager // The short prefix and bare guid keep the pipe's Unix-domain-socket path // (TMPDIR + "CoreFxPipe_" + name) inside the 104-byte sun_path limit on // macOS, whose default per-user TMPDIR is ~49 chars; the gateway PID keeps - // the name collision-free across gateway restarts (NEXT-01). + // the name collision-free across gateway restarts. string pipeName = $"mxgw-{Environment.ProcessId}-{sessionUid}"; string nonce = CreateNonce(); DateTimeOffset openedAt = _timeProvider.GetUtcNow(); diff --git a/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerClient.cs b/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerClient.cs index a300af8..873dcdd 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerClient.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerClient.cs @@ -28,7 +28,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. Bounded at 2 × EventChannelCapacity - // (GWC-24): an unbounded staging channel let a consumer that drains slower than the worker + // because an unbounded staging channel let a consumer that drains slower than the worker // produces grow gateway memory without limit and without a fault, because each individual timed // write into _events still completed inside EventChannelFullModeTimeout. A rejected TryWrite here // is the sustained-slow-drain signal and faults the client immediately (ProtocolViolation), which @@ -41,7 +41,7 @@ public sealed class WorkerClient : IWorkerClient private readonly SemaphoreSlim _pendingCommandSlots; private readonly CancellationTokenSource _stopCts = new(); - // PERF-02: this client's contribution to the gateway-wide worker queue-depth gauge. Registered + // this client's contribution to the gateway-wide worker queue-depth gauge. Registered // once here and read only when the gauge is scraped, so staging and consuming an event cost an // Interlocked on _eventQueueDepth and nothing else — previously each of those two hot-path steps // called into GatewayMetrics and took its process-wide lock. Null when metrics are disabled. @@ -233,13 +233,13 @@ public sealed class WorkerClient : IWorkerClient // size the caller controls; checking here keeps a MessageTooLarge in the write loop a // genuine desync signal. // - // PERF-07: this size cannot be handed to WorkerFrameWriter to spare its own + // this size cannot be handed to WorkerFrameWriter to spare its own // CalculateSize. WriteLoopAsync stamps envelope.Sequence immediately before the write - // (GWC-28), and a non-zero varint field grows the encoding — so the number computed here + // and a non-zero varint field grows the encoding — so the number computed here // is a lower bound on the frame the writer actually emits, never the frame length. Passing // it as a knownSize would under-length the prefix and desync the worker's framing. // - // IPC-39: being a lower bound is conservative for the knownSize question but the wrong + // being a lower bound is conservative for the knownSize question but the wrong // direction for this pre-check, which must not admit an envelope the writer will then // reject with a session-fatal MessageTooLarge. Compare against the frame maximum less the // MaxSequenceEncodingBytes the stamp can add, so every envelope this check accepts still @@ -257,7 +257,7 @@ public sealed class WorkerClient : IWorkerClient await EnqueueAsync(commandEnvelope, cancellationToken).ConfigureAwait(false); - // PERF-07: one pooled timer instead of a linked CTS + Task.Delay + WhenAny per command. + // one pooled timer instead of a linked CTS + Task.Delay + WhenAny per command. // Task.WaitAsync arms a TimerQueueTimer on the shared timer queue and cancels it when the // reply lands, so the steady-state cost of a command that replies in time is a single // continuation — the old shape allocated a linked CancellationTokenSource, its @@ -354,7 +354,7 @@ public sealed class WorkerClient : IWorkerClient { await foreach (WorkerEvent workerEvent in _events.Reader.ReadAllAsync(cancellationToken).ConfigureAwait(false)) { - // No metrics call on the hot path: the gauge pulls _eventQueueDepth when scraped (PERF-02). + // No metrics call on the hot path: the gauge pulls _eventQueueDepth when scraped. Interlocked.Decrement(ref _eventQueueDepth); yield return workerEvent; } @@ -420,7 +420,7 @@ public sealed class WorkerClient : IWorkerClient /// A task that represents the asynchronous operation. public async ValueTask DisposeAsync() { - // GWC-32: disposal is the third channel-completing path, and it must enter a terminal state + // disposal is the third channel-completing path, and it must enter a terminal state // like the other two (MarkClosed, SetFaulted) BEFORE the channels are completed. Otherwise a // client disposed while still Ready keeps reporting IsTerminalState() == false, and a read // loop already inside DispatchEnvelope (the _stopCts.Cancel below only interrupts the NEXT @@ -490,7 +490,7 @@ public sealed class WorkerClient : IWorkerClient /// Worst-case number of bytes 's sequence stamp adds to an /// envelope that was sized before it: one tag byte plus a ten-byte varint, the maximum /// encoding of a 64-bit protobuf field. The CommandTooLarge pre-check subtracts this - /// from the negotiated frame maximum (IPC-39) because it measures the envelope while + /// from the negotiated frame maximum because it measures the envelope while /// Sequence is still 0 — an unset field costs nothing to encode, so the measured size /// is a lower bound on the frame the writer emits, and without the allowance an envelope in /// the last eleven bytes of the range would pass the pre-check and then fault the session on @@ -505,12 +505,12 @@ public sealed class WorkerClient : IWorkerClient { await foreach (WorkerEnvelope envelope in _outboundEnvelopes.Reader.ReadAllAsync(_stopCts.Token).ConfigureAwait(false)) { - // GWC-28: stamp the sequence at the point of writing, not when the envelope is built. + // stamp the sequence at the point of writing, not when the envelope is built. // Stamping at construction let two concurrent InvokeAsync callers take 1 and 2 and then // enqueue in the order 2, 1 — non-monotonic on the wire, breaking gateway.md's // "monotonic per sender" contract. This loop is the channel's single consumer // (SingleReader = true), so wire order and stamp order are the same thing here and - // _nextSequence needs no interlocking. Mirrors the worker's WRK-04 fix. + // _nextSequence needs no interlocking. Mirrors the worker's equivalent fix. envelope.Sequence = unchecked(++_nextSequence); await _writer.WriteAsync(envelope, _stopCts.Token).ConfigureAwait(false); } @@ -678,11 +678,11 @@ public sealed class WorkerClient : IWorkerClient /// faults regardless of how backed up the event path is. It returns false in two cases: /// the staging channel has been completed by one of the three completing paths — /// , or , each of - /// which enters a terminal state before completing the channels (GWC-32), so the event is + /// which enters a terminal state before completing the channels, so the event is /// safely dropped because the client is already terminal — or the channel is full at its /// 2 × bound. The latter means the /// consumer has been draining slower than the worker produces for the whole time it took the - /// worker to emit that many further events, so the client is faulted immediately (GWC-24) — + /// worker to emit that many further events, so the client is faulted immediately — /// is non-blocking, so the read loop still never awaits here. The /// complementary full-stall case (a consumer that stops entirely) is caught earlier by the /// timed write in . @@ -753,7 +753,7 @@ public sealed class WorkerClient : IWorkerClient /// the actionable fix (attach StreamEvents or raise /// MxGateway:Events:QueueCapacity). This is the full-stall half of /// the backpressure policy; a consumer that merely drains too slowly is - /// caught by the staging bound in (GWC-24). + /// caught by the staging bound in . /// Queue depth is not counted here — the event was already counted when it /// was staged, so moving it between the two channels changes nothing. /// @@ -851,7 +851,7 @@ public sealed class WorkerClient : IWorkerClient /// /// Fails the pending command for a caller whose own token fired — a client deadline shorter /// than the gateway's command timeout, or a disconnect that cancelled the RPC's token — and - /// forwards a WorkerCancel for the correlation the caller has abandoned (IPC-34). + /// forwards a WorkerCancel for the correlation the caller has abandoned. /// /// Caller cancellation is the common abandonment path, not the rare one, and it leaves the /// worker in exactly the state the timeout arm's cancel exists for: the correlation is still @@ -982,7 +982,7 @@ public sealed class WorkerClient : IWorkerClient { WorkerEnvelope envelope = await _reader.ReadAsync(cancellationToken).ConfigureAwait(false); - // IPC-35: a fault frame during the handshake is the worker reporting a structured startup + // a fault frame during the handshake is the worker reporting a structured startup // failure — most often MXAccess COM creation (not installed, not registered, access denied) // — not a protocol problem. Flattening it to ProtocolViolation discarded the category and // diagnostic exactly where they matter most: first session open on a freshly deployed host, @@ -1303,7 +1303,7 @@ public sealed class WorkerClient : IWorkerClient string correlationId, WorkerCommand command) { - // IPC-05: no second clone. MxAccessGrpcMapper.MapCommand already deep-cloned the command + // no second clone. MxAccessGrpcMapper.MapCommand already deep-cloned the command // out of the caller-owned gRPC MxCommandRequest, so this WorkerCommand is a fresh graph // owned by the invoke pipeline and referenced by no other consumer. The envelope is built // and owned entirely inside WorkerClient and the command is not touched again after this @@ -1340,7 +1340,7 @@ public sealed class WorkerClient : IWorkerClient { // Sequence is deliberately left unset here: WriteLoopAsync stamps it immediately before the // frame goes out, so the numbers are monotonic in wire order however the callers interleave - // between construction and enqueue (GWC-28, mirroring the worker's WRK-04 fix). + // between construction and enqueue (mirroring the worker's fix). WorkerEnvelope envelope = new() { ProtocolVersion = _connection.FrameOptions.ProtocolVersion, diff --git a/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerFrameReader.cs b/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerFrameReader.cs index 1c79432..70964bc 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerFrameReader.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerFrameReader.cs @@ -19,7 +19,7 @@ public sealed class WorkerFrameReader private readonly WorkerFrameProtocolOptions _options; private readonly Stream _stream; - // Reused across frames rather than allocated per read (GWC-30). Safe because ReadAsync is + // Reused across frames rather than allocated per read. Safe because ReadAsync is // single-consumer by construction; the prefix is fully overwritten by every read. private readonly byte[] _lengthPrefix = new byte[sizeof(uint)]; @@ -61,8 +61,8 @@ public sealed class WorkerFrameReader } // Rent the payload buffer from the shared pool rather than allocating a fresh byte[] per - // frame; large event frames near the cap would otherwise allocate an LOH buffer each time - // (IPC-14). ParseFrom copies whatever it needs into the parsed message, so the rented buffer + // frame; large event frames near the cap would otherwise allocate an LOH buffer each time. + // ParseFrom copies whatever it needs into the parsed message, so the rented buffer // can be returned as soon as parsing completes without the envelope aliasing it. The rented // buffer may be larger than requested, so the read and parse are bounded to length. int length = checked((int)payloadLength); diff --git a/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerFrameWriter.cs b/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerFrameWriter.cs index 124e58f..a7f1cf4 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerFrameWriter.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerFrameWriter.cs @@ -57,7 +57,7 @@ public sealed class WorkerFrameWriter // Serialize once into a single pooled buffer that carries the 4-byte little-endian length // prefix followed by the payload, then issue one stream write. This avoids a second // serialization pass (ToByteArray re-runs CalculateSize), a separate prefix array and its - // own write, and any per-frame heap allocation (GWC-08, IPC-13). The rented buffer may be + // own write, and any per-frame heap allocation. The rented buffer may be // larger than requested, so only the first frameLength bytes are ever written. int frameLength = sizeof(uint) + payloadLength; byte[] frame = ArrayPool.Shared.Rent(frameLength); diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Alarms/AlarmWatchListResolverTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Alarms/AlarmWatchListResolverTests.cs index 62f8809..6e89c67 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Alarms/AlarmWatchListResolverTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Alarms/AlarmWatchListResolverTests.cs @@ -19,7 +19,7 @@ public sealed class AlarmWatchListResolverTests /// /// Resolves and projects to the target list. These cases assert composition rules, which /// live in ; the discovery-provenance fields - /// the resolution also carries (GWC-37) are asserted by their own cases below. + /// the resolution also carries are asserted by their own cases below. /// /// The resolver under test. /// Alarm options to resolve against. @@ -259,7 +259,7 @@ public sealed class AlarmWatchListResolverTests } /// - /// GWC-37: swallowing the discovery failure is right for the resolver but leaves the + /// Swallowing the discovery failure is right for the resolver but leaves the /// caller unable to tell "discovery produced nothing" from "nothing was configured", which /// is why the empty watch-list could sit unnoticed for a monitor's whole life. The /// resolution therefore reports whether discovery was attempted, whether it failed, and @@ -284,7 +284,7 @@ public sealed class AlarmWatchListResolverTests /// /// The complement of the case above: discovery disabled is not discovery failed. An empty /// watch-list here means the deployment did not ask for subtag coverage, which the monitor - /// must not report as a degraded standby (GWC-37). + /// must not report as a degraded standby. /// /// A task that represents the asynchronous operation. [Fact] diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Alarms/GatewayAlarmMonitorAttachOrderTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Alarms/GatewayAlarmMonitorAttachOrderTests.cs index f319ae2..fee7c4a 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Alarms/GatewayAlarmMonitorAttachOrderTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Alarms/GatewayAlarmMonitorAttachOrderTests.cs @@ -13,7 +13,7 @@ using ZB.MOM.WW.MxGateway.Tests.TestSupport; namespace ZB.MOM.WW.MxGateway.Tests.Alarms; /// -/// GWC-26 regression tests for the alarm monitor's startup ordering. Unlike the +/// Regression tests for the alarm monitor's startup ordering. Unlike the /// sibling alarm-monitor tests, the session manager here hands the monitor a REAL /// that is driven to Ready with a dashboard mirror, so the /// distributor pump is already running when the monitor attaches — the production @@ -156,7 +156,7 @@ public sealed class GatewayAlarmMonitorAttachOrderTests } /// - /// GWC-35: an acknowledged cached alarm followed by an active snapshot with a newer + /// An acknowledged cached alarm followed by an active snapshot with a newer /// original-raise timestamp is a new alarm instance, not an acknowledgement reversal. The /// reconcile repair must broadcast a Raise carrying the incoming instance identity. /// @@ -216,7 +216,7 @@ public sealed class GatewayAlarmMonitorAttachOrderTests } /// - /// NEXT-03. A reconcile Raise repair applied while the matching live Raise is still + /// A reconcile Raise repair applied while the matching live Raise is still /// buffered must not double-broadcast: the live transition carrying the same worker /// timestamp and resulting state the cache already holds is a duplicate and is suppressed. /// @@ -275,7 +275,7 @@ public sealed class GatewayAlarmMonitorAttachOrderTests } /// - /// NEXT-03, the Clear leg. A reconcile Clear repair removes the cache entry, so the + /// The Clear leg of the dedup. A reconcile Clear repair removes the cache entry, so the /// buffered live Clear is deduped through the tombstone keyed on the instance's original /// raise timestamp — and a genuinely new raise/clear cycle is never swallowed. /// @@ -490,7 +490,7 @@ public sealed class GatewayAlarmMonitorAttachOrderTests SourceProvider = AlarmProviderMode.Alarmmgr, }; - // Snapshot carrying the worker-side identity markers the NEXT-03 dedup compares on. + // Snapshot carrying the worker-side identity markers the dedup compares on. private static ActiveAlarmSnapshot SnapshotAt( AlarmConditionState state, Timestamp lastTransition, @@ -502,7 +502,7 @@ public sealed class GatewayAlarmMonitorAttachOrderTests return snapshot; } - // Live transition with explicit worker timestamps, for driving the NEXT-03 dedup. + // Live transition with explicit worker timestamps, for driving the dedup. private static MxEvent TransitionAt( ulong sequence, AlarmTransitionKind kind, diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Alarms/GatewayAlarmMonitorProviderModeTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Alarms/GatewayAlarmMonitorProviderModeTests.cs index 6e054e0..643d2b6 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Alarms/GatewayAlarmMonitorProviderModeTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Alarms/GatewayAlarmMonitorProviderModeTests.cs @@ -624,7 +624,7 @@ public sealed class GatewayAlarmMonitorProviderModeTests } /// - /// GWC-37: a Galaxy Repository outage at startup used to leave one LogWarning and an + /// A Galaxy Repository outage at startup used to leave one LogWarning and an /// empty subtag watch-list that stayed empty for the monitor's whole life — invisible until /// a failover found the standby covering nothing. The condition is now a standing verdict /// on the provider status, so it reaches the alarm feed, the dashboard badge and every @@ -661,7 +661,7 @@ public sealed class GatewayAlarmMonitorProviderModeTests } /// - /// GWC-37: the failover edge is the moment the watch-list stops being theoretical, so the + /// The failover edge is the moment the watch-list stops being theoretical, so the /// monitor re-resolves it there, hot-swaps the recovered list through /// UpdateAlarmWatchList, and publishes the acknowledged verdict on the same status /// frame the failover produces. The update must precede the status frame so the frame never @@ -748,6 +748,7 @@ public sealed class GatewayAlarmMonitorProviderModeTests } /// Periodic reconciliation applies a recovered watch list once and suppresses an unchanged resolution on the next cadence. + /// A task that represents the asynchronous operation. [Fact] public async Task WatchListRefresh_PeriodicRecovery_SendsExactlyOneMaterialUpdate() { @@ -774,6 +775,7 @@ public sealed class GatewayAlarmMonitorProviderModeTests } /// A rejected refresh keeps the old fingerprint and degraded verdict so the same material change is retried. + /// A task that represents the asynchronous operation. [Fact] public async Task WatchListRefresh_WorkerRejects_RetainsFingerprintAndRetriesSameTargets() { @@ -937,7 +939,7 @@ public sealed class GatewayAlarmMonitorProviderModeTests /// that hands out a scripted sequence of /// resolutions — one per call, the last repeating — and counts the calls, so a test can /// drive "discovery was down at subscribe and back up by the failover" and assert that the - /// second resolution happened at all (GWC-37). + /// second resolution happened at all. /// /// Resolutions to return, in call order. private sealed class ScriptedWatchListResolver(params AlarmWatchListResolution[] script) : IAlarmWatchListResolver @@ -997,6 +999,8 @@ public sealed class GatewayAlarmMonitorProviderModeTests _events.Writer.TryWrite(new WorkerEvent { Event = mxEvent }); /// Pushes a provider-mode change onto the worker event stream. + /// The provider mode being reported. + /// The reason for the mode change. public void EmitModeChange(AlarmProviderMode mode, string reason) => EmitEvent(new MxEvent { OnAlarmProviderModeChanged = new OnAlarmProviderModeChangedEvent @@ -1008,6 +1012,9 @@ public sealed class GatewayAlarmMonitorProviderModeTests }); /// Waits until at least the requested number of update commands have arrived. + /// The minimum number of update commands to wait for. + /// The maximum time to wait. + /// A task that represents the asynchronous operation. public async Task WaitForUpdateCountAsync(int count, TimeSpan timeout) { await WaitUntilAsync(() => UpdateCommands.Count >= count, timeout); diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GalaxyRepositoryOptionsValidatorTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GalaxyRepositoryOptionsValidatorTests.cs index d1d4619..7fc3a1e 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GalaxyRepositoryOptionsValidatorTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GalaxyRepositoryOptionsValidatorTests.cs @@ -8,7 +8,7 @@ using ZB.MOM.WW.MxGateway.Server.Configuration; namespace ZB.MOM.WW.MxGateway.Tests.Configuration; /// -/// Gateway-owned validation of the shared (SEC-33): the Galaxy +/// Gateway-owned validation of the shared : the Galaxy /// package binds the options but ships no validator, so the gateway rejects a persisted snapshot /// whose path is blank, invalid, or non-rooted on the running host, and supplies a rooted per-OS /// default when the shipped config leaves the path blank. @@ -109,7 +109,7 @@ public sealed class GalaxyRepositoryOptionsValidatorTests /// /// Verifies the gateway supplies a rooted per-OS default when the shipped config leaves /// SnapshotCachePath blank, so the removed appsettings literal is not needed and validation - /// passes on any host (SEC-33). Resolving the options triggers the registered validator. + /// passes on any host. Resolving the options triggers the registered validator. /// [Fact] public void Build_DefaultsBlankSnapshotCachePathToRootedDefault() diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GatewayConfigurationProviderTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GatewayConfigurationProviderTests.cs index 3b5a148..32dad0f 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GatewayConfigurationProviderTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GatewayConfigurationProviderTests.cs @@ -9,7 +9,7 @@ namespace ZB.MOM.WW.MxGateway.Tests.Configuration; /// /// /// The projection is hand-written member by member, so an option that exists and validates can -/// still be invisible on the settings page — which is exactly what happened to the two SEC-25 +/// still be invisible on the settings page — which is exactly what happened to the two /// dashboard-ACL options. These tests pin the dashboard-tag members to the options they come /// from; the redaction assertions pin the opposite invariant, that the two secret-bearing members /// are the only ones masked. diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GatewayOptionsTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GatewayOptionsTests.cs index 966cec3..8c471e9 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GatewayOptionsTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GatewayOptionsTests.cs @@ -86,7 +86,7 @@ public sealed class GatewayOptionsTests ["MxGateway:Events:QueueCapacity"] = "256", ["MxGateway:Dashboard:Enabled"] = "false", ["MxGateway:Protocol:MaxGrpcMessageBytes"] = "8388608", - // The frame-headroom rule is two-sided (IPC-33): the worker-frame maximum must sit + // The frame-headroom rule is two-sided: the worker-frame maximum must sit // between one and two 64 KiB envelope reserves above the gRPC cap, so moving the // gRPC cap without moving this one no longer validates. Kept at one reserve above, // the same shape as the shipped default. @@ -175,7 +175,7 @@ public sealed class GatewayOptionsTests /// but only the binder decides whether that instance is populated in place or replaced by a /// default-comparer one. Asserting the comparer on a hand-constructed /// would prove nothing about the configured path; a mis-cased LDAP group name from the directory - /// would then silently grant no tags, and the SEC-25 ACL would deny with no diagnostic. + /// would then silently grant no tags, and the dashboard session ACL would deny with no diagnostic. /// [Fact] public void DashboardOptions_GroupToTag_BindsCaseInsensitively() diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GatewayOptionsValidatorTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GatewayOptionsValidatorTests.cs index 54be27a..cbafda0 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GatewayOptionsValidatorTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GatewayOptionsValidatorTests.cs @@ -778,7 +778,7 @@ public sealed class GatewayOptionsValidatorTests } /// - /// Verifies rooting is host-meaningful (SEC-33): a Windows drive-qualified literal fails on a + /// Verifies rooting is host-meaningful: a Windows drive-qualified literal fails on a /// Unix host (where it is not rooted) rather than being blessed and written as a junk-named /// relative file. On Windows the same literal is genuinely rooted and passes. /// @@ -805,7 +805,7 @@ public sealed class GatewayOptionsValidatorTests } /// - /// Verifies MxGateway:Events:QueueCapacity above int.MaxValue/2 fails (GWC-24 rider): the value + /// Verifies MxGateway:Events:QueueCapacity above int.MaxValue/2 fails: the value /// flows into WorkerClient as checked(2 * EventChannelCapacity), which would otherwise overflow. /// [Fact] @@ -1078,7 +1078,7 @@ public sealed class GatewayOptionsValidatorTests result.Failures!, f => f.Contains("MxGateway:Ldap:ServiceAccountPassword is required when LDAP login is enabled.")); - // SEC-36: the message must steer the operator to the two supported out-of-band channels + // The message must steer the operator to the two supported out-of-band channels // (dev user-secrets, deployed env var) so a blanked/unresolved credential never gets // "fixed" by re-committing a value. Assert.Contains("dotnet user-secrets set", failure); @@ -1258,7 +1258,7 @@ public sealed class GatewayOptionsValidatorTests } /// - /// Verifies the headroom rule's upper half (IPC-33): a worker-frame maximum more than two + /// Verifies the headroom rule's upper half: a worker-frame maximum more than two /// envelope reserves above the gRPC cap is rejected. The worker budgets a DrainEvents reply at /// the frame maximum less two reserves and the gateway returns that same reply through /// Invoke, so a wider gap lets the worker pack a reply the pipe carries and the gRPC send diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Configuration/PreHostSecretExpansionTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Configuration/PreHostSecretExpansionTests.cs index 95725ae..a905a61 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Configuration/PreHostSecretExpansionTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Configuration/PreHostSecretExpansionTests.cs @@ -18,6 +18,10 @@ namespace ZB.MOM.WW.MxGateway.Tests.Configuration; /// public sealed class PreHostSecretExpansionTests { + /// + /// A seeded secret reference in configuration resolves to its plaintext. + /// + /// A task that represents the asynchronous operation. [Fact] public async Task ExpandConfiguration_SeededReference_ResolvesToPlaintext() { @@ -37,6 +41,7 @@ public sealed class PreHostSecretExpansionTests /// ${secret:ldap/mxgateway/bind}. Proves that exact reference at that exact key resolves to /// the seeded plaintext through the same pre-host expander the host runs. /// + /// A task that represents the asynchronous operation. [Fact] public async Task ExpandConfiguration_LdapBindReference_ResolvesToSeededPassword() { @@ -52,6 +57,10 @@ public sealed class PreHostSecretExpansionTests Assert.Equal(bindPassword, config["MxGateway:Ldap:ServiceAccountPassword"]); } + /// + /// A reference to an absent secret throws instead of leaving the token in place. + /// + /// A task that represents the asynchronous operation. [Fact] public async Task ExpandConfiguration_MissingReference_ThrowsFailClosed() { @@ -81,8 +90,11 @@ public sealed class PreHostSecretExpansionTests _envVarName = envVarName; } + /// Gets the secret resolver from the fixture's DI graph. public ISecretResolver Resolver => _provider.GetRequiredService(); + /// Creates a fixture with a unique SQLite store and master-key environment variable. + /// The migrated fixture. public static SecretsFixture Create() { string unique = Guid.NewGuid().ToString("N"); @@ -111,8 +123,13 @@ public sealed class PreHostSecretExpansionTests return new SecretsFixture(provider, sqlitePath, envVarName); } - // Seals a plaintext value under a fresh DEK and upserts it — the same store/cipher pair the - // CLI 'secret set' verb uses. + /// + /// Seals a plaintext value under a fresh DEK and upserts it, using the same store/cipher pair + /// the CLI 'secret set' verb uses. + /// + /// The secret name. + /// The plaintext value to store. + /// A task that represents the asynchronous operation. public async Task SeedAsync(string name, string plaintext) { var secretName = new SecretName(name); @@ -122,11 +139,15 @@ public sealed class PreHostSecretExpansionTests await store.UpsertAsync(sealed_, default); } + /// Builds an in-memory configuration from the given key/value pairs. + /// The configuration entries. + /// The built configuration root. public IConfigurationRoot BuildConfig(params (string Key, string Value)[] entries) => new ConfigurationBuilder() .AddInMemoryCollection(entries.ToDictionary(e => e.Key, e => (string?)e.Value)) .Build(); + /// Disposes the service provider and clears the test environment variable. public void Dispose() { _provider.Dispose(); diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Dashboard/SecretsNavRenderTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Dashboard/SecretsNavRenderTests.cs index 1b4bbb8..fee22f1 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Dashboard/SecretsNavRenderTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Dashboard/SecretsNavRenderTests.cs @@ -15,7 +15,7 @@ namespace ZB.MOM.WW.MxGateway.Tests.Dashboard; /// /// Renders and asserts which policy-gated links the side rail emits — -/// Secrets (secrets:manage) and, since SEC-40, Alarms +/// Secrets (secrets:manage) and Alarms /// (). /// /// @@ -100,7 +100,7 @@ public sealed class SecretsNavRenderTests } /// - /// SEC-40's nav half: an anonymous caller — the read-only localhost path — is not offered the + /// The nav half: an anonymous caller — the read-only localhost path — is not offered the /// Alarms link, because the page and hub now refuse them. Same reasoning as the Secrets /// negative above: this is the assertion that distinguishes a gate from an inert one. /// @@ -114,7 +114,7 @@ public sealed class SecretsNavRenderTests } /// - /// The control for that negative: an authenticated Viewer still gets the Alarms link. SEC-40 + /// The control for that negative: an authenticated Viewer still gets the Alarms link. The Alarms gate /// excludes the environment bypasses, not Viewers — alarm metadata stays galaxy-wide for a /// real dashboard role because the central monitor carries no per-session provenance. /// @@ -135,7 +135,7 @@ public sealed class SecretsNavRenderTests { options.AddSecretsAuthorization(); // The rail's Alarms AuthorizeView resolves this by name; an unregistered policy throws - // rather than denying, so it must be present for any rail render (SEC-40). + // rather than denying, so it must be present for any rail render. options.AddPolicy( DashboardAuthenticationDefaults.AlarmsPolicy, policy => policy.AddRequirements(AuthenticatedDashboardRoleRequirement.AnyDashboardRole)); @@ -163,6 +163,7 @@ public sealed class SecretsNavRenderTests // vacuously. private sealed class StubAuthenticationStateProvider(string[] roles) : AuthenticationStateProvider { + /// public override Task GetAuthenticationStateAsync() { ClaimsIdentity identity = roles.Length == 0 @@ -181,6 +182,7 @@ public sealed class SecretsNavRenderTests // under static rendering. private sealed class StubNavigationManager : NavigationManager { + /// Initializes the stub with a fixed localhost base URI. public StubNavigationManager() => Initialize("https://localhost/", "https://localhost/"); } } diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Dashboard/SettingsPageTagVisibilityRenderTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Dashboard/SettingsPageTagVisibilityRenderTests.cs index 0a99f8e..c75d43d 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Dashboard/SettingsPageTagVisibilityRenderTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Dashboard/SettingsPageTagVisibilityRenderTests.cs @@ -11,7 +11,7 @@ using HtmlRenderer = Microsoft.AspNetCore.Components.Web.HtmlRenderer; namespace ZB.MOM.WW.MxGateway.Tests.Dashboard; /// -/// Renders and asserts the two SEC-25 dashboard-ACL options — +/// Renders and asserts the two dashboard-ACL options — /// Dashboard:GroupToTag and Dashboard:UntaggedSessionVisibility — reach the page. /// /// diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/AlarmFeedAuthorizationTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/AlarmFeedAuthorizationTests.cs index fb85b24..ba571cc 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/AlarmFeedAuthorizationTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/AlarmFeedAuthorizationTests.cs @@ -16,7 +16,7 @@ using AuthenticationOptions = ZB.MOM.WW.MxGateway.Server.Configuration.Authentic namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Dashboard; /// -/// Covers SEC-40: the alarm feed requires a real authenticated dashboard role, so the read-only +/// Covers the rule that the alarm feed requires a real authenticated dashboard role, so the read-only /// environment bypasses (Authentication:Mode=Disabled, anonymous localhost) no longer /// receive galaxy-wide alarm metadata. /// @@ -65,7 +65,7 @@ public sealed class AlarmFeedAuthorizationTests /// /// An authenticated Viewer — the ordinary LDAP-logged-in dashboard user — does satisfy the - /// alarm requirement: SEC-40 excludes the environment bypasses, not Viewers. Alarm metadata + /// alarm requirement: the rule excludes the environment bypasses, not Viewers. Alarm metadata /// stays galaxy-wide for them because the monitor is gateway-central and carries no /// per-session provenance to tag-scope by. /// diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/AlarmsHubPublisherTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/AlarmsHubPublisherTests.cs index 7eb18b3..ea9e9fb 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/AlarmsHubPublisherTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/AlarmsHubPublisherTests.cs @@ -11,7 +11,7 @@ namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Dashboard; /// /// Verifies that honours -/// MxGateway:Dashboard:ShowTagValues (TST-16): the alarm value fields of +/// MxGateway:Dashboard:ShowTagValues: the alarm value fields of /// both value-bearing payload arms are stripped from the copy broadcast to /// browser clients when the flag is off, present when it is on, and the source /// — shared with the gRPC StreamAlarms @@ -286,22 +286,48 @@ public sealed class AlarmsHubPublisherTests /// Gets the capturing client proxy shared by this fake. public CapturingClientProxy GroupProxy { get; } = new(); + /// Gets the shared capturing proxy for all clients. public IClientProxy All => GroupProxy; + /// Returns the shared capturing proxy, ignoring the exclusions. + /// Connection ids to exclude. + /// The shared capturing proxy. public IClientProxy AllExcept(IReadOnlyList excludedConnectionIds) => GroupProxy; + /// Returns the shared capturing proxy for one connection. + /// The target connection id. + /// The shared capturing proxy. public IClientProxy Client(string connectionId) => GroupProxy; + /// Returns the shared capturing proxy for several connections. + /// The target connection ids. + /// The shared capturing proxy. public IClientProxy Clients(IReadOnlyList connectionIds) => GroupProxy; + /// Returns the shared capturing proxy for one group. + /// The target group name. + /// The shared capturing proxy. public IClientProxy Group(string groupName) => GroupProxy; + /// Returns the shared capturing proxy for a group, ignoring the exclusions. + /// The target group name. + /// Connection ids to exclude. + /// The shared capturing proxy. public IClientProxy GroupExcept(string groupName, IReadOnlyList excludedConnectionIds) => GroupProxy; + /// Returns the shared capturing proxy for several groups. + /// The target group names. + /// The shared capturing proxy. public IClientProxy Groups(IReadOnlyList groupNames) => GroupProxy; + /// Returns the shared capturing proxy for one user. + /// The target user id. + /// The shared capturing proxy. public IClientProxy User(string userId) => GroupProxy; + /// Returns the shared capturing proxy for several users. + /// The target user ids. + /// The shared capturing proxy. public IClientProxy Users(IReadOnlyList userIds) => GroupProxy; } @@ -342,9 +368,19 @@ public sealed class AlarmsHubPublisherTests private sealed class NoopGroupManager : IGroupManager { + /// Ignores the membership change and completes synchronously. + /// The connection being added. + /// The group the connection joins. + /// Token to observe for cancellation. + /// A completed task. public Task AddToGroupAsync(string connectionId, string groupName, CancellationToken cancellationToken = default) => Task.CompletedTask; + /// Ignores the removal and completes synchronously. + /// The connection being removed. + /// The group the connection leaves. + /// Token to observe for cancellation. + /// A completed task. public Task RemoveFromGroupAsync(string connectionId, string groupName, CancellationToken cancellationToken = default) => Task.CompletedTask; } diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardBrowseAndAlarmModelTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardBrowseAndAlarmModelTests.cs index 55ca53b..d124bc7 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardBrowseAndAlarmModelTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardBrowseAndAlarmModelTests.cs @@ -180,7 +180,7 @@ public sealed class DashboardBrowseAndAlarmModelTests /// /// An explicitly-degraded status whose mode is still Alarmmgr (the /// Degraded || Mode==Subtag guard's second, independent branch) must map to the - /// amber badge, but not to the failover label: GWC-37 gives that combination a meaning — + /// amber badge, but not to the failover label: that combination has its own meaning — /// the alarm manager is serving and the subtag standby behind it covers nothing — so /// reporting "Subtag monitoring (degraded)" would name the wrong live provider. /// diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardEventBroadcasterTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardEventBroadcasterTests.cs index 55aa942..9e5a0b5 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardEventBroadcasterTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardEventBroadcasterTests.cs @@ -9,7 +9,7 @@ namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Dashboard; /// /// Verifies that honours -/// MxGateway:Dashboard:ShowTagValues (SEC-25): tag values are stripped +/// MxGateway:Dashboard:ShowTagValues: tag values are stripped /// from the mirrored copy when the flag is off, present when it is on, and the /// shared source event is never mutated. Also verifies the viewer gate — the /// mirror does no work at all for a session nobody is watching. @@ -325,22 +325,48 @@ public sealed class DashboardEventBroadcasterTests /// Gets the capturing client proxy shared by this fake. public CapturingClientProxy GroupProxy { get; } = new(); + /// Gets the shared capturing proxy for all connections. public IClientProxy All => GroupProxy; + /// Returns the shared capturing proxy, ignoring exclusions. + /// Ignored. + /// The shared capturing proxy. public IClientProxy AllExcept(IReadOnlyList excludedConnectionIds) => GroupProxy; + /// Returns the shared capturing proxy for any connection. + /// Ignored. + /// The shared capturing proxy. public IClientProxy Client(string connectionId) => GroupProxy; + /// Returns the shared capturing proxy for any connection set. + /// Ignored. + /// The shared capturing proxy. public IClientProxy Clients(IReadOnlyList connectionIds) => GroupProxy; + /// Returns the shared capturing proxy for any group. + /// Ignored. + /// The shared capturing proxy. public IClientProxy Group(string groupName) => GroupProxy; + /// Returns the shared capturing proxy for any group, ignoring exclusions. + /// Ignored. + /// Ignored. + /// The shared capturing proxy. public IClientProxy GroupExcept(string groupName, IReadOnlyList excludedConnectionIds) => GroupProxy; + /// Returns the shared capturing proxy for any group set. + /// Ignored. + /// The shared capturing proxy. public IClientProxy Groups(IReadOnlyList groupNames) => GroupProxy; + /// Returns the shared capturing proxy for any user. + /// Ignored. + /// The shared capturing proxy. public IClientProxy User(string userId) => GroupProxy; + /// Returns the shared capturing proxy for any user set. + /// Ignored. + /// The shared capturing proxy. public IClientProxy Users(IReadOnlyList userIds) => GroupProxy; } @@ -367,9 +393,19 @@ public sealed class DashboardEventBroadcasterTests private sealed class NoopGroupManager : IGroupManager { + /// Ignores the group membership change. + /// Ignored. + /// Ignored. + /// Ignored. + /// A completed task. public Task AddToGroupAsync(string connectionId, string groupName, CancellationToken cancellationToken = default) => Task.CompletedTask; + /// Ignores the group membership change. + /// Ignored. + /// Ignored. + /// Ignored. + /// A completed task. public Task RemoveFromGroupAsync(string connectionId, string groupName, CancellationToken cancellationToken = default) => Task.CompletedTask; } diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardLiveDataServiceTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardLiveDataServiceTests.cs index 9079e5f..820e29b 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardLiveDataServiceTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardLiveDataServiceTests.cs @@ -20,6 +20,7 @@ public sealed class DashboardLiveDataServiceTests /// /// Verifies a tag already in the advise set is not subscribed again on a later read. /// + /// A task that represents the asynchronous operation. [Fact] public async Task ReadAsync_WhenTagAlreadySubscribed_DoesNotResubscribe() { @@ -41,6 +42,7 @@ public sealed class DashboardLiveDataServiceTests /// Verifies the advise set is capped: subscribing past the cap unadvises the /// least-recently-read tag on the worker and leaves the re-read tag advised. /// + /// A task that represents the asynchronous operation. [Fact] public async Task ReadAsync_PastCap_EvictsLeastRecentlyReadTag() { @@ -78,6 +80,7 @@ public sealed class DashboardLiveDataServiceTests /// than the cap keeps them all (a read never evicts a tag it is about to return), /// and the next read that subscribes anything squeezes the overshoot back out. /// + /// A task that represents the asynchronous operation. [Fact] public async Task ReadAsync_WithMoreDistinctTagsThanCap_KeepsThemAllThenSelfCorrects() { @@ -108,6 +111,7 @@ public sealed class DashboardLiveDataServiceTests /// touches nearly the whole advise set evicts only the untouched remainder, ends over /// the cap, and the following read trims it back. /// + /// A task that represents the asynchronous operation. [Fact] public async Task ReadAsync_WhenTouchedTagsFillTheCap_EvictsOnlyUntouchedTags() { @@ -135,6 +139,7 @@ public sealed class DashboardLiveDataServiceTests /// Verifies a tag the worker failed to advise still occupies a slot but is evicted /// without any unsubscribe command — there is no item handle to unadvise. /// + /// A task that represents the asynchronous operation. [Fact] public async Task ReadAsync_WhenAdviseFailed_EvictsTagWithoutUnsubscribing() { @@ -165,6 +170,7 @@ public sealed class DashboardLiveDataServiceTests /// Verifies a failed unadvise of an evicted tag does not fail the read, and the /// evicted tag is dropped from tracking anyway. /// + /// A task that represents the asynchronous operation. [Fact] public async Task ReadAsync_WhenEvictionUnsubscribeFails_StillCompletesRead() { @@ -190,10 +196,11 @@ public sealed class DashboardLiveDataServiceTests /// /// Verifies the /browse live-value seam honours - /// MxGateway:Dashboard:ShowTagValues (TST-16): with the flag off — the + /// MxGateway:Dashboard:ShowTagValues: with the flag off — the /// default — the value text the page renders is the redaction placeholder, /// never the formatted tag value. /// + /// A task that represents the asynchronous operation. [Fact] public async Task ReadAsync_WhenShowTagValuesFalse_RedactsValueTextButKeepsMetadata() { @@ -220,6 +227,7 @@ public sealed class DashboardLiveDataServiceTests } /// Verifies the formatted value is served when the flag is on. + /// A task that represents the asynchronous operation. [Fact] public async Task ReadAsync_WhenShowTagValuesTrue_ServesFormattedValue() { @@ -243,6 +251,7 @@ public sealed class DashboardLiveDataServiceTests /// withheld, and its diagnostic still reaches the panel. Redacting it would /// contradict the error the same row displays. /// + /// A task that represents the asynchronous operation. [Fact] public async Task ReadAsync_WhenReadFailedAndShowTagValuesFalse_LeavesPlaceholderUnredacted() { @@ -489,7 +498,8 @@ public sealed class DashboardLiveDataServiceTests { } - /// + /// Completes immediately because the fake holds no resources. + /// A completed value task. public ValueTask DisposeAsync() => ValueTask.CompletedTask; private BulkSubscribeReply Subscribe(IEnumerable tagAddresses) diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardSessionAclTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardSessionAclTests.cs index 5d872ca..f9af367 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardSessionAclTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardSessionAclTests.cs @@ -10,7 +10,7 @@ namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Dashboard; /// /// Covers , the single decision both dashboard subscribe seams -/// consult (SEC-25 / TST-15). +/// consult. /// /// /// Every branch is asserted in its denying direction as well as its allowing one, because the @@ -77,6 +77,7 @@ public sealed class DashboardSessionAclTests } /// A blank session id is denied without consulting anything. + /// A blank or whitespace-only session id. [Theory] [InlineData("")] [InlineData(" ")] @@ -146,8 +147,8 @@ public sealed class DashboardSessionAclTests } /// - /// A principal carrying no tag claims — the anonymous-localhost / empty-grant Viewer of - /// SEC-02 — sees a tagged session never, and an untagged one only when the operator opted + /// A principal carrying no tag claims — the anonymous-localhost / empty-grant Viewer — + /// sees a tagged session never, and an untagged one only when the operator opted /// into . /// [Fact] @@ -179,7 +180,7 @@ public sealed class DashboardSessionAclTests } /// - /// SEC-41, the load-bearing regression: a cookie-style principal whose tag claim was stamped + /// The load-bearing regression: a cookie-style principal whose tag claim was stamped /// at login under an older GroupToTag map is DENIED once that map no longer grants the /// tag to any group the principal belongs to. The auth cookie survives the restart that /// rebinds options, so trusting the stamped claim let a config-revoked grant live on for the @@ -224,7 +225,7 @@ public sealed class DashboardSessionAclTests /// /// Re-resolution is ordinal-ignore-case on both sides — the group key against the map (which /// owns) and the resolved tag against the session's tag - /// set. Pinned because the pre-SEC-41 claim scan got the second comparison from + /// set. Pinned because the earlier claim scan got the second comparison from /// Session.Tags's own comparer, and the re-resolution arm probes the grant set in the /// opposite direction. /// @@ -294,7 +295,7 @@ public sealed class DashboardSessionAclTests } /// - /// Builds a dashboard principal. is the SEC-41 discriminator: a + /// Builds a dashboard principal. is the discriminator: a /// principal WITH group claims is re-resolved against the current map (the cookie seam), one /// WITHOUT falls back to its stamped tag claims (the hub-token seam). Existing cases pass /// tags only, so they continue to exercise the fallback arm exactly as before. diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/EventsHubTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/EventsHubTests.cs index b0f2b83..1874723 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/EventsHubTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/EventsHubTests.cs @@ -7,7 +7,7 @@ using ZB.MOM.WW.MxGateway.Server.Dashboard.Hubs; namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Dashboard; /// -/// Covers the ACL gate on (SEC-25 / TST-15). +/// Covers the ACL gate on . /// /// /// The denial assertions are the load-bearing ones — before the gate existed every caller was @@ -131,7 +131,11 @@ public sealed class EventsHubTests /// Gets the (connection id, group name) pairs added, in order. public List<(string ConnectionId, string GroupName)> Added { get; } = []; - /// + /// Records the group membership and completes synchronously. + /// The connection being added. + /// The group the connection joins. + /// Token to observe for cancellation. + /// A completed task. public Task AddToGroupAsync(string connectionId, string groupName, CancellationToken cancellationToken = default) { Added.Add((connectionId, groupName)); @@ -139,7 +143,11 @@ public sealed class EventsHubTests return Task.CompletedTask; } - /// + /// Ignores the removal and completes synchronously. + /// The connection being removed. + /// The group the connection leaves. + /// Token to observe for cancellation. + /// A completed task. public Task RemoveFromGroupAsync( string connectionId, string groupName, diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/EventsHubViewerRegistryTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/EventsHubViewerRegistryTests.cs index e220b9c..acd5712 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/EventsHubViewerRegistryTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/EventsHubViewerRegistryTests.cs @@ -129,6 +129,8 @@ public sealed class EventsHubViewerRegistryTests } /// Blank connection or session ids are ignored rather than tracked. + /// The hub connection id, possibly blank. + /// The session id, possibly blank. [Theory] [InlineData("", "session-1")] [InlineData(" ", "session-1")] 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 7846fd3..b48cf51 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/HubTokenServiceTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/HubTokenServiceTests.cs @@ -178,7 +178,7 @@ public sealed class HubTokenServiceTests } /// - /// The dashboard visibility grant (SEC-25) survives the mint/validate round-trip: tags are + /// The dashboard visibility grant survives the mint/validate round-trip: tags are /// resolved from the caller's LDAP-group claims through Dashboard:GroupToTag at /// and rehydrated as /// claims on the principal diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/SessionDetailsPageEventAclTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/SessionDetailsPageEventAclTests.cs index 1480d26..1bb16b5 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/SessionDetailsPageEventAclTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/SessionDetailsPageEventAclTests.cs @@ -16,7 +16,7 @@ using ZB.MOM.WW.MxGateway.Server.Dashboard.Hubs; namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Dashboard; /// -/// Covers the ACL gate on the session-details page's in-process subscribe seam (SEC-25 / TST-15). +/// Covers the ACL gate on the session-details page's in-process subscribe seam. /// /// /// @@ -214,7 +214,7 @@ public sealed class SessionDetailsPageEventAclTests /// public ChannelReader Reader => _channel.Reader; - /// + /// Marks the subscription as released and completes its channel. public void Dispose() { IsDisposed = true; diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/GatewayApplicationTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/GatewayApplicationTests.cs index a20ffbc..1ebfbad 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/GatewayApplicationTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/GatewayApplicationTests.cs @@ -81,7 +81,7 @@ public sealed class GatewayApplicationTests { // Bind an ephemeral port (:0) — xUnit runs test collections in parallel, so any // started-host test must avoid a fixed port to prevent a bind collision. The auth SQLite - // store path is isolated to a per-process temp file by TestHostEnvironmentInitializer (SEC-33). + // store path is isolated to a per-process temp file by TestHostEnvironmentInitializer. await using WebApplication app = GatewayApplication.Build(["--urls=http://127.0.0.1:0"]); await app.StartAsync(); try @@ -266,7 +266,7 @@ public sealed class GatewayApplicationTests { // Bind an ephemeral port (:0) — xUnit runs test collections in parallel, so any // WebApplication-building test must avoid a fixed port to prevent a bind collision. The auth - // store path is isolated by TestHostEnvironmentInitializer (SEC-33), so startup opens a + // store path is isolated by TestHostEnvironmentInitializer, so startup opens a // writable store and the injected misconfiguration is what fails validation. await using WebApplication app = GatewayApplication.Build( [$"--{key}={value}", "--urls=http://127.0.0.1:0"]); diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/GatewayEndToEndReconnectReplayTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/GatewayEndToEndReconnectReplayTests.cs index 7bfa802..df3c53a 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/GatewayEndToEndReconnectReplayTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/GatewayEndToEndReconnectReplayTests.cs @@ -19,7 +19,7 @@ namespace ZB.MOM.WW.MxGateway.Tests.Gateway; /// /// End-to-end reconnect/replay tests through the real gRPC StreamEvents path via the -/// fake worker harness (TST-01, server half). A single subscriber detaches (cancel + await +/// fake worker harness (server half). A single subscriber detaches (cancel + await /// the stream task so its lease is fully disposed), the session is retained by detach-grace /// while the distributor pump keeps appending worker events to the replay ring, and a second /// stream reconnects with . Covers both @@ -233,7 +233,7 @@ public sealed class GatewayEndToEndReconnectReplayTests } /// - /// GWC-25 regression, end to end: when age eviction has emptied the replay ring, the + /// Regression, end to end: when age eviction has emptied the replay ring, the /// ReplayGap sentinel carries the next deliverable sequence (highest observed + 1), /// so a client applying the documented /// after_worker_sequence = oldest_available_sequence - 1 formula on its follow-up @@ -305,7 +305,7 @@ public sealed class GatewayEndToEndReconnectReplayTests Assert.NotNull(sentinel.ReplayGap); Assert.Equal(staleCursor, sentinel.ReplayGap.RequestedAfterSequence); - // The whole point of GWC-25: the sentinel anchors the resume at the next sequence that + // The whole point of the fix: the sentinel anchors the resume at the next sequence that // can still be delivered, not at 0. Assert.Equal(highestSeen + 1, sentinel.ReplayGap.OldestAvailableSequence); diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Grpc/GalaxyRepositoryHostWiringTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Grpc/GalaxyRepositoryHostWiringTests.cs index 0178369..2d2ffe1 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Grpc/GalaxyRepositoryHostWiringTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Grpc/GalaxyRepositoryHostWiringTests.cs @@ -28,7 +28,7 @@ public sealed class GalaxyRepositoryHostWiringTests /// Verifies the full production authz-filtering chain end-to-end. /// Phase 1: an identity carrying no BrowseSubtrees pushed → BrowseChildren /// returns both children (unconstrained). That, not an absent identity, is production's - /// unscoped case since GWC-38 — with Authentication:Mode=ApiKey an absent identity + /// unscoped case — with Authentication:Mode=ApiKey an absent identity /// now denies, which the isolated provider test pins. /// Phase 2: identity with BrowseSubtrees = ["NonExistent"] /// pushed via → the real diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Grpc/MxAccessGatewayServiceTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Grpc/MxAccessGatewayServiceTests.cs index 6a82b5d..15c5493 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Grpc/MxAccessGatewayServiceTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Grpc/MxAccessGatewayServiceTests.cs @@ -52,7 +52,7 @@ public sealed class MxAccessGatewayServiceTests /// /// Verifies OpenSession forwards the calling key's dashboard-visibility tags, so the - /// session's tags are derived from the owning API key rather than the wire request (SEC-25). + /// session's tags are derived from the owning API key rather than the wire request. /// /// A task that represents the asynchronous operation. [Fact] diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Grpc/MxAccessGrpcMapperTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Grpc/MxAccessGrpcMapperTests.cs index ebf4957..c82424d 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Grpc/MxAccessGrpcMapperTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Grpc/MxAccessGrpcMapperTests.cs @@ -41,7 +41,7 @@ public sealed class MxAccessGrpcMapperTests /// /// The command-only overload exists so Invoke does not deep-clone the whole request just to - /// overwrite and discard its command (GWC-29). It must still perform the one clone that keeps the + /// overwrite and discard its command. It must still perform the one clone that keeps the /// worker-bound graph unaliased from the caller-owned gRPC command, and must produce the same /// as the request overload. /// @@ -145,7 +145,7 @@ public sealed class MxAccessGrpcMapperTests } /// - /// Verifies MapEvent transfers ownership of the inner MxEvent (GWC-07 / IPC-05): the + /// Verifies MapEvent transfers ownership of the inner MxEvent: the /// returned reference is the same instance carried by the WorkerEvent, not a clone. The /// WorkerEvent is discarded after mapping and the distributor pump is its single consumer, /// so moving the inner event out is safe and avoids a per-event deep copy. diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/GatewaySessionTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/GatewaySessionTests.cs index 5581103..8d07108 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/GatewaySessionTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/GatewaySessionTests.cs @@ -669,7 +669,7 @@ public sealed class GatewaySessionTests } /// - /// GWC-27: must refuse to + /// must refuse to /// attach before the session is Ready. Without the gate the attach would construct and /// start the distributor against a not-yet-Ready worker; the pump source throws /// SessionNotReady, every subscriber is completed with that error, and the @@ -720,7 +720,7 @@ public sealed class GatewaySessionTests } /// - /// GWC-34, the disposal-side twin of the GWC-27 premature-attach poison: an attach that + /// The disposal-side twin of the premature-attach poison: an attach that /// passed its readiness gate and then lost the CPU until the session was torn down used to /// find _eventDistributor null, read that as "not created yet" and build a /// REPLACEMENT distributor. Its pump could never start (the started flag is a one-way latch) diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/SessionEventDistributorTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/SessionEventDistributorTests.cs index 5220ac3..fdbe7c5 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/SessionEventDistributorTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/SessionEventDistributorTests.cs @@ -843,7 +843,7 @@ public sealed class SessionEventDistributorTests } /// - /// GWC-25: when age eviction has emptied the replay ring, a resume behind the highest + /// When age eviction has emptied the replay ring, a resume behind the highest /// observed sequence still reports a gap, and the reported oldest-available sequence is /// the next sequence that can possibly be delivered (highest seen + 1) — never 0, /// which would make the client's documented oldest - 1 resume formula wrap to @@ -889,7 +889,7 @@ public sealed class SessionEventDistributorTests } /// - /// GWC-25: the same next-deliverable-sequence rule applies when retention is disabled + /// The same next-deliverable-sequence rule applies when retention is disabled /// outright (replay capacity 0) — the ring is always empty, so a behind cursor must still /// get a usable resume anchor rather than 0. /// @@ -924,7 +924,7 @@ public sealed class SessionEventDistributorTests } /// - /// GWC-25 regression: a client that applies the documented + /// Regression: a client that applies the documented /// after_worker_sequence = oldest_available_sequence - 1 formula to an empty-ring /// ReplayGap resumes with a live watermark equal to the highest observed sequence, so the /// caller's live filter passes every subsequent event. Under the pre-fix 0 the diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/SessionManagerTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/SessionManagerTests.cs index c0405df..0a04f13 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/SessionManagerTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/SessionManagerTests.cs @@ -41,7 +41,7 @@ public sealed class SessionManagerTests /// Verifies the pipe name stays short enough that its Unix-domain-socket path /// (TMPDIR + "CoreFxPipe_" + name) fits the 104-byte macOS sun_path limit under the /// default per-user TMPDIR (~49 chars), and keeps the pid + session-guid uniqueness - /// contract (NEXT-01). + /// contract. /// /// A task that represents the asynchronous operation. [Fact] @@ -110,7 +110,7 @@ public sealed class SessionManagerTests } /// - /// Verifies a session inherits the owning API key's dashboard-visibility tags (SEC-25), + /// Verifies a session inherits the owning API key's dashboard-visibility tags, /// compared ordinal-ignore-case so a differently cased grant still matches. /// /// A task that represents the asynchronous operation. diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/SessionWorkerClientFactoryFakeWorkerTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/SessionWorkerClientFactoryFakeWorkerTests.cs index c8f7047..3dd5ea2 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/SessionWorkerClientFactoryFakeWorkerTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/SessionWorkerClientFactoryFakeWorkerTests.cs @@ -24,7 +24,10 @@ public sealed class SessionWorkerClientFactoryFakeWorkerTests : IAsyncLifetime private readonly List _launchers = []; - /// + /// + /// Completes immediately; no asynchronous setup is required before each test. + /// + /// A task that represents the asynchronous operation. public Task InitializeAsync() => Task.CompletedTask; /// 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 89084bd..ec1824e 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Workers/WorkerClientTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Workers/WorkerClientTests.cs @@ -36,10 +36,10 @@ public sealed class WorkerClientTests /// /// The GatewayHello carries the negotiated worker-frame maximum so the worker adopts the - /// configured limit instead of its own default (IPC-02); a regression to sending 0 means + /// configured limit instead of its own default; a regression to sending 0 means /// "older gateway, use default" to the worker and would silently downgrade the negotiated limit. /// The adoption half is asserted only in the Windows-only worker suite, so the gateway half is - /// pinned here, in the portable suite (TST-28). Both the default and an override are covered so + /// pinned here, in the portable suite. Both the default and an override are covered so /// the assertion tracks configuration rather than a constant. /// /// Configured worker-frame maximum to negotiate. @@ -164,7 +164,7 @@ public sealed class WorkerClientTests TestTimeout, CancellationToken.None); - // The timeout also emits a WorkerCancel for the abandoned correlation (PERF-07), which sits + // The timeout also emits a WorkerCancel for the abandoned correlation, which sits // ahead of the second command on the FIFO pipe; skip it rather than mistaking it for the // command this assertion is about. WorkerEnvelope secondCommand = await ReadNextCommandAsync(pipePair, timedOutCommand.CorrelationId); @@ -180,7 +180,7 @@ public sealed class WorkerClientTests /// /// A command timeout abandons the gateway-side wait, but the worker keeps the correlation on /// its single STA queue and would still run it — so the gateway forwards a WorkerCancel - /// for the abandoned correlation id (PERF-07). Without it, a client that retries after a + /// for the abandoned correlation id. Without it, a client that retries after a /// timeout stacks work the worker still intends to execute. Asserted on the wire because the /// cancel is protocol behavior the worker depends on, not an internal detail. /// @@ -223,7 +223,7 @@ public sealed class WorkerClientTests /// /// Caller cancellation abandons the correlation exactly as a gateway timeout does — a client /// deadline shorter than the gateway's command timeout, or a disconnect, is the common - /// abandonment path — so it forwards the same WorkerCancel (IPC-34). Without it a + /// abandonment path — so it forwards the same WorkerCancel. Without it a /// retry storm of deadline-cancelled calls leaves the worker's single STA working through /// commands nobody is waiting for while live callers queue behind them. Twin of /// , driven @@ -271,7 +271,7 @@ public sealed class WorkerClientTests /// /// The cancel is forwarded for a caller that abandoned its command, never for the client's own - /// teardown (IPC-34): once the stop token has fired the outbound channel is being completed and + /// teardown: once the stop token has fired the outbound channel is being completed and /// every pending command is failed anyway, so a cancel would at best be pointless and at worst /// a write racing teardown. Asserted on the wire — the guard in the cancellation arm is the /// code-level expression of it, and the completed outbound channel is the second line of @@ -330,8 +330,7 @@ public sealed class WorkerClientTests /// A WorkerFault arriving in place of a handshake frame is the worker reporting a /// structured startup failure — most often MXAccess COM creation — not a protocol problem, so /// the gateway surfaces with the fault's - /// category and diagnostic rather than a generic protocol violation naming only the body type - /// (IPC-35). This is the window where the information matters most: first session open on a + /// category and diagnostic rather than a generic protocol violation naming only the body type. This is the window where the information matters most: first session open on a /// freshly deployed host, where the message reaches the operator log and the client's /// OpenFailed detail. /// @@ -368,8 +367,7 @@ public sealed class WorkerClientTests /// /// The envelope sequence is a monotonic per-sender counter (gateway.md), so the values /// observed on the pipe must be strictly increasing in wire order. Stamping the sequence when - /// the envelope is constructed lets two concurrent invokes stamp 1 and 2 but enqueue 2 then 1 - /// (GWC-28); stamping on the single-consumer write loop makes wire order and sequence order the + /// the envelope is constructed lets two concurrent invokes stamp 1 and 2 but enqueue 2 then 1; stamping on the single-consumer write loop makes wire order and sequence order the /// same thing by construction. /// /// A task that represents the asynchronous operation. @@ -710,12 +708,12 @@ public sealed class WorkerClientTests } /// - /// GWC-32: disposing a still-Ready client while the worker is emitting events must be + /// Disposing a still-Ready client while the worker is emitting events must be /// an ordinary teardown, not a fault. DisposeAsync completes the same three channels /// MarkClosed and SetFaulted do, so it must enter a terminal state first; /// otherwise the read loop — already inside DispatchEnvelope when the staging channel /// is completed, or reading a stream that is torn down underneath it — misdiagnoses the - /// completed channel as the GWC-24 sustained-backpressure overflow and raises a spurious + /// completed channel as the sustained-backpressure overflow and raises a spurious /// ProtocolViolation fault, a worker-event-staging overflow metric and a /// redundant kill during an intentional dispose. The event channel is sized far above the /// burst so a genuine overflow is impossible, and DisposeAsync awaits its background @@ -726,7 +724,7 @@ public sealed class WorkerClientTests public async Task DisposeAsync_WhileWorkerIsEmittingEvents_ClosesWithoutFaultingOrOverflowing() { // The burst is capped at EmittedEventCeiling and the staging channel holds 2 x capacity, so - // even if every emitted event were staged and none consumed the GWC-24 bound is unreachable: + // even if every emitted event were staged and none consumed the staging bound is unreachable: // an overflow metric in this test can only be the dispose-race misdiagnosis. const int capacity = 2048; using GatewayMetrics metrics = new(); @@ -1007,13 +1005,13 @@ public sealed class WorkerClientTests } /// - /// GWC-24: the staging channel between the read loop and the event writer is bounded at + /// The staging channel between the read loop and the event writer is bounded at /// 2 × , so a consumer that drains /// slower than the worker produces faults the client at the bound instead of growing /// gateway memory silently. The full-mode timeout here is far longer than the test could /// ever wait, which proves the fault came from the staging bound and not from the timed /// write in EnqueueWorkerEventAsync. A command reply interleaved before the fault - /// must still complete: the read loop never blocks behind events (the GWC-04 guarantee). + /// must still complete: the read loop never blocks behind events (the read-loop guarantee). /// /// A task that represents the asynchronous operation. [Fact] @@ -1094,7 +1092,7 @@ public sealed class WorkerClientTests } /// - /// GWC-24: the worker event queue-depth gauge counts staged *and* queued events, so a + /// The worker event queue-depth gauge counts staged *and* queued events, so a /// backlog held in the staging channel is visible rather than invisible. Depth is /// incremented at staging and decremented when the consumer reads, so the single counter /// reports total undelivered events and returns to zero once drained. @@ -1148,7 +1146,7 @@ public sealed class WorkerClientTests } /// - /// GWC-33: a faulted client can remain retained for diagnostics, but its abandoned event + /// A faulted client can remain retained for diagnostics, but its abandoned event /// backlog must stop contributing to the gateway-wide queue-depth gauge as soon as the /// client enters a terminal state rather than waiting for disposal. /// diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Workers/WorkerFrameProtocolTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Workers/WorkerFrameProtocolTests.cs index 6663b0f..ecc452b 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Workers/WorkerFrameProtocolTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Workers/WorkerFrameProtocolTests.cs @@ -57,7 +57,7 @@ public sealed class WorkerFrameProtocolTests /// /// One reader instance reads many frames in sequence. The reader reuses a single length-prefix - /// scratch buffer across calls (GWC-30), so varying the payload length frame to frame proves the + /// scratch buffer across calls, so varying the payload length frame to frame proves the /// reused buffer is fully overwritten each time rather than carrying a stale prefix forward. /// /// A task that represents the asynchronous operation. diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Metrics/GatewayMetricsTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Metrics/GatewayMetricsTests.cs index 4101600..3ee8db7 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Metrics/GatewayMetricsTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Metrics/GatewayMetricsTests.cs @@ -19,11 +19,11 @@ public sealed class GatewayMetricsTests metrics.CommandFailed("WriteSecured", "AuthorizationFailed", TimeSpan.FromMilliseconds(12)); metrics.EventReceived("session-1", "OnDataChange"); metrics.EventReceived("session-1", "OnDataChange"); - // PERF-02: the worker queue-depth gauge sums one live source per worker client, so the two + // The worker queue-depth gauge sums one live source per worker client, so the two // registrations below stand in for two concurrent sessions holding 3 and 4 events. using IDisposable workerDepthSourceA = metrics.RegisterWorkerEventQueueDepthSource(static () => 3); using IDisposable workerDepthSourceB = metrics.RegisterWorkerEventQueueDepthSource(static () => 4); - // GWC-15: the gRPC stream queue-depth gauge sums live backlog sources at collection time + // The gRPC stream queue-depth gauge sums live backlog sources at collection time // rather than tracking a pushed running total. Register a source reporting 3. using IDisposable backlogSource = metrics.RegisterEventStreamBacklogSource(static () => 3); metrics.QueueOverflow("session-events"); @@ -58,7 +58,7 @@ public sealed class GatewayMetricsTests } /// - /// PERF-02: the worker queue-depth gauge sums every registered source rather than holding a + /// The worker queue-depth gauge sums every registered source rather than holding a /// single pushed scalar, so concurrent sessions add up instead of overwriting one another, /// and a disposed registration (a worker client going away) drops out of the sum. Disposal /// is idempotent because a client's dispose path can run twice. diff --git a/src/ZB.MOM.WW.MxGateway.Tests/ProjectStructure/WindevCiContractTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/ProjectStructure/WindevCiContractTests.cs index 0d59a1e..5b5e389 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/ProjectStructure/WindevCiContractTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/ProjectStructure/WindevCiContractTests.cs @@ -1,7 +1,9 @@ namespace ZB.MOM.WW.MxGateway.Tests.ProjectStructure; +/// Pins the structure of the windev worker CI script. public sealed class WindevCiContractTests { + /// Verifies live mode runs the Windows test tiers in the documented order. [Fact] public void LiveMode_RunsWindowsTiersInDocumentedOrder() { 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 467e7d0..a07eaff 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Security/Authentication/ApiKeyAdminCommandLineParserTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Security/Authentication/ApiKeyAdminCommandLineParserTests.cs @@ -238,6 +238,7 @@ public sealed class ApiKeyAdminCommandLineParserTests /// Verifies an empty tag segment is rejected rather than dropped: a stray comma must not /// silently persist a grant the operator did not write. /// + /// The raw dashboard tag list containing an empty segment. [Theory] [InlineData("team-a,,team-b")] [InlineData("team-a, ")] 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 49dce31..a13dd72 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Security/Authentication/CachingApiKeyVerifierTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Security/Authentication/CachingApiKeyVerifierTests.cs @@ -100,7 +100,7 @@ public sealed class CachingApiKeyVerifierTests } /// - /// SEC-34 window 3: an that lands while a + /// Window 3: an that lands while a /// verification is in flight in the inner library must discard that verification's repopulation, /// so the very next request re-verifies (revoke takes effect immediately, not after the TTL). /// diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Security/Authorization/ApiKeyFailureLimiterTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Security/Authorization/ApiKeyFailureLimiterTests.cs index 2e7e3e6..17b05d5 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Security/Authorization/ApiKeyFailureLimiterTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Security/Authorization/ApiKeyFailureLimiterTests.cs @@ -4,7 +4,7 @@ using ZB.MOM.WW.MxGateway.Tests.TestSupport; namespace ZB.MOM.WW.MxGateway.Tests.Security.Authorization; /// -/// Unit tests for the two-layer API-key failure limiter (SEC-31 / SEC-32): composite +/// Unit tests for the two-layer API-key failure limiter: composite /// (transport peer, key id) partitions, the cross-peer per-key-id aggregate, probe /// admission, the per-peer key-id partition cap, and the eviction preference order. /// @@ -58,7 +58,7 @@ public sealed class ApiKeyFailureLimiterTests /// /// The composite partition binds a throttle to the failing address: the same key id presented - /// from a different transport peer is unaffected. This is the structural half of the SEC-31 fix. + /// from a different transport peer is unaffected. This is the structural half of the per-address throttle. /// [Fact] public void CompositePartition_ThrottleDoesNotFollowKeyIdToAnotherPeer() @@ -102,7 +102,7 @@ public sealed class ApiKeyFailureLimiterTests } /// - /// SEC-38: a valid key id still feeds and consults its cross-peer aggregate when each + /// A valid key id still feeds and consults its cross-peer aggregate when each /// attacking peer has exhausted its 32 dedicated key-id partitions and therefore records /// the peer layer through the fallback bucket. /// @@ -372,7 +372,7 @@ public sealed class ApiKeyFailureLimiterTests } /// - /// SEC-39: a request with no effective key id does not own the peer's shared fallback + /// A request with no effective key id does not own the peer's shared fallback /// partition. Resetting after such a request must leave other fallback failures tracked. /// [Fact] @@ -395,7 +395,7 @@ public sealed class ApiKeyFailureLimiterTests } /// - /// SEC-32: a spray of unique junk partitions (junk tokens resolve to the sender's fallback + /// A spray of unique junk partitions (junk tokens resolve to the sender's fallback /// partition, one per address) must not evict a partition that is currently throttled — the /// LRU cap bounds memory, it must not be a reset button for the block. /// @@ -420,7 +420,7 @@ public sealed class ApiKeyFailureLimiterTests } /// - /// SEC-32: one address may mint at most + /// One address may mint at most /// key-id partitions; the overflow collapses into that address's fallback partition, which then /// throttles the address wholesale instead of letting the spray mint unbounded state. /// @@ -445,7 +445,7 @@ public sealed class ApiKeyFailureLimiterTests } /// - /// SEC-32 eviction preference: with the map at capacity a new failure evicts an entry whose + /// Eviction preference: with the map at capacity a new failure evicts an entry whose /// window has fully expired rather than an entry that is still counting. /// [Fact] diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Security/Authorization/GatewayBrowseScopeProviderTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Security/Authorization/GatewayBrowseScopeProviderTests.cs index 1e0eba2..e6fa3a9 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Security/Authorization/GatewayBrowseScopeProviderTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Security/Authorization/GatewayBrowseScopeProviderTests.cs @@ -32,8 +32,8 @@ public sealed class GatewayBrowseScopeProviderTests /// /// An authenticated key with no BrowseSubtrees constraint is legitimately unscoped: the - /// empty list is what the library reads as "no scoping". This is the state GWC-38 required - /// to stay distinguishable from an absent identity, so it is asserted alongside its + /// empty list is what the library reads as "no scoping". This is the state that must + /// stay distinguishable from an absent identity, so it is asserted alongside its /// denying twin below rather than left implicit. /// [Fact] @@ -49,7 +49,7 @@ public sealed class GatewayBrowseScopeProviderTests } /// - /// GWC-38: with API-key authentication enabled, an absent ambient identity means the + /// With API-key authentication enabled, an absent ambient identity means the /// interceptor-ordering invariant is broken — the request's browse scope cannot be /// established. The provider must DENY rather than resolve to the full hierarchy, because /// a silently-unscoped browse is an invisible authorization bypass. The status code is 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 b9327a6..c38b7db 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Security/Authorization/GatewayGrpcAuthorizationInterceptorTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Security/Authorization/GatewayGrpcAuthorizationInterceptorTests.cs @@ -365,7 +365,7 @@ public sealed class GatewayGrpcAuthorizationInterceptorTests } /// - /// SEC-31: once an attacking peer has exceeded the failure limit for a key id, the interceptor + /// Once an attacking peer has exceeded the failure limit for a key id, the interceptor /// short-circuits with BEFORE calling the verifier, so /// an online guessing loop stops spending a store read per attempt. The composite /// (peer, key id) partition keeps that bound per attacking address. @@ -407,7 +407,7 @@ public sealed class GatewayGrpcAuthorizationInterceptorTests } /// - /// SEC-31 (the lockout inversion): an attacker who floods failures for a victim's key id from its + /// Lockout inversion: an attacker who floods failures for a victim's key id from its /// own address must not deny that key to the legitimate holder. The holder presents the correct /// secret from a different transport peer and authenticates on the first attempt — the verifier /// is reached and the RPC succeeds. @@ -454,7 +454,7 @@ public sealed class GatewayGrpcAuthorizationInterceptorTests } /// - /// SEC-31 layer 2: failures for one key id sprayed across more distinct peers than + /// Layer 2: failures for one key id sprayed across more distinct peers than /// ApiKeyFailureAggregateLimit put that key id into probe mode globally, so a /// rotating-source attacker gets at most one verifier call per probe interval. /// @@ -515,7 +515,7 @@ public sealed class GatewayGrpcAuthorizationInterceptorTests } /// - /// SEC-31: the success-reset path stays reachable while throttled. A throttled partition admits + /// The success-reset path stays reachable while throttled. A throttled partition admits /// one probe per interval; the correct secret rides that slot, authenticates, and fully clears /// both limiter layers, so the next wrong attempt is /// rather than . @@ -572,7 +572,7 @@ public sealed class GatewayGrpcAuthorizationInterceptorTests } /// - /// SEC-32: only a validly shaped mxgw_<keyId>_<secret> token mints a key-id + /// Only a validly shaped mxgw_<keyId>_<secret> token mints a key-id /// partition. Junk tokens of varied shapes all collapse onto the sender's transport-peer fallback /// partition, so a spray cannot mint one tracked entry per invented token. /// diff --git a/src/ZB.MOM.WW.MxGateway.Tests/TestSupport/ChannelWorkerClient.cs b/src/ZB.MOM.WW.MxGateway.Tests/TestSupport/ChannelWorkerClient.cs index 378d125..67bdf24 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/TestSupport/ChannelWorkerClient.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/TestSupport/ChannelWorkerClient.cs @@ -61,6 +61,7 @@ public sealed class ChannelWorkerClient(string sessionId, ChannelReader + /// Completes immediately because the fake holds no resources. + /// A completed value task. public ValueTask DisposeAsync() => ValueTask.CompletedTask; } diff --git a/src/ZB.MOM.WW.MxGateway.Tests/TestSupport/TestHostEnvironmentInitializer.cs b/src/ZB.MOM.WW.MxGateway.Tests/TestSupport/TestHostEnvironmentInitializer.cs index 7c6881a..650c944 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/TestSupport/TestHostEnvironmentInitializer.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/TestSupport/TestHostEnvironmentInitializer.cs @@ -85,7 +85,7 @@ internal static class TestHostEnvironmentInitializer Environment.SetEnvironmentVariable("Secrets__SqlitePath", secretsPath); } - // Starting the full host eagerly opens the auth SQLite store. Since SEC-33 the shipped + // Starting the full host eagerly opens the auth SQLite store. The shipped // appsettings.json no longer carries an Authentication:SqlitePath, and the CommonApplicationData // code default resolves under an unwritable /usr/share on macOS. Point every host-building test at // a per-process temp store (same pattern as Secrets__SqlitePath above) so host-start tests are diff --git a/src/ZB.MOM.WW.MxGateway.Worker.Tests/Conversion/MxStatusProxyConverterTests.cs b/src/ZB.MOM.WW.MxGateway.Worker.Tests/Conversion/MxStatusProxyConverterTests.cs index 8539640..fbd9a0b 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker.Tests/Conversion/MxStatusProxyConverterTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker.Tests/Conversion/MxStatusProxyConverterTests.cs @@ -108,7 +108,7 @@ public sealed class MxStatusProxyConverterTests /// /// Verifies that repeated conversions of the same status type produce - /// identical results. WRK-06 caches the resolved FieldInfo objects per + /// identical results. The converter caches the resolved FieldInfo objects per /// type after the first conversion; the cached path must yield the same /// message the uncached first call produced. /// @@ -137,7 +137,7 @@ public sealed class MxStatusProxyConverterTests /// /// Verifies every accessor strategy converts the same status values - /// identically. PERF-20 reads a public field of a publicly visible type + /// identically. The converter reads a public field of a publicly visible type /// through a delegate compiled from an expression tree instead of /// FieldInfo.GetValue; a field type whose conversion to int is not /// a lossless widening () and a type the 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 8ab6d0b..75bbe1e 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker.Tests/Ipc/WorkerFrameProtocolTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker.Tests/Ipc/WorkerFrameProtocolTests.cs @@ -521,7 +521,7 @@ public sealed class WorkerFrameProtocolTests /// Control-frame completion decoupling. The boundary flush is charged per class run, not per /// control frame: a pass carrying nothing but control frames still pays exactly one flush. Flushing /// after every control frame - /// would reinstate the syscall-per-heartbeat cost WRK-12 removed. + /// would reinstate the syscall-per-heartbeat cost the earlier per-frame flush design incurred. /// /// A task that represents the asynchronous operation. [Fact] @@ -592,7 +592,7 @@ public sealed class WorkerFrameProtocolTests } /// - /// Verifies a per-frame rejection does not burn a sequence number (WRK-23). The sequence is a + /// Verifies a per-frame rejection does not burn a sequence number. The sequence is a /// diagnostic counter, so a gap breaks nothing functionally — but an operator correlating a pipe /// capture reads a gap as a lost frame and chases a bug that does not exist, and the gap-free /// guarantee the concurrent-write test asserts would otherwise only hold until the first @@ -664,7 +664,7 @@ public sealed class WorkerFrameProtocolTests /// /// Verifies a negotiated frame maximum below the worker floor is rejected as - /// InvalidConfiguration (WRK-24), and that exactly the floor is adopted. The floor matches + /// InvalidConfiguration and that exactly the floor is adopted. The floor matches /// the gateway's own GatewayOptionsValidator.MinimumMaxMessageBytes, so the worker never /// rejects a value the gateway's validator accepts, yet a nonsensical tiny value faults at the /// handshake instead of leaving a session that fails every later frame. @@ -684,7 +684,7 @@ public sealed class WorkerFrameProtocolTests } /// - /// WRK-22 / IPC-26. A WriteAsync cancelled while it waits for the write lock must never + /// A WriteAsync cancelled while it waits for the write lock must never /// have its frame written by the next lock-holder. Writer A holds the lock mid-write (blocked in /// the stream); an event write is queued and then cancelled; when A is released and a later /// control frame drains, the wire carries A's frame and the control frame only — the cancelled @@ -727,7 +727,7 @@ public sealed class WorkerFrameProtocolTests } /// - /// NEXT-04. A frame claimed by the draining lock-holder before its caller's cancellation lands + /// A frame claimed by the draining lock-holder before its caller's cancellation lands /// is abandoned — the cancelled caller never awaits its completion. If the wire write then /// faults, the tombstone path's fault-observing continuation must still observe the exception /// so it never surfaces as . @@ -791,7 +791,7 @@ public sealed class WorkerFrameProtocolTests } /// - /// WRK-22 / IPC-26, the review's shutdown scenario. A cancelled event frame queued before a + /// Shutdown scenario: a cancelled event frame queued before a /// shutdown-ack control frame must not trail the ack on the wire: the tombstone rule plus the /// control-before-event scheduler keeps the ack the last frame written. /// @@ -830,9 +830,9 @@ public sealed class WorkerFrameProtocolTests } /// - /// WRK-25. The batch entry point enqueues a whole event burst under one lock acquisition and + /// The batch entry point enqueues a whole event burst under one lock acquisition and /// drains it together, so N events cost exactly one flush and reach the wire in batch order with - /// monotonic sequences — the coalescing WRK-12 shipped, now on the event hot path. + /// monotonic sequences — the flush coalescing now on the event hot path. /// /// A task that represents the asynchronous operation. [Fact] @@ -868,7 +868,7 @@ public sealed class WorkerFrameProtocolTests } /// - /// WRK-25. Control-before-event still holds mid-batch: a control frame queued while a batch is + /// Control-before-event still holds mid-batch: a control frame queued while a batch is /// draining jumps ahead of the batch's remaining events. /// /// A task that represents the asynchronous operation. @@ -1223,7 +1223,7 @@ public sealed class WorkerFrameProtocolTests // A MemoryStream whose first write blocks until released and whose second write blocks until // released and then throws, so a test can abandon a claimed frame by cancellation and fault its - // wire write afterwards (NEXT-04). + // wire write afterwards. private sealed class SecondWriteFaultingGatedStream : MemoryStream { private readonly SemaphoreSlim _firstRelease = new SemaphoreSlim(0); @@ -1235,6 +1235,8 @@ public sealed class WorkerFrameProtocolTests private readonly string _faultMessage; private int _writeCount; + /// Initializes a new instance of the class. + /// The message of the exception thrown by the second write. public SecondWriteFaultingGatedStream(string faultMessage) { _faultMessage = faultMessage; 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 09df002..033b5c8 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker.Tests/Ipc/WorkerPipeSessionTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker.Tests/Ipc/WorkerPipeSessionTests.cs @@ -20,7 +20,7 @@ public sealed class WorkerPipeSessionTests private const string SessionId = "session-1"; private const string Nonce = "nonce-secret"; - // Byte-heavy drain fixture (WRK-21). 10,000 events at ~1.7 KiB each is ~17 MB of queue — far + // Byte-heavy drain fixture. 10,000 events at ~1.7 KiB each is ~17 MB of queue — far // more than one frame — so DrainEvents must truncate. // // Two limits below are harness accommodations, not properties of the fix. PipePair runs both @@ -559,7 +559,7 @@ public sealed class WorkerPipeSessionTests } /// - /// The WRK-21 repro. A queue full of byte-heavy events (large string values — the payload + /// The repro for the oversized drain reply. A queue full of byte-heavy events (large string values — the payload /// profile this gateway exists for) used to make DrainEvents max_events = 0 build a /// reply above the negotiated frame maximum: the writer rejected the frame, the exception /// unwound the session, and the already-dequeued events were gone. The drain is now @@ -703,7 +703,7 @@ public sealed class WorkerPipeSessionTests { // The lowest Worker.MaxMessageBytes GatewayOptionsValidator permits: the public gRPC floor // (1024) plus the 64 KiB envelope-overhead reserve. The naive budget — frame max less the - // two reserves the DrainEvents budget subtracts (IPC-33) — would be negative here; the + // two reserves the DrainEvents budget subtracts — would be negative here; the // floored budget is half of the frame max (~33 KiB). const uint validatorFloorFrameMax = 1024 + (64 * 1024); using CancellationTokenSource cancellation = new(TimeSpan.FromSeconds(60)); @@ -761,7 +761,7 @@ public sealed class WorkerPipeSessionTests } /// - /// Verifies the control-reply write seam is not session-fatal on size (WRK-21 backstop). The + /// Verifies the control-reply write seam is not session-fatal on size (the drain backstop). The /// reply builders size their payloads, so this path needs a deliberately budget-blind drain /// to reach — but that is the point: a future command or a sizing bug must degrade to an /// error reply for that correlation, never to a dead session. @@ -859,7 +859,7 @@ public sealed class WorkerPipeSessionTests } /// - /// IPC-30. An event above the negotiated frame maximum is undeliverable end to end (the pipe + /// An event above the negotiated frame maximum is undeliverable end to end (the pipe /// maximum sits only an envelope reserve above the public gRPC cap), so the session stays /// fatal by design — but the death must be structured: a WorkerFault naming the event, with /// no value payload in it, before the process exits. Silently dropping the event or @@ -915,7 +915,7 @@ public sealed class WorkerPipeSessionTests } /// - /// WRK-31, the other side of the invariant. The graceful path leaves the message loop + /// The other side of the transport-ownership invariant. The graceful path leaves the message loop /// through its return after the shutdown ack, with that iteration's read already /// awaited — so there is no abandoned read for teardown to account for. Pinning this keeps /// the new disposal-and-observe step a pure no-op on the path production takes every time a @@ -955,7 +955,7 @@ public sealed class WorkerPipeSessionTests } /// - /// WRK-31. The session now owns and closes the transport rather than leaving it to + /// The session now owns and closes the transport rather than leaving it to /// WorkerPipeClient's using, because the read it unblocks has to be observed /// while the session still holds it. This asserts the closure actually happens on the /// session's own timeline: the gateway end of the pipe must see disconnection while @@ -1250,7 +1250,7 @@ public sealed class WorkerPipeSessionTests } /// - /// WRK-27. An STA call outside the command dispatcher (the alarm poll) advertises itself on + /// An STA call outside the command dispatcher (the alarm poll) advertises itself on /// the heartbeat snapshot's StaCallInProgress flag, and the watchdog grants it the same /// grace-to-ceiling suppression as a dispatched command: stale STA activity within the ceiling /// does not fault while the flag is set, but stale activity beyond the ceiling faults anyway. @@ -1340,8 +1340,8 @@ public sealed class WorkerPipeSessionTests } /// - /// WRK-25. The event drain loop submits a whole drained batch through the writer's batch entry - /// point, so a burst of 128 events costs one flush, not 128 — the assertion the WRK-12 + /// The event drain loop submits a whole drained batch through the writer's batch entry + /// point, so a burst of 128 events costs one flush, not 128 — the assertion the /// tracking claim needed to actually hold on the event hot path. /// /// A task that represents the asynchronous operation. @@ -1413,7 +1413,7 @@ public sealed class WorkerPipeSessionTests } /// - /// WRK-24. A GatewayHello negotiating a frame maximum below the worker floor faults at the + /// A GatewayHello negotiating a frame maximum below the worker floor faults at the /// handshake with a fault frame rather than being adopted — mirroring the above-ceiling /// handshake behavior — so a nonsensical tiny value never leaves a session that fails every /// later frame. No message loop is entered. @@ -2338,7 +2338,7 @@ public sealed class WorkerPipeSessionTests public sealed class AbandonedPipeReadTeardownTests { /// - /// WRK-31. A fault exit unwinds the message loop while its frame read is still pending, + /// A fault exit unwinds the message loop while its frame read is still pending, /// and on net48 nothing can cancel that read — NamedPipeClientStream.ReadAsync /// ignores the token, so only closing the handle ends it. The session must therefore /// dispose the transport itself and account for the read that disposal unblocks: the diff --git a/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessCommandExecutorTests.cs b/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessCommandExecutorTests.cs index 7533db1..e8725a6 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessCommandExecutorTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessCommandExecutorTests.cs @@ -2166,14 +2166,17 @@ public sealed class MxAccessCommandExecutorTests /// private sealed class CompletionCacheEventSink : IMxAccessEventSink, IWriteCompletionCacheProvider { + /// public MxAccessWriteCompletionCache WriteCompletionCache { get; } = new MxAccessWriteCompletionCache(); + /// public void Attach( object mxAccessComObject, string sessionId) { } + /// public void Detach() { } diff --git a/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessEventMapperTests.cs b/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessEventMapperTests.cs index 1194d0f..e7c6149 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessEventMapperTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessEventMapperTests.cs @@ -239,14 +239,14 @@ public sealed class MxAccessEventMapperTests } /// - /// Verifies the exact-format fast path PERF-20 added in front of the + /// Verifies the exact-format fast path added in front of the /// general parse chain returns exactly what that chain returned. The /// cases cover the captured MXAccess shape (which the fast path is meant /// to catch on a US-shaped host), its zero-padded and second-precision /// siblings, and strings the derived formats cannot match — an ISO /// round-trip form, a long-date form, and a day-first European form — /// which must fall through to the unchanged two-stage chain. The - /// expectation is computed with the pre-PERF-20 chain in this test, so it + /// expectation is computed with the original chain in this test, so it /// holds on any host culture: the fast path is a shortcut, never a /// different answer. /// @@ -353,8 +353,8 @@ public sealed class MxAccessEventMapperTests } /// - /// The parse chain exactly as it stood before PERF-20 added the - /// exact-format stage, used as the parity oracle above. + /// The parse chain exactly as it stood before the + /// exact-format stage was added, used as the parity oracle above. /// /// Timestamp string to parse. /// The parsed UTC timestamp on success. diff --git a/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessEventQueueTests.cs b/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessEventQueueTests.cs index 8d4681d..c5de83a 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessEventQueueTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessEventQueueTests.cs @@ -37,8 +37,7 @@ public sealed class MxAccessEventQueueTests /// /// Verifies that Enqueue takes ownership of the passed event instead of /// cloning it: the dequeued instance is the very reference passed in, and - /// the worker sequence/timestamp are stamped on that same instance - /// (WRK-11). + /// the worker sequence/timestamp are stamped on that same instance. /// [Fact] public void Enqueue_TakesOwnershipOfPassedEventInstance() @@ -105,7 +104,7 @@ public sealed class MxAccessEventQueueTests /// /// Verifies the byte-budgeted drain stops before the budget is exceeded, leaves the - /// remainder queued in order, and reports the exact remaining count (WRK-21). Events that + /// remainder queued in order, and reports the exact remaining count. Events that /// do not fit must never be dequeued — dequeuing them is how the pre-fix drain lost events /// when the reply frame was rejected. /// @@ -184,7 +183,7 @@ public sealed class MxAccessEventQueueTests } /// - /// The no-loss half of the WRK-21 acceptance criterion, at full scale. Draining the review's + /// The no-loss half of the drain guarantee, at full scale. Draining the review's /// 10,000 byte-heavy events under a budget that fits only a fraction of them per call must /// return every event exactly once and in order: the pre-fix drain removed events from the /// queue before the reply was sized, so a rejected frame destroyed them. This runs at the @@ -258,7 +257,7 @@ public sealed class MxAccessEventQueueTests /// Brackets the byte budget's per-event charge to the exact serialized size of the event. /// The queue measures that size once at enqueue rather than during the drain, so this pins /// the two ends of the charge against the very instance the queue holds: a budget one byte - /// short must refuse the head (and leave it queued, WRK-21), and a budget of exactly the + /// short must refuse the head (and leave it queued), and a budget of exactly the /// charge must ship it. An undercharge — sizing the event before Enqueue stamps its worker /// sequence and timestamp, say — passes the first probe and breaks the frame guarantee; an /// overcharge of even one byte fails the second. The preStamped case covers an event @@ -371,7 +370,7 @@ public sealed class MxAccessEventQueueTests } /// - /// The other half of the WRK-21 head guarantee: a head refused for its size is drained + /// The other half of the head guarantee: a head refused for its size is drained /// unchanged by a later call whose budget fits it. The size the queue charges lives with the /// event across calls, so a refused attempt must neither consume nor alter it — and the /// refusal itself is justified by the event's real serialized size. diff --git a/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessStaSessionTests.cs b/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessStaSessionTests.cs index 3ff55fe..4659d70 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessStaSessionTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessStaSessionTests.cs @@ -527,7 +527,7 @@ public sealed class MxAccessStaSessionTests } /// - /// WRK-27. While the alarm poll's PollOnce is executing on the STA, a heartbeat captured mid-poll + /// While the alarm poll's PollOnce is executing on the STA, a heartbeat captured mid-poll /// must report so the watchdog /// grants the poll the same grace-to-ceiling suppression as a dispatched command; once the poll /// returns the flag clears. PollOnce is blocked on a gate so the heartbeat can be captured while diff --git a/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/WnWrapAlarmConsumerXmlTests.cs b/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/WnWrapAlarmConsumerXmlTests.cs index 4ecac2a..afef255 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/WnWrapAlarmConsumerXmlTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/WnWrapAlarmConsumerXmlTests.cs @@ -319,7 +319,7 @@ public sealed class WnWrapAlarmConsumerXmlTests } // ------------------------------------------------------------------------- - // Truncation cliff (Task 23). GetXmlCurrentAlarms2 caps its reply at + // Truncation cliff. GetXmlCurrentAlarms2 caps its reply at // maxAlmCnt with no "more available" flag. Before the guard, a capped fetch // shrank the retained snapshot, and every alarm past the cap vanished from // SnapshotActiveAlarms — which the gateway's reconcile pass reads as a @@ -587,7 +587,7 @@ public sealed class WnWrapAlarmConsumerXmlTests } // ------------------------------------------------------------------------- - // Single-pass parse parity (Task 23). The per-field SelectSingleNode calls + // Single-pass parse parity. The per-field SelectSingleNode calls // were replaced by one walk over ChildNodes; these pin the semantics that // walk has to reproduce. // ------------------------------------------------------------------------- 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 456205d..016b042 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker.Tests/TestSupport/FakeRuntimeSession.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker.Tests/TestSupport/FakeRuntimeSession.cs @@ -494,7 +494,7 @@ internal sealed class FakeRuntimeSession : IWorkerRuntimeSession /// /// Enqueues a batch of worker events atomically under one lock so the drain loop cannot /// observe a partial batch. Lets a test assert the drain loop coalesces a whole batch into one - /// flush (WRK-25) without racing a mid-enqueue drain that would split the batch. + /// flush without racing a mid-enqueue drain that would split the batch. /// /// The events to enqueue in order. public void EnqueueEvents(IEnumerable workerEvents) diff --git a/src/ZB.MOM.WW.MxGateway.Worker/Conversion/MxStatusProxyConverter.cs b/src/ZB.MOM.WW.MxGateway.Worker/Conversion/MxStatusProxyConverter.cs index 9aad71f..c03cfcc 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/Conversion/MxStatusProxyConverter.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/Conversion/MxStatusProxyConverter.cs @@ -263,6 +263,11 @@ public sealed class MxStatusProxyConverter /// private readonly struct StatusFields { + /// Initializes a new instance of the struct. + /// Accessor for the raw success member. + /// Accessor for the raw category member. + /// Accessor for the raw detected-by member. + /// Accessor for the raw detail member. public StatusFields( Func success, Func category, @@ -275,12 +280,16 @@ public sealed class MxStatusProxyConverter Detail = detail; } + /// Gets the accessor for the raw success member. public Func Success { get; } + /// Gets the accessor for the raw category member. public Func Category { get; } + /// Gets the accessor for the raw detected-by member. public Func DetectedBy { get; } + /// Gets the accessor for the raw detail member. public Func Detail { get; } } } diff --git a/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerFrameReader.cs b/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerFrameReader.cs index 21c2afe..32bc0dd 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerFrameReader.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerFrameReader.cs @@ -20,7 +20,7 @@ public sealed class WorkerFrameReader private readonly WorkerFrameProtocolOptions _options; private readonly Stream _stream; - // Reused across frames rather than allocated per read (PERF-22). Safe because ReadAsync is + // Reused across frames rather than allocated per read. Safe because ReadAsync is // single-consumer by construction; the prefix is fully overwritten by every read. // // The single-consumer invariant has to survive teardown as well as steady state, because a read diff --git a/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerFrameWriter.cs b/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerFrameWriter.cs index 0593a15..ae9432b 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerFrameWriter.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerFrameWriter.cs @@ -103,13 +103,13 @@ public sealed class WorkerFrameWriter /// Token to cancel waiting for the write lock. /// A task that completes when the frame has been written and flushed. /// - /// Cancellation contract (WRK-22): if the token fires while this call is waiting for the write + /// Cancellation contract: if the token fires while this call is waiting for the write /// lock, the frame is tombstoned so it is never written — unless a draining lock-holder has /// already claimed it, in which case the frame may still reach the wire even though this call /// observes . That residual window is by design: blocking /// the canceller behind the very write it is abandoning would defeat the point of cancellation. /// The abandoned frame's completion gets a fault-observing continuation so a write failure after - /// the caller unwinds never raises an unobserved-task exception (NEXT-04). + /// the caller unwinds never raises an unobserved-task exception. /// /// Latency contract: a control frame's bytes are written, flushed, and its completion /// resolved before the events a drain pass writes after it — the delivery point of a heartbeat, @@ -201,7 +201,7 @@ public sealed class WorkerFrameWriter /// /// Queues a whole batch of envelopes at one priority under a single lock acquisition and drains /// it, so a burst of frames — the event drain loop's hot path — pays one flush for the batch - /// rather than one per frame (WRK-25, realizing the WRK-12 coalescing on the path it was built + /// rather than one per frame (realizing the flush coalescing on the path it was built /// for). Intra-batch order is preserved because the enqueue is atomic under _gate and each /// class queue is FIFO; the control-before-event guarantee still holds because any concurrently /// queued control frame is drained ahead of this batch by , and — since @@ -221,10 +221,10 @@ public sealed class WorkerFrameWriter /// A per-frame rejection inside the batch (for example one oversized event) surfaces from the /// awaited completions as its ; the remaining frames are /// still observed so none faults unobserved. Cancellation while waiting for the lock tombstones - /// every still-unclaimed frame in the batch, per the WRK-22 contract on + /// every still-unclaimed frame in the batch, per the cancellation contract on /// ; frames /// the cancelled caller abandons (claimed mid-write, or already faulted) get a fault-observing - /// continuation so a later write failure never raises an unobserved-task exception (NEXT-04). + /// continuation so a later write failure never raises an unobserved-task exception. /// /// This path deliberately keeps the plain wait-then-drain shape rather than the single-frame /// path's completion-versus-lock race. A batch caller's result is its whole set of completions, @@ -338,7 +338,7 @@ public sealed class WorkerFrameWriter } /// - /// Observes any fault on a frame the cancelled caller stops awaiting (NEXT-04). A frame + /// Observes any fault on a frame the cancelled caller stops awaiting. A frame /// claimed by a draining lock-holder — or already faulted by a concurrent /// FailAllQueued — completes on a task nobody awaits after cancellation unwinds the /// caller; a later write failure would then surface as an unobserved-task exception. A @@ -353,7 +353,7 @@ public sealed class WorkerFrameWriter } /// - /// Attaches the NEXT-04 fault-observing continuation to a task this writer starts and then + /// Attaches the fault-observing continuation to a task this writer starts and then /// discards. Every such task must carry one: with no awaiter, a fault would otherwise reach /// nobody and resurface as at finalization /// — a detached, unattributable failure long after the code that caused it. The continuation @@ -388,7 +388,7 @@ public sealed class WorkerFrameWriter /// Outstanding write-lock acquisition the caller has walked away from. private void DetachLockWait(Task lockWait) { - // The continuation task is discarded, so it takes the NEXT-04 fault observer: nothing awaits + // The continuation task is discarded, so it takes the fault observer: nothing awaits // it, and a throw out of OnDetachedLockWaitSettled would otherwise be an unobserved-task // exception raised at finalization rather than an attributable failure here. ObserveFault(lockWait.ContinueWith( @@ -418,7 +418,7 @@ public sealed class WorkerFrameWriter return; } - // Discarded, so it takes the NEXT-04 fault observer too. DrainDetachedAsync swallows the drain + // Discarded, so it takes the fault observer too. DrainDetachedAsync swallows the drain // itself, but its release sits in a finally outside that catch: a Release that ever throws (a // SemaphoreFullException from some future double-release regression, say) must fail somewhere // attributable rather than at finalization. @@ -455,9 +455,8 @@ public sealed class WorkerFrameWriter // The stream write itself is not cancellable: a frame is written atomically or fails, never left // half-written on the pipe because a caller gave up waiting. // - // Flushes are coalesced within a priority class rather than blindly across the whole pass (WRK-12 / - // IPC-15, narrowed by the control-frame completion decoupling): each frame is written to the stream - // but not flushed individually, and one FlushAsync runs at the end of the pass — plus one at each + // Flushes are coalesced within a priority class rather than blindly across the whole pass (narrowed by the control-frame completion + // decoupling): each frame is written to the stream but not flushed individually, and one FlushAsync runs at the end of the pass — plus one at each // control-to-event boundary, which flushes and completes the control frames written so far before // the event backlog behind them is written, instead of after it. Without that boundary flush the // priority scheduler only got control *bytes* out early: their delivery point, and every waiting @@ -468,7 +467,7 @@ public sealed class WorkerFrameWriter // pass. Cost is bounded: a pure-event pass (the event hot path) still pays exactly one flush, a burst // of control frames still pays one for the whole burst, and only a pass that actually mixes both // classes pays a second — never one flush per control frame, which is the syscall-per-heartbeat cost - // WRK-12 removed. + // the earlier per-frame flush removed. private async Task DrainQueuedFramesAsync() { List written = new List(); @@ -582,7 +581,7 @@ public sealed class WorkerFrameWriter } // Returns the next frame to write, control frames first, skipping any frame a cancelled caller - // tombstoned while it waited for the lock (WRK-22). The frame actually returned is marked Claimed + // tombstoned while it waited for the lock. The frame actually returned is marked Claimed // under _gate in the same critical section that checks the tombstone, so a claim and a concurrent // cancel are mutually exclusive: whichever acquires _gate first wins. private PendingFrame? DequeueNext() @@ -640,7 +639,7 @@ public sealed class WorkerFrameWriter // 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. // - // Peek-stamp-commit (WRK-23): the sequence participates in CalculateSize() (varint width), + // Peek-stamp-commit: the sequence participates in CalculateSize() (varint width), // so it must be stamped before the size checks — but a per-frame rejection must not burn a // number, or the wire shows phantom gaps that an operator reads as lost frames. Stamp a // candidate, validate the stamped envelope, and commit the counter only once the frame is @@ -668,7 +667,7 @@ public sealed class WorkerFrameWriter // Serialize once into a single pooled buffer that carries the 4-byte length prefix followed // by the payload, then issue one stream write. This avoids a second serialization pass, a // separate prefix array, a separate prefix write, and any per-frame heap allocation — the - // gateway-side writer's pooled-buffer shape (PERF-22), now matched on the net48 side. The + // gateway-side writer's pooled-buffer shape, now matched on the net48 side. The // rented buffer may be larger than requested, so only the first frameLength bytes are ever // written. The buffer is returned only after this frame's write has completed; the batch // flush deferred to the end of the drain (see DrainQueuedFramesAsync) does not read from it. diff --git a/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerPipeSession.cs b/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerPipeSession.cs index 60297c5..c9afa8d 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerPipeSession.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerPipeSession.cs @@ -43,7 +43,7 @@ public sealed class WorkerPipeSession // The transport the reader and writer share, when this session was handed one to own. Null for // the reader/writer constructors, whose callers keep ownership of whatever streams they built the - // pair over. Owning it is what lets teardown close the handle (WRK-31): on net48 a pipe read + // pair over. Owning it is what lets teardown close the handle: on net48 a pipe read // parked in the kernel cannot be cancelled, only unblocked by disposal. private readonly Stream? _transportStream; @@ -175,7 +175,7 @@ public sealed class WorkerPipeSession // Closing the transport is what actually ends a pipe read parked in the kernel: on net48 // NamedPipeClientStream.ReadAsync ignores its CancellationToken, so the message loop's - // cancellation can never reach one (WRK-31). It is deliberately the LAST teardown step, + // cancellation can never reach one. It is deliberately the LAST teardown step, // because in the ordinary case every frame this session will ever write has been written // AND flushed by the time control reaches here: WorkerFrameWriter.WriteAsync signals only // after both, and each exit path awaits its final write before unwinding — the shutdown @@ -563,7 +563,7 @@ public sealed class WorkerPipeSession /// task eventually faults, so a task nobody is awaiting any more can never reach the /// finalizer with an unobserved exception. Mirrors the shape /// WorkerFrameWriter.ObserveAbandonedFault uses for frames a cancelled caller stops - /// awaiting (NEXT-04). Faulting is the only outcome that runs the continuation, and the + /// awaiting. Faulting is the only outcome that runs the continuation, and the /// continuation is scheduled inline, so this costs nothing on the ordinary path. /// /// Task that may fault after its awaiter has walked away. @@ -614,7 +614,7 @@ public sealed class WorkerPipeSession } // Submit the whole drained batch through the writer's batch entry point under one lock - // acquisition so the burst pays a single flush instead of one per event (WRK-25). Events + // acquisition so the burst pays a single flush instead of one per event. 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, // and intra-batch order is preserved. @@ -634,7 +634,7 @@ public sealed class WorkerPipeSession when (exception.ErrorCode == WorkerFrameProtocolErrorCode.MessageTooLarge) { // A single oversized event surfaces from the batch's awaited completions; the death is - // still IPC-30's structured, event-naming fault. Map the rejection back to the first + // still the structured, event-naming fault. Map the rejection back to the first // event in batch order whose envelope overshoots the negotiated maximum — the same // frame the writer rejected first. await FaultOnOversizedEventAsync( @@ -665,8 +665,8 @@ public sealed class WorkerPipeSession } /// - /// Ends the session on an event that cannot be framed, but deliberately and diagnosably - /// (IPC-30). An event above the negotiated frame maximum is undeliverable end to end — the + /// Ends the session on an event that cannot be framed, but deliberately and diagnosably. + /// An event above the negotiated frame maximum is undeliverable end to end — the /// pipe maximum sits only an envelope reserve above the public gRPC cap — so dropping it /// would silently make the event stream unfaithful, and synthesizing a placeholder is barred /// by the no-synthesized-events rule. Instead the worker records which event blocked (never @@ -869,7 +869,7 @@ public sealed class WorkerPipeSession /// overshot the frame maximum. The fallback itself is a handful of bytes, so it fits any /// sane negotiated maximum; the only way it can also throw MessageTooLarge is a /// pathologically tiny negotiated maximum below the gateway's validation floor — the - /// pre-existing WRK-24 gap, which adds the negotiated-max lower bound that makes this + /// pre-existing gap, which adds the negotiated-max lower bound that makes this /// unreachable. Until then, a defensive swallow keeps the "no diagnostics command is /// session-fatal" invariant true even in that degenerate config: the correlation goes /// unanswered and the gateway's own per-command timeout covers it, but the session lives. @@ -1028,7 +1028,7 @@ public sealed class WorkerPipeSession /// MxCommandReply does not stop at the pipe: the gateway returns it verbatim through /// Invoke, where the public gRPC send cap applies, and the gateway's validator /// couples the two limits so the pipe maximum never exceeds the gRPC cap by more than two - /// reserves (IPC-33). Subtracting both therefore keeps the packed events at or below the + /// reserves. Subtracting both therefore keeps the packed events at or below the /// gRPC cap and leaves the wrapper — correlation id, protocol status, kind, the /// drain_events length prefix and, in the truncated case, the /// DiagnosticMessage — a full reserve of margin under it. Budgeting against the pipe @@ -1246,8 +1246,8 @@ public sealed class WorkerPipeSession _activeCommandTasks.Add(commandTask); } - // WRK-33: every other discarded task in this file is wrapped with ObserveFaultWhenever - // (NEXT-04 discipline) so a fault reaching the finalizer unobserved is impossible. This + // Every other discarded task in this file is wrapped with ObserveFaultWhenever + // (the fault-observation discipline) so a fault reaching the finalizer unobserved is impossible. This // wrapper task was the one exception — ObserveCommandTaskAsync only swallows // OperationCanceledException, so any other exception escaping ProcessCommandAsync's own // catch path (e.g. a future edit, or a failure past TryWriteFaultAsync's narrow filter) @@ -1380,7 +1380,7 @@ public sealed class WorkerPipeSession if ((!string.IsNullOrEmpty(snapshot.CurrentCommandCorrelationId) || snapshot.StaCallInProgress) && staleFor <= _sessionOptions.HeartbeatStuckCeiling) { - // A command is in flight, or an STA call outside the dispatcher (the alarm poll, WRK-27) is + // A command is in flight, or an STA call outside the dispatcher (the alarm poll) is // executing, and we are still within the defensive suppression ceiling — the STA is busy // doing that work, not hung. The next MarkActivity() in StaRuntime.ProcessQueuedCommands // will refresh LastActivityUtc once the work returns, at which point this branch stops @@ -1402,7 +1402,7 @@ public sealed class WorkerPipeSession // the StaHung fault just sent. Without this the heartbeat loop keeps // advertising a non-faulted state that contradicts the fault. _state = WorkerState.Faulted; - // WRK-34: CommandMethod is a method-name field, not a correlation-id field, so a + // CommandMethod is a method-name field, not a correlation-id field, so a // consumer reading it (dashboard, gateway logs) shouldn't see a bare correlation id // there. Use a stable label and carry the stuck command's correlation id (if any) in // the diagnostic message instead. diff --git a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/FailoverAlarmConsumer.cs b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/FailoverAlarmConsumer.cs index 1c870d1..0b94d55 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/FailoverAlarmConsumer.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/FailoverAlarmConsumer.cs @@ -290,12 +290,6 @@ public sealed class FailoverAlarmConsumer : IMxAccessAlarmConsumer } /// - /// - /// Both values come from ONE call to the active child, so the snapshot - /// and its verdict cannot end up describing different children across a - /// failover. A failover to the subtag standby therefore reports - /// not-truncated — correctly, since that child performs no capped fetch. - /// public IReadOnlyList SnapshotActiveAlarms(out bool truncated) { if (disposed) throw new ObjectDisposedException(nameof(FailoverAlarmConsumer)); diff --git a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/LmxSubtagAlarmSource.cs b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/LmxSubtagAlarmSource.cs index 2e40d9c..9fa0cd8 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/LmxSubtagAlarmSource.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/LmxSubtagAlarmSource.cs @@ -108,12 +108,9 @@ public sealed class LmxSubtagAlarmSource : ISubtagAlarmSource public event EventHandler? ValueChanged; /// - /// - /// Idempotent per address: an address already advised is skipped - /// rather than re-registered. - /// public void Advise(IReadOnlyCollection itemAddresses) { + // Idempotent per address: an address already advised is skipped rather than re-registered. if (itemAddresses is null) { throw new ArgumentNullException(nameof(itemAddresses)); @@ -144,12 +141,9 @@ public sealed class LmxSubtagAlarmSource : ISubtagAlarmSource } /// - /// - /// Writes with MXAccess user id 0 — always an unsecured Write, never - /// WriteSecured semantics. - /// public void Write(string itemAddress, object? value) { + // Writes with MXAccess user id 0 — always an unsecured Write, never WriteSecured semantics. if (itemAddress is null) { throw new ArgumentNullException(nameof(itemAddress)); diff --git a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessBaseEventSink.cs b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessBaseEventSink.cs index d3903c9..21d8e4b 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessBaseEventSink.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessBaseEventSink.cs @@ -85,12 +85,7 @@ public sealed class MxAccessBaseEventSink : IMxAccessEventSink, IWriteCompletion /// public MxAccessValueCache ValueCache => valueCache; - /// - /// The OnWriteComplete completion cache populated by this sink. Exposed - /// via so the - /// MxAccessSession can share the same instance with the write command - /// executor's bounded completion wait. - /// + /// public MxAccessWriteCompletionCache WriteCompletionCache => writeCompletionCache; /// diff --git a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessCommandExecutor.cs b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessCommandExecutor.cs index ee6005b..4442805 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessCommandExecutor.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessCommandExecutor.cs @@ -1025,7 +1025,7 @@ public sealed class MxAccessCommandExecutor : IStaCommandExecutor /// /// Captures the write-completion baseline for / /// immediately before a Write/Write2/WriteSecured/ - /// WriteSecured2 COM call. Pumps once first (WRK-32) so an OnWriteComplete + /// WriteSecured2 COM call. Pumps once first so an OnWriteComplete /// already queued for this handle — most commonly the still-undelivered callback of an /// earlier write to the same tag that this worker gave up waiting on — folds into the /// baseline instead of being available to ride this write's reply. diff --git a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessEventQueue.cs b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessEventQueue.cs index 53686c5..528a526 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessEventQueue.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessEventQueue.cs @@ -17,7 +17,7 @@ namespace ZB.MOM.WW.MxGateway.Worker.MxAccess; /// Ownership invariant: takes ownership of the /// passed to it — it stamps the worker sequence and /// timestamp on that same instance and enqueues it directly, without -/// cloning (WRK-11). Every caller must therefore pass a freshly built +/// cloning. Every caller must therefore pass a freshly built /// that it does not retain, reuse, or mutate after the /// call returns. All production callers (MxAccessBaseEventSink, /// MxAccessAlarmEventSink, AlarmCommandHandler) build a new event per @@ -231,7 +231,7 @@ public sealed class MxAccessEventQueue // lock, so a large gateway-pulled drain kept the STA's next Enqueue blocked for the whole // sizing walk — exactly when a burst of COM callbacks needs the lock. Measuring on the // enqueuing thread is safe on both counts: that thread owns this event exclusively at this - // point, and WRK-11 (see the class remarks) makes the hand-off final — nothing mutates an + // point, and the ownership invariant (see the class remarks) makes the hand-off final — nothing mutates an // event after Enqueue, so a memoized size can never go stale. // // Enqueue itself stamps the worker sequence and worker timestamp, and those must stay under @@ -263,7 +263,7 @@ public sealed class MxAccessEventQueue throw new MxAccessEventQueueOverflowException(capacity); } - // WRK-11: stamp the sequence/timestamp on the caller's own event and + // Stamp the sequence/timestamp on the caller's own event and // enqueue that same instance under the lock instead of cloning. See // the ownership invariant on the class summary — the caller hands the // event over exclusively, so a defensive Clone() here is pure @@ -374,7 +374,7 @@ public sealed class MxAccessEventQueue /// /// The size decision happens inside the queue lock, so an event is dequeued only once it is /// known to fit: an event that does not fit stays at the head for the next call and is never - /// lost (WRK-21). Per-event cost is the serialized WorkerEvent size measured once at + /// lost. Per-event cost is the serialized WorkerEvent size measured once at /// enqueue time — which already includes the event's own tag and length prefix, the same /// shape the reply's events repeated field packs it into — plus /// of pure slack, so the running total stays @@ -528,17 +528,22 @@ public sealed class MxAccessEventQueue // exactly as before. private readonly struct QueuedEvent { + /// Initializes a queued entry from an event and its serialized size. + /// The queued event. + /// The serialized size of the event in bytes. public QueuedEvent(WorkerEvent workerEvent, int size) { Event = workerEvent; Size = size; } - // The queued event, returned unchanged by TryDequeue and both Drain overloads. + /// Gets the queued event, returned unchanged by TryDequeue and both Drain overloads. public WorkerEvent Event { get; } - // Serialized size of Event, valid for the queue's whole lifetime because an enqueued event - // is never mutated again (WRK-11). + /// + /// Gets the serialized size of , valid for the queue's whole lifetime because an enqueued event + /// is never mutated again. + /// public int Size { get; } } } diff --git a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessStaSession.cs b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessStaSession.cs index ae1455c..ef9022a 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessStaSession.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessStaSession.cs @@ -60,8 +60,8 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession // the StaCommandDispatcher (so it does not inflate PendingCommandCount or perturb command dispatch // ordering), which means CaptureHeartbeat would otherwise see no in-flight activity during a long // poll and the watchdog would fault the session at the 15 s grace instead of the 75 s ceiling - // granted to dispatched commands. Surfacing the poll on the heartbeat closes that asymmetry - // (WRK-27). Volatile: written on the STA thread, read on the heartbeat thread. + // granted to dispatched commands. Surfacing the poll on the heartbeat closes that asymmetry. + // Volatile: written on the STA thread, read on the heartbeat thread. private volatile bool staAlarmPollInProgress; private bool disposed; @@ -204,6 +204,7 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession /// environment variable; a missing or invalid value falls back to /// . /// + /// The resolved write-completion timeout. internal static TimeSpan ResolveWriteCompletionTimeout() { string? value = Environment.GetEnvironmentVariable(WorkerLaunchEnvironment.WriteCompletionWaitMilliseconds); @@ -344,8 +345,8 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession await staRuntime.InvokeAsync( () => { - // Advertise the poll to the watchdog for exactly the span of the COM call - // (WRK-27): set on the STA thread immediately before the affinity check and + // Advertise the poll to the watchdog for exactly the span of the COM call: + // set on the STA thread immediately before the affinity check and // PollOnce, clear in the finally so a heartbeat captured mid-poll reports // StaCallInProgress and one captured after does not. staAlarmPollInProgress = true; diff --git a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessWriteCompletionCache.cs b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessWriteCompletionCache.cs index f846cd2..9488e8b 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessWriteCompletionCache.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessWriteCompletionCache.cs @@ -203,6 +203,9 @@ public sealed class MxAccessWriteCompletionCache /// private readonly struct CompletionEntry { + /// Initializes a completion entry. + /// The monotonic completion version. + /// The statuses reported for the completion. public CompletionEntry( ulong version, RepeatedField statuses) @@ -211,8 +214,10 @@ public sealed class MxAccessWriteCompletionCache Statuses = statuses; } + /// Gets the monotonic completion version. public ulong Version { get; } + /// Gets the statuses reported for the completion. public RepeatedField Statuses { get; } } } diff --git a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/SubtagAlarmConsumer.cs b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/SubtagAlarmConsumer.cs index 0d0292c..cca921d 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/SubtagAlarmConsumer.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/SubtagAlarmConsumer.cs @@ -152,13 +152,6 @@ public sealed class SubtagAlarmConsumer : IMxAccessAlarmConsumer } /// - /// - /// is always : subtag - /// mode is advise-driven over a fixed watch list, so there is no bulk - /// fetch and no per-fetch cap to hit. Subtag snapshots are lower-fidelity - /// in other ways, which MxAlarmSnapshotRecord.Degraded already - /// reports; truncation is not one of them. - /// public IReadOnlyList SnapshotActiveAlarms(out bool truncated) { truncated = false; diff --git a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WnWrapAlarmConsumer.cs b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WnWrapAlarmConsumer.cs index ccb3b12..5f3a7f4 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WnWrapAlarmConsumer.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WnWrapAlarmConsumer.cs @@ -381,18 +381,6 @@ public sealed class WnWrapAlarmConsumer : IMxAccessAlarmConsumer } /// - /// - /// This is the set the gateway's reconcile pass diffs its cache - /// against, so an alarm missing here is what the gateway reads as a - /// Clear. After a truncated fetch the snapshot deliberately still - /// carries alarms the capped reply could not mention — see - /// . Erring toward "still active" keeps the feed - /// at-least-once: a stale entry is repaired by the next sub-cap poll, - /// whereas a dropped one is broadcast as a Clear that never happened. - /// The snapshot and are produced under one - /// syncRoot acquisition, the same one - /// writes them both under, so no poll can interleave between them. - /// public IReadOnlyList SnapshotActiveAlarms(out bool truncated) { if (disposed) throw new ObjectDisposedException(nameof(WnWrapAlarmConsumer)); diff --git a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WorkerEventDrainResult.cs b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WorkerEventDrainResult.cs index 4639469..f015540 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WorkerEventDrainResult.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WorkerEventDrainResult.cs @@ -11,7 +11,7 @@ namespace ZB.MOM.WW.MxGateway.Worker.MxAccess; /// maximum: byte-heavy events (large string or array MxValues) overshoot the frame max /// long before the count ceiling is reached, and the writer's per-frame rejection then /// destroys events that were already removed from the queue. The byte-budgeted drain sizes -/// the reply while draining, so an event that does not fit is never dequeued (WRK-21), and +/// the reply while draining, so an event that does not fit is never dequeued, and /// this result carries the truncation facts the reply's DiagnosticMessage reports — /// no contract change is needed to express them. /// Plain constructor and get-only properties: the worker targets .NET Framework 4.8, which diff --git a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WorkerRuntimeHeartbeatSnapshot.cs b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WorkerRuntimeHeartbeatSnapshot.cs index 2b8e05f..2f06115 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WorkerRuntimeHeartbeatSnapshot.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WorkerRuntimeHeartbeatSnapshot.cs @@ -11,8 +11,7 @@ public sealed class WorkerRuntimeHeartbeatSnapshot /// Sequence number of the most recent event. /// Correlation ID of the in-flight command. /// - /// True while an STA call outside the command dispatcher is executing (currently the alarm poll, - /// WRK-27). The watchdog treats this like an in-flight command: it suppresses the stale-STA fault + /// True while an STA call outside the command dispatcher is executing (currently the alarm poll). The watchdog treats this like an in-flight command: it suppresses the stale-STA fault /// up to the stuck ceiling instead of the shorter grace, so a healthy-but-slow poll does not fault /// a healthy session. Named generically so any future non-dispatcher STA work reuses it. /// @@ -50,7 +49,7 @@ public sealed class WorkerRuntimeHeartbeatSnapshot /// /// Gets a value indicating whether an STA call outside the command dispatcher (the alarm poll) is /// executing. When true the watchdog grants the poll the same grace-to-ceiling suppression as a - /// dispatched command (WRK-27). + /// dispatched command. /// public bool StaCallInProgress { get; } }