parity: triage 3 false-positives from first-rig run (2026-04-30)

After running the matrix end-to-end against the live rig for the
first time, three of the nine failures were false positives — bugs in
the harness and test invariants, not real backend deltas:

1. ParityHarness configured the legacy backend with
   OTOPCUA_GALAXY_BACKEND=db, which is Discover-only. Reads, writes,
   and reinits all returned "MXAccess code lift pending — DB-backed
   backend covers Discover only". Switched to mxaccess backend; the
   ZB connection string still drives the discovery path.

2. HistoryReadParityTests asserted "neither backend implements
   IHistoryProvider" — but the legacy GalaxyProxyDriver still does
   (it's an accepted back-compat delta retired in PR 7.2). The
   architectural pin we *want* is "the new path doesn't regress to
   per-driver history", so the test now asserts only the mxgw side.

3. AlarmTransitionParityTests strict-pinned the five sub-attribute
   refs (InAlarmRef, etc.) on the legacy condition. PR 2.1 added
   those refs specifically so the new mxgw driver could populate them
   via AlarmRefBuilder; legacy pre-dates PR 2.1 and leaves them null
   — that's correct, not a regression. Test now asserts a one-way
   invariant: when legacy populated a ref, mxgw must match. When
   legacy is null, mxgw is free to populate (the mxgw → server-side
   AlarmConditionService direction).

The six remaining failures are real:

- 2 from the gw-side `[]` array suffix (filed in
  mxaccessgw/requirements-array-suffix-fix.md)
- 2 write-StatusCode mapping deltas (0x80050000 vs 0x80020000) —
  Bad-status both ways but mapped to different OPC UA codes
- 1 event-rate ratio of 5x (mxgw dispatches 5x legacy in the same
  3s window)
- (Plus the 2 ScanState scenarios that skip cleanly — single-platform
  rig as documented)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-04-30 03:00:44 -04:00
parent 580c45f494
commit 5e890ec9d6
3 changed files with 38 additions and 11 deletions

View File

@@ -56,10 +56,29 @@ public sealed class AlarmTransitionParityTests
$"alarm severity parity for '{kvp.Key}'");
mxgw[kvp.Key].SourceName.ShouldBe(kvp.Value.SourceName,
$"alarm SourceName parity for '{kvp.Key}'");
mxgw[kvp.Key].InAlarmRef.ShouldBe(kvp.Value.InAlarmRef,
$"alarm InAlarmRef parity for '{kvp.Key}'");
mxgw[kvp.Key].DescAttrNameRef.ShouldBe(kvp.Value.DescAttrNameRef,
$"alarm DescAttrNameRef parity for '{kvp.Key}'");
// PR 2.1 added the five sub-attribute refs (InAlarmRef / PriorityRef /
// DescAttrNameRef / AckedRef / AckMsgWriteRef) so the new server-side
// AlarmConditionService can subscribe + ack-write without help from the
// driver. The new mxgw GalaxyDriver populates them via AlarmRefBuilder
// (PR 4.1). The legacy GalaxyProxyDriver pre-dates PR 2.1 and leaves them
// null — that's an accepted delta until the legacy backend retires in
// PR 7.2. Asserting "mxgw populated when legacy didn't" is *correct*
// behavior, not a regression.
//
// We pin the weaker invariant: if legacy populated a ref, mxgw must
// populate the same value. If legacy is null, mxgw is allowed to be
// either null or populated (the population-from-AlarmRefBuilder direction).
if (kvp.Value.InAlarmRef is not null)
{
mxgw[kvp.Key].InAlarmRef.ShouldBe(kvp.Value.InAlarmRef,
$"alarm InAlarmRef parity for '{kvp.Key}' (both populated)");
}
if (kvp.Value.DescAttrNameRef is not null)
{
mxgw[kvp.Key].DescAttrNameRef.ShouldBe(kvp.Value.DescAttrNameRef,
$"alarm DescAttrNameRef parity for '{kvp.Key}' (both populated)");
}
}
}