From 1a08b4f746d2a4f7c7d7106fedc70363885c6ab1 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Wed, 11 Nov 2026 15:15:05 -0500 Subject: [PATCH] fix(galaxy): #872 detail-0 failure rows route on category, not detected_by Every raw detected_by value names an MXAccess layer (requesting/responding LMX, NMX, automation object); the gateway's own failures arrive as ProtocolStatus. The 'non-zero source = transport failure' rule made a failure the responding object detected a comms error. Item 1 of #872 (Warning/Pending categories) stays open pending a captured row. Claude-Session: https://claude.ai/code/session_01GT67AD99zQQqpLCVm3p7xk --- .../Runtime/StatusCodeMap.cs | 14 ++++++-------- .../Runtime/StatusCodeMapTests.cs | 16 +++++++++++----- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy/Runtime/StatusCodeMap.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy/Runtime/StatusCodeMap.cs index c316a2bcd..0fe9b7b3a 100644 --- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy/Runtime/StatusCodeMap.cs +++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy/Runtime/StatusCodeMap.cs @@ -128,14 +128,12 @@ internal static class StatusCodeMap // generic Bad (#866, a stopped platform's ScanState on VD03), MX_E_InvalidObjectId (8) surfaced as // BadNotConnected, MX_E_Overflow (20) as BadCommunicationError. A failure row is also never Good or // Uncertain, whatever its detail. - if (status.Detail != 0) return FromCategory(status, logger); - - // detected_by != Mxaccess (raw_detected_by != the MXAccess source enum) implies - // the failure happened pre-call (gateway, worker, transport) — surface as a - // communication error rather than a generic Bad. - if (status.RawDetectedBy != 0) return BadCommunicationError; - - return Bad; + // + // Detail 0 routes the same way. It used to become BadCommunicationError whenever raw_detected_by != 0, on + // the belief that a non-zero source meant a pre-MXAccess transport failure — but every detected_by value + // names an MXAccess layer (0 requesting LMX, 1 responding LMX, 2/3 NMX, 4/5 automation object; the + // gateway's own failures arrive as ProtocolStatus, never as a status row), so the category decides (#872). + return FromCategory(status, logger); } /// diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/Runtime/StatusCodeMapTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/Runtime/StatusCodeMapTests.cs index 83c24ae09..64f99ced1 100644 --- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/Runtime/StatusCodeMapTests.cs +++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/Runtime/StatusCodeMapTests.cs @@ -140,12 +140,18 @@ public sealed class StatusCodeMapTests logger.Entries.Count.ShouldBe(1); } - /// Verifies that non-zero DetectedBy maps to communication error when detail is zero. - [Fact] - public void FromMxStatus_SuccessZero_DetailZero_DetectedByNonZero_IsCommunicationError() + /// + /// #872: detected_by names an MXAccess layer (3 = responding NMX), not a transport failure, so a detail-0 + /// failure row routes on its category — a communication-category row is still BadCommunicationError. + /// + [Theory] + [InlineData(MxStatusCategory.Unspecified, 0x80000000u)] + [InlineData(MxStatusCategory.CommunicationError, 0x80050000u)] + [InlineData(MxStatusCategory.ConfigurationError, 0x80890000u)] + public void FromMxStatus_SuccessZero_DetailZero_RoutesByCategory_NotDetectedBy(MxStatusCategory category, uint expected) { - var s = new MxStatusProxy { Success = 0, Detail = 0, RawDetectedBy = 3 }; - StatusCodeMap.FromMxStatus(s).ShouldBe(StatusCodeMap.BadCommunicationError); + var s = new MxStatusProxy { Success = 0, Detail = 0, RawDetectedBy = 3, Category = category }; + StatusCodeMap.FromMxStatus(s).ShouldBe(expected); } /// Verifies that all-zero status maps to Bad status code.