e3cb4f0c7b
Review r1 N1 (Important): the I1 fix made IsDeviceRefusal default-open and mapped libplctag's ERR_REMOTE_ERR to an answer. libplctag folds EVERY non-zero PCCC STS and CIP status into that code, including a routing hop's "unconnected send timed out" / "connection lost" / "remote node missing", and native BadReply covers truncated / mismatched / unmapped replies - so a powered-off PLC behind a bridge read as reachable: Healthy, LastSuccessfulRead advancing, the comms-loss watchdog unable to fire. Three classes now, each pinned both ways: - no reply (Timeout/BadConnection/Abort/Pending/unknown): comms fault; - answered - an ALLOW-LIST again (NotFound/NotAllowed/OutOfBounds/TooLarge/ TooSmall/TypeMismatch/DeviceStateConflict): proves reachability; - OPAQUE (BadReply/Partial; ERR_REMOTE_ERR/ERR_BAD_STATUS now translate to the existing BadReply, OPC UA BadCommunicationError as before r0): per-tag Bad, handle kept, coalescing fallback, never fail-fast, never an answer. Option (a): a device whose pass/call produced only opaque outcomes is Degraded with LastSuccessfulRead not advanced, so the watchdog reaches a dead routed PLC. The probe and index reads classify the same way. Minors: - N2: a mismatched array (type vs data file) READS BadTypeMismatch instead of decoding the file at the wrong width as Good. - N3: a swept indirect resolution is never reconnected under a forgotten gate; that one call answers neutrally. - N4: an index read answered Unsupported is neutral, not a transport fault. - N5: ArrayFileMismatch checks indirect addresses (the file letter is static). - Test nit: the evicted-while-waiting test signals gate arrival via a seam instead of sleeping. Live: 51/51 on ab_server (slc500/micrologix/plc5 x both transports), incl. an opaque-only libplctag device staying Degraded with LastSuccessfulRead frozen. Refs #738 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT