Every deliverable was already on master with the plan's verbatim commit messages (T1 24796f2c, T2 4fca4e1a, T3 1b7f995a, T4 8ce57e47, T5 ec9599e2, T6 5f48f81d). Re-verified: DeploymentArtifactTests 29/29 + MultiClusterScopingTests 1/1 green; central-1 live-serves its slice on the running hub-and-spoke rig. No code written.
Two bugs caught by live verification against the mxaccessgw at 10.100.0.48:5120:
- MaxAttempts=1 produced an invalid Polly RetryStrategyOptions -> the probe failed
on every real gateway. Removed the Retry override (matches GalaxyDriver); fail-fast
is already guaranteed by the TCP preflight + the per-call deadline.
- A rejected key surfaces as a typed MxGatewayAuthenticationException, not a raw
RpcException, so 'auth-rejection = reachable' was bypassed. Catch the typed auth/
authorization exceptions -> Ok=true.
Adds DriverProbeHandshakeE2eTests: direct-probe, skip-gated cross-protocol green/red
discrimination (Modbus, OpcUaClient, Galaxy + a local real OPC UA server).
Create docs/drivers/TestConnectProbes.md: full reference for the Phase 5
protocol-handshake probes — result contract, per-driver handshake table,
TwinCAT/FOCAS/Galaxy degrade semantics, live-verify scope, and the
Historian.Wonderware already-done note. Annotate the Phase 7 step in
docs/plans/2026-05-28-adminui-driver-pages-design.md with a shipped note
pointing at the phase-5 design doc and TestConnectProbes.md.
Replaces the bare-TCP AbLegacyDriverProbe with a two-phase probe:
Phase 1 is the existing TCP preflight; Phase 2 initialises a
LibplctagLegacyTagRuntime (Protocol.ab_eip + per-family PlcType) to
open a real PCCC-over-EIP session, using AbLegacyProbeOptions.ProbeAddress
("S:0") as the probe tag. Status-code discrimination mirrors the AbCip
probe: ErrorNotFound/ErrorNoMatch/ErrorBadDevice → Ok=true "controller
reachable"; transport errors → Ok=false "handshake failed".
Adds AbLegacyDriverProbeTests (5 unit tests, all green, 168 total).
Replace the bare-TCP-only AbCipDriverProbe with a two-phase check:
Phase 1 keeps the existing TCP preflight; Phase 2 initialises a
LibplctagTagRuntime against the first device to open a real EIP session
and CIP Forward Open, so a live-but-rejecting CIP endpoint reads red
instead of a false-positive green.
Status mapping: ErrorNotFound / ErrorNoMatch / ErrorBadDevice → reachable
(controller answered CIP, probe tag absent); ErrorTimeout / ErrorBadConnection
/ ErrorBadGateway / ErrorWinsock / ErrorOpen / ErrorClose / ErrorRead /
ErrorWrite / ErrorBadReply / ErrorRemoteErr / ErrorPartial / ErrorAbort →
handshake failed. LibPlcTagException message text is used as a secondary
signal for the reachable-exception path. All other statuses default to
handshake-failed (conservative).
Add AbCipDriverProbeTests: invalid JSON, no devices, malformed host address,
closed-port TCP rejection, and black-hole timeout — all offline-determinable.
Happy path + CIP-error path covered live against the CIP sim.
Replace the bare TCP-connect return in OpcUaClientDriverProbe with a real
OPC UA GetEndpoints discovery handshake (mirroring SelectMatchingEndpointAsync
in the driver). TCP preflight still fast-fails closed ports; the handshake
confirms the remote is actually an OPC UA server, so a live-but-rejecting
non-OPC-UA process now reads RED instead of a false-healthy green.
Replace bare TCP-connect with a two-phase probe: Phase 1 keeps the
existing SocketException / timeout / generic preflight paths unchanged;
Phase 2 runs Plc.OpenAsync (COTP CR/CC + S7 setup-communication) so a
device that accepts TCP but is not an S7 PLC reads red instead of green.
A linked CTS distinguishes caller cancellation ("timed out") from the
S7netplus internal read-timeout OCE ("handshake failed: timed out").
Replace the bare TCP-connect probe in ModbusDriverProbe with a two-phase
check: TCP connect via ModbusTcpTransport (keeps the same SocketException /
timeout / generic error paths and messages), then a one-shot FC03 Read
Holding Registers (qty 1 @ addr 0). A normal response → Ok=true "Modbus
FC03 OK"; a Modbus exception PDU → Ok=true "Modbus FC03 OK (device
returned exception PDU)"; any other failure after TCP succeeds → Ok=false
"Reachable at host:port but Modbus FC03 handshake failed: …".
Add ModbusDriverProbeTests (6 tests) covering invalid JSON, missing
host/port, closed port, TCP-accept-then-close, canned MBAP happy path,
and Modbus exception PDU path. All 277 Modbus tests green.
Add IFocasClientFactory.EnsureUsable() — a config-time probe called by
FocasDriver.InitializeAsync before any background loops start. The
UnimplementedFocasClientFactory throws NotSupportedException immediately
(faulting the driver at init), eliminating the footgun where a driver on
the 'unimplemented' backend appeared Healthy then failed every read/write/
subscribe silently. WireFocasClientFactory and FakeFocasClientFactory are
no-ops. Backstop Create() throw remains in place.
Make MapDataType internal, split the combined Int64/UInt64 arm to return
DriverDataType.Int64 and DriverDataType.UInt64 respectively, and remove
the now-stale Driver.Modbus-007 caveat doc block and inline comment.
Add a Theory covering both cases; full suite 271/271 green.