fix(ab): Task 22 review r1 — release-note timing and AbLegacy regressions, probe E2E on both transports

- I1: the upgrade restart itself moves every no-key AbCip/AbLegacy driver to Native (every process
  start constructs drivers, incl. boot-from-cache). Docs + CLAUDE.md now say so and tell operators
  to set "transport": "Libplctag" BEFORE upgrading (older builds ignore the unknown key).
- I2: AbLegacy release note leads with the status-bit write refusal (T4:0.DN, C5:0.CU, R6:0.EN, ...)
  on defaulted SLC/MicroLogix drivers and the fallback. The real client status is
  BadCommunicationError, not BadNotSupported (the driver's write catch-all maps the runtime's
  AbTransportException like any transport failure, as #571 does for PLC-5), with handle eviction
  and Degraded health; pinned by a driver-level test (no 0xAA write on the wire, with a positive
  control). ST string byte order stated as unverified. Also corrects the docs' "T4:0/DN" examples:
  the driver's address grammar is "T4:0.DN" ("/DN" is a malformed address).
- I3: DriverProbeHandshakeE2eTests' two AbCip probe tests are a [Theory] over Native and
  Libplctag. Run against ab_server controllogix: all 4 rows pass with identical exact messages.
- M1: documented (docs + resolver XML doc): the AdminUI merges only the probed device, so a
  mixed-family defaulted AbLegacy driver can probe Native while running Libplctag.
- M2: factory ParseEnum rejects undefined integer strings ("7") via Enum.IsDefined (both drivers).
- M3: AbCip driver-level bad value reads "AB CIP config in '<id>' has unknown Transport ..."; the
  named constant is gone (tagName: null now means driver-level).
- M4: the explicit-Native limited-family Warning is once per instance, not per (re)init.
- Nit: EffectiveTransport docs note that injected factories bypass it.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-07 21:06:40 -04:00
parent ed0881d672
commit 1806af54f0
13 changed files with 211 additions and 34 deletions
+8 -4
View File
@@ -97,7 +97,8 @@ The driver has two interchangeable wire transports, selected by one `DriverConfi
- An absent key means "use the default". It is resolved in one place, `AbCipTransportResolver`, which the
driver, the Test Connect probe and the CLI all go through. An explicit value always wins.
- A bad value fails like a bad `PlcFamily`; the Test Connect probe reports "Config JSON is invalid".
- A change takes effect when the driver instance is **recreated**. A deploy that changes `DriverConfig`
- A change takes effect when the driver instance is **constructed**: on every process start (so the upgrade
restart moves no-key drivers to Native — see the release note) and when a deploy that changes `DriverConfig`
does this (`DriverHostActor` respawns the child, #516). `ReinitializeAsync` alone keeps the transport
it was constructed with.
- AdminUI: the **Transport** dropdown in the advanced section of the AbCip driver form ("Default (Native)" /
@@ -120,8 +121,11 @@ The driver has two interchangeable wire transports, selected by one `DriverConfi
authored type; see [Native transport behaviour](#native-transport-behaviour).
- **To fall back**, set `"transport": "Libplctag"` in the driver config (or pick "libplctag (fallback)" in
the AdminUI dropdown).
- **When it takes effect:** a change of transport — including this default flip for an existing driver with
no key — takes effect when a deploy recreates the driver instance, not on `ReinitializeAsync`.
- ⚠️ **When it takes effect: the upgrade restart itself.** Every process start constructs the drivers
(including boot-from-cache on a driver-only node), so **every driver with no `transport` key moves to Native
the moment the upgraded node starts** — no deploy is needed. To keep a driver on libplctag, add
`"transport": "Libplctag"` and deploy it **before upgrading**: older builds ignore the unknown key. After the
upgrade, a transport change takes effect when a deploy recreates the driver (not on `ReinitializeAsync`).
- **Evidence, and its limits:** AbCip **reads** (plus browse and templates) were proven on a production
CompactLogix 5069-L320ERS2 (**Z-35162**, read-only, 2026-10-08; see
[gate results](../plans/2026-10-07-native-ab-transport-gate-results.md) §Real press). AbCip **writes** are
@@ -153,7 +157,7 @@ The driver has two interchangeable wire transports, selected by one `DriverConfi
writes are gated on a mismatch too.
- Malformed tag names put the device into backoff, as under libplctag (#570). STRING capacity includes the
alignment pad (84, not 82) (#570).
- Status-bit sub-element writes (e.g. `T4:0/DN`) are Unsupported natively (they would clobber the control
- Status-bit sub-element writes (e.g. `T4:0.DN`) are Unsupported natively (they would clobber the control
word); nothing goes on the wire. Sub-element arrays are Unsupported natively.
## Known gaps
+28 -5
View File
@@ -108,9 +108,17 @@ PCCC-over-EtherNet/IP client (`Driver.Ab.Transport`).
the CLI): an absent key resolves to `Native`, **unless any configured device has `PlcFamily` `Plc5` or
`LogixPccc`** — then the whole driver resolves to `Libplctag`, because the transport is chosen once per driver,
not per device. An explicit value always wins: explicit `Native` with a PLC-5 / `LogixPccc` device is allowed
(an operator choice) and logs one Warning at init naming the limitation.
(an operator choice) and logs one Warning per driver instance naming the limitation.
A change takes effect when the driver instance is recreated (a deploy that changes `DriverConfig`). The
⚠️ **Test Connect in a mixed-family driver can disagree with the runtime.** The AdminUI probe merges the
driver config with **only the device being probed**, so in a defaulted driver holding an SLC 500 and a PLC-5,
Test Connect on the SLC device resolves `Native` while the deployed driver runs every device on `Libplctag`.
SLC 500 works on both stacks, so this is cosmetic; the reverse (probe on libplctag, driver on native) cannot
happen. Set `transport` explicitly to make them agree.
A change takes effect when the driver instance is constructed: on a deploy that changes `DriverConfig`
(which recreates it) **and on every process start**, including the restart that installs this build (see the
release note). The
AdminUI form's **Transport** dropdown offers "Default (Native; libplctag for PLC-5 / LogixPccc)" (omits the
key on save), "Native (managed)" and "libplctag (fallback)". The driver logs
`transport=<effective> (default|configured)` on every init, e.g. `transport=Libplctag (default; PLC-5/LogixPccc
@@ -124,6 +132,16 @@ Native routing: SLC500/MicroLogix/PLC-5 are direct; `logixpccc` uses Unconnected
### Release note — native is the default (Task 22)
- ⚠️ **Status-bit writes are refused on the default.** An SLC 500 / MicroLogix driver on the native default
**refuses writes to status-bit sub-elements** — `T4:0.DN`, `T4:0.TT`, `T4:0.EN`, `C5:0.CU`, `C5:0.DN`,
`R6:0.EN`, and the rest. The client sees **`BadCommunicationError`** (not `BadNotSupported`: the driver's
write path maps the refusal like any transport failure, as for the PLC-5 refusal in #571), the tag's handle
is evicted, and driver health goes `Degraded` with "Writes to a status-bit sub-element … are not supported by
the native transport". **Nothing is written to the PLC.** libplctag performed a masked bit write here, so an
authored writable status-bit tag that worked before the upgrade fails after it. This is deliberate: a native
read-modify-write of the control word would race the PLC updating TT/DN and could clobber EN/TT/DN, and such
writes are rare. **Drivers that need them must set `"transport": "Libplctag"`.** Reads of status bits are
unaffected. Word sub-elements (`T4:0.PRE`, `T4:0.ACC`, `R6:0.LEN`, `R6:0.POS`) still write natively.
- **The native managed transport is now the default** for SLC 500 and MicroLogix. A driver config with no
`transport` key runs `Native`; libplctag remains available as the fallback.
- **Exclusion:** a defaulted driver with **any PLC-5 or `LogixPccc` device stays on libplctag**. Native PLC-5
@@ -131,9 +149,14 @@ Native routing: SLC500/MicroLogix/PLC-5 are direct; `logixpccc` uses Unconnected
there, with a Warning at init.
- ⚠️ **Client-visible:** native reports **`BadTypeMismatch`** for a mis-authored same-width type, where
libplctag returned Good garbage (see [AbCip.md §Native transport behaviour](AbCip.md#native-transport-behaviour)).
- **ST (string) files:** the native ST file byte order is **unverified** (no capture), so string values read or
written natively may be byte-swapped. Drivers with ST tags whose values matter should fall back.
- **To fall back**, set `"transport": "Libplctag"` in the driver config.
- **When it takes effect:** when a deploy recreates the driver instance (not on `ReinitializeAsync`). An existing
driver with no key moves to Native on its next recreate.
- ⚠️ **When it takes effect: the upgrade restart itself.** Every process start constructs the drivers
(including boot-from-cache on a driver-only node), so **every driver with no `transport` key moves to
Native the moment the upgraded node starts** — no deploy is needed. To keep a driver on libplctag, add
`"transport": "Libplctag"` and deploy it **before upgrading**: older builds ignore the unknown key. After the
upgrade, a transport change takes effect when a deploy recreates the driver (not on `ReinitializeAsync`).
- **Evidence, and its limits:** all of AbLegacy is **simulator-proven only** (`ab_server`). No real SLC,
MicroLogix or PLC-5 has been exercised. (The real-press gate, Z-35162 2026-10-08, covered AbCip reads only.)
@@ -144,7 +167,7 @@ Native routing: SLC500/MicroLogix/PLC-5 are direct; `logixpccc` uses Unconnected
- **PLC-5 native is N-file reads only.** Every other file type and all PLC-5 writes are refused, currently
surfacing as `BadCommunicationError` rather than `BadNotSupported` (#571). The default excludes PLC-5 and
`logixpccc` (they resolve to libplctag unless `Native` is set explicitly).
- Status-bit sub-element writes (e.g. `T4:0/DN`, `C5:0/DN`) are Unsupported natively (they would clobber the
- Status-bit sub-element writes (e.g. `T4:0.DN`, `C5:0.DN`) are Unsupported natively (they would clobber the
control word); nothing goes on the wire. Sub-element arrays are Unsupported natively. L-file bit indexes
above 15 are allowed.
- **ST (string) file layout is uncaptured**, so the string byte order is unverified natively.