test(717): deferred-event boundary, ACL-filtered promotion refresh, native-ack refusal, effective-URI XML load; docs + T13 overlay
v2-ci / unit-tests (push) Has been cancelled

- Wire: an event reported while Active but deferred (#838) is delivered after demotion; the promotion refresh
  withholds an ACL-denied condition (positive control alongside); ConditionRefresh2 and model-change events on the
  Standby; a native Acknowledge is refused and never routed; Warm validates and advertises the EFFECTIVE
  ApplicationUri from an ApplicationConfigPath XML load.
- docs/Redundancy.md: Warm section, the Unset/None i=11314 behaviour (orphan readable by NodeId under Unset,
  deleted under None), instruments; OpcUaServer.md, Configuration.md, security.md, cluster-topology rule.
- docker-dev/docker-compose.warm.yml for the T13 gate (central pair, Mode=Warm).
- Plan: implementation status and the test gaps left for T13/follow-up.

Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
This commit is contained in:
Joseph Doherty
2026-11-13 03:39:49 -05:00
parent 7829b937d1
commit f817c77e02
9 changed files with 357 additions and 14 deletions
@@ -255,13 +255,9 @@ The T0-adopted alternative (owner, 2026-11-13). The 1.5 object-keyed provenance
## T13: Live gate on docker-dev (central pair, `:4840` / `:4841`)
**Overlay:** new `docker-dev/docker-compose.warm.yml`, central-1/2 only. It sets:
- `OpcUa__ApplicationUri=urn:OtOpcUa:central-1|2` (matching `seed-clusters.sql`);
- `OpcUa__PeerApplicationUris__0=<the other>`;
- `OpcUa__Redundancy__Mode=Warm`.
Today both centrals advertise the default `urn:OtOpcUa` (spec §2). Login is disabled on the rig, so drive
**Overlay:** `docker-dev/docker-compose.warm.yml`, central-1/2 only. It sets `OpcUa__Redundancy__Mode=Warm`. The base
compose file already gives each central its own `OpcUa__ApplicationUri=urn:OtOpcUa:central-1|2` (matching
`seed-clusters.sql`) and its partner in `OpcUa__PeerApplicationUris__0` (lmxopcua#801, after spec §2 was written). Login is disabled on the rig, so drive
everything yourself. Use `otopcua-cli` from `src/Client/ZB.MOM.WW.OtOpcUa.Client.CLI` against both ports.
| Leg | Procedure | Pass |
@@ -300,3 +296,20 @@ Set `OpcUa:Redundancy:Mode=Unset` on both nodes and restart them. The setting is
nothing persistent to undo: no schema changes and no artifact changes. The `Revision` field stays and is harmless,
because unrevisioned handling is preserved. Rolling back ScadaBridge is independent; its ServiceLevel-aware
failover also works against `Unset` servers.
## Implementation status (2026-11-13)
T1–T10 and T12 are implemented on `master`, default-off (`OpcUa:Redundancy:Mode` defaults to `Unset`, pinned by
`WarmRedundancyWireTests.Unset_is_todays_server_exactly` and `OpcUaPublishActorWarmTests.Unset_never_drives_the_projection_and_keeps_todays_levels`).
T11 is ScadaBridge#138 (separate). T13 has not run: `docker-dev/docker-compose.warm.yml` is ready (see T13).
Test homes: `WarmStandbyUnitTests` (OpcUaServer.Tests: marker, rewrite, decision record, the SDK-field regression pin,
option rules), `WarmRedundancyWireTests` (OpcUaServer.IntegrationTests: metadata per mode, read projection, write and
Part 9 refusal, the three T0 demotion timings, live-item queue, alarm suppression/refresh, effective-URI XML load),
`WarmRedundancyRevisionTests` + `WarmServiceLevelBandTests` (ControlPlane.Tests), `WarmRoleAuthorityTests` +
`OpcUaPublishActorWarmTests` (Runtime.Tests), `OpcUaRedundancyValidationTests` (Host.IntegrationTests).
Not yet covered by a test (left for T13 or a follow-up): the real 2-node singleton-handover test and the
"removed node cannot rejoin" pin (T3; ordering is covered at gate and actor level), a DataEncoding read, a
deleted/reused monitored item between queue and publish and the ExtensionObject/DiagnosticInfo clone matrix (T7), the
ACL-stood-down item on transition (T8), and per-counter MeterListener tests beyond `mode_mismatch` (T10).