A. #566 on a real CompactLogix (Z-35162, read-only): a class-3 connection idled 60 s past its
16 s timeout is closed at the TCP level, not with CIP 0x0107/0x0203; the next request fails on
its write (Broken pipe -> BadConnection) in 9 ms and the one after reopens. The transport already
handles this, so no production behaviour changed; the shape is pinned offline by
CipConnectionTests.A_target_that_resets_an_idle_session_... (FakeEipServer.ResetAllConnections),
proven red with a reconnect-skipping mutation. The diagnostics-only exchange tap now also sees
Forward Open replies (granted RPIs).
B. Native STRING write through the driver on Program:ES03_TemperatureControl._StrTmp: write,
read back, restored byte-exact (user-approved). Array write NOT run: the census's unreferenced
DINT[32] _SEQ_EP arrays are aliased sequencer step registers (raw L5X grep), so none qualifies.
#567 still impossible (a 128 B array / 88 B STRING never fragments).
New env-gated harness: AbCip.IntegrationTests/HardwareGate (IdleConnectionEvidenceTests,
StringWriteGateTests). Docs: gate results (hw5 section + raw output), AbCip.md, CLAUDE.md.
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
- Process-wide CipConnection statics (ForwardOpenCount, Forward Close counts, RequestsSent, exchange
stats, OpenAttempts, OpenAttemptBudget) move to an instance ConnectionDiagnostics owned by each
DeviceSessionPool (and by a stand-alone CipConnection). The open-attempt budget becomes a disposable
per-DeviceKey limit (LimitOpenAttempts); the differential harness scopes one to the press under test.
- LibplctagOrphanRelease.ForceRootingForTest is gone: ReleaseIfOrphaned(tag, canReadHandles) overload.
- A connected reply with general 0x01 / extended 0x0107 or 0x0203 (target no longer holds the class-3
connection, TCP still up) retires the session at once; the next send reopens through the throttle.
- New tests: dispose during RegisterSession, abandoned-open observer (falsified), scheduler-dispose-throws
guard (DeviceSessionPool.CloseAsync), release-in-background with a shared open in flight, per-device
limit scoping, CIP-close recycle (request + keep-alive + negative control).
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
#570
- STRING: the built-in 88-byte Logix STRING holds 82 characters (LEN + DATA[82] + 2 pad), not 84; the
write still sends the whole 88-byte structure and a read never decodes the pad as characters.
- Mistype churn: a type refusal (CIP 0xFF/0x2107) on the existence read no longer throws from
InitializeAsync, so the runtime is not disposed/re-created (and re-Forward-Opened) every poll; the driver
also keeps a runtime that throws TypeMismatch from init and records the device as reachable.
- Malformed tag name: a TagPath that does not parse, or a name the CIP symbolic encoder refuses (now also
non-ASCII, empty member, >255-char member, unterminated subscript), is AbCipTagConfigurationException ->
per-tag BadConfigurationError (named const in AbCipStatusMapper), warned once, never feeding the device
backoff or health; siblings keep reading. A malformed probe tag no longer feeds the backoff either.
- Micro800 BYTE/WORD/LWORD (0xD1/0xD2/0xD4) are type-checked as integers of their width: REAL/LREAL or a
narrower integer over LWORD is TypeMismatch, not decoded garbage.
#579
- ConnectionLease.ReleaseInBackground: one non-blocking release shape for both native AB runtimes. The
AbCip runtime's Dispose no longer waits up to 2 s under the per-tag lock; the pool still registers the
close synchronously so a re-create cannot overlap it. Offline soak tests (25 cycles, AbCip + AbLegacy)
prove no leaked connection; FakeEipServer gains OpenConnections.
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe