diff --git a/archreview/plans/R2-04-failure-visibility-plan.md.tasks.json b/archreview/plans/R2-04-failure-visibility-plan.md.tasks.json index 260ece96..df8e7382 100644 --- a/archreview/plans/R2-04-failure-visibility-plan.md.tasks.json +++ b/archreview/plans/R2-04-failure-visibility-plan.md.tasks.json @@ -9,7 +9,7 @@ { "id": "T5", "subject": "Galaxy internal IMxWriteOps seam under GatewayGalaxyDataWriter (sealed SDK session untestable) (06/S-1)", "status": "completed", "blockedBy": [] }, { "id": "T6", "subject": "Galaxy fail-closed write on AdviseSupervisory failure -> BadCommunicationError, no raw write", "status": "completed", "blockedBy": ["T5"] }, { "id": "T7", "subject": "Galaxy counters galaxy.writes.advise_failed + galaxy.writes.unconfirmed (empty-statuses Good metered)", "status": "completed", "blockedBy": ["T6"] }, - { "id": "T8", "subject": "Galaxy skip-gated live smoke: normal advised write still returns Good post-change", "status": "pending", "blockedBy": ["T6"] }, + { "id": "T8", "subject": "Galaxy skip-gated live smoke: normal advised write still returns Good post-change", "status": "completed", "blockedBy": ["T6"] }, { "id": "T9", "subject": "PrimaryGatePolicy pure class + truth-table tests + otopcua.redundancy.primary_gate_denied counter (03/S4)", "status": "pending", "blockedBy": [] }, { "id": "T10", "subject": "Wire DriverHostActor gates (:977/:1039/:1095) through PrimaryGatePolicy with member-count provider + S5 log-once", "status": "pending", "blockedBy": ["T9"] }, { "id": "T11", "subject": "DriverHostActorPrimaryGateTests: deny-on-unknown multi-node, allow single-node, ack/emit variants, meter tags", "status": "pending", "blockedBy": ["T10"] }, diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/Runtime/GatewayGalaxyLiveReopenAndWriteTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/Runtime/GatewayGalaxyLiveReopenAndWriteTests.cs index f2a3cb1e..95a2e68c 100644 --- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/Runtime/GatewayGalaxyLiveReopenAndWriteTests.cs +++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/Runtime/GatewayGalaxyLiveReopenAndWriteTests.cs @@ -173,6 +173,34 @@ public sealed class GatewayGalaxyLiveReopenAndWriteTests $"borrow smoke: subscribed {WriteRef} -> handle {match.ItemHandle}; borrowed write Good with AddItemCallCount=0; control AddItemCallCount=1"); } + /// + /// archreview 06/S-1 guard — after the fail-closed advise change, a NORMAL advised no-login write + /// must STILL return Good (0). Guards against over-eager fail-closing: the advise round-trip succeeds + /// on a healthy gateway, so the raw Write proceeds and commits. If this ever returns Bad on a healthy + /// gateway, the fail-closed branch is mis-judging a successful advise. Skip-gated like the others. + /// + [Fact] + public async Task Live_normal_advised_write_still_returns_good_under_failclosed_advise() + { + var (endpoint, apiKey) = RequireLiveGatewayOrSkip(); + var ct = TestContext.Current.CancellationToken; + var clientOptions = BuildClientOptions(endpoint, apiKey); + + await using var session = new GalaxyMxSession(new GalaxyMxAccessOptions(ClientName: "OtOpcUaFailClosedGuard")); + await session.ConnectAsync(clientOptions, ct); + var writer = new GatewayGalaxyDataWriter(session, writeUserId: 0); + + var result = await writer.WriteAsync( + [new WriteRequest(WriteRef, 5151.0f)], _ => SecurityClassification.FreeAccess, ct); + + result.ShouldHaveSingleItem().StatusCode.ShouldBe(0u, + "a normal advised write must still return Good after the fail-closed advise change (no over-eager fail-close)"); + writer.CachedSupervisedHandleCount.ShouldBe(1, "the healthy advise should have supervised the handle"); + + TestContext.Current.SendDiagnosticMessage( + $"fail-closed guard smoke: normal advised write {WriteRef}=5151 returned Good; handle supervised"); + } + private static (string Endpoint, string ApiKey) RequireLiveGatewayOrSkip() { var endpoint = Environment.GetEnvironmentVariable("MXGW_ENDPOINT");