- 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
I1: a live verdict no longer waives the value check. Only elements that moved between the two libplctag reads are excused.
- An element equal in both libplctag reads must also be equal through native; otherwise **NO: stable element i**.
- A moved element must not read NaN or infinity on native unless libplctag saw it too; otherwise **NO: non-finite element i**.
- Live tags log their full raw bytes and every element, and the summary reports live tags as N of the total.
- The offline pin adds the pass-1 Real[5] shape: a stable element shifted on native fails. It is red with the element check disabled.
M9: after each native phase (differential and latency), wait for the session close. With AB_DIFF_COUNT_OPENS=1, assert exactly one Forward Close was sent, using the new CipConnection counters; acked is logged.
M8: each latency pass logs its GC collection deltas (gen0/1/2) for both transports, plus native per-exchange round-trip min/mean/max.
M4: the latency test also asserts that no libplctag orphan had to be rooted.
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe