fix(s7): access-denied is an answer; review round-1 minors (R07:H4 follow-through)
I-1: a PUT/GET-disabled CPU (every read denied) stayed Faulted with LastSuccessfulRead frozen, so the comms-loss watchdog reconnected and re-subscribed it every 90 s forever. Both access-denied arms now also record an answer: Faulted is kept (worst state wins), the stamp advances, and the sticky write fault still clears only on a real success. Pinned end to end in Runtime.Tests: the real S7Driver under the real DriverInstanceActor watchdog is not reconnected across 2x the threshold; the control (every read timing out) is. Minors: - m-1: injectable pass clock (UtcNow); "LastSuccessfulRead advanced" is now asserted strictly, including the write-refusal case. - m-2: the editor uses S7TagShapeRules.IsByteAnchored instead of its own list. - m-3: a release that races an init publish also cancels the probe loop the publish started after the release took its snapshot. - m-4: a retained poll failing during a lifecycle operation writes no health. - m-5: a multi-var chunk that fell back memoises its members refused alone. - m-7: access-denied needs S7.Net's exact wording and no transport fault in the chain. - m-8: sync teardown clears the write-side Degraded owner; a sync Dispose racing a running shutdown is tested. Refs #742 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
+6
-2
@@ -314,7 +314,7 @@ Portal under *Protection & Security* for the CPU.
|
||||
| Tag not in config | `BadNodeIdUnknown` | unchanged |
|
||||
| Read-only tag written | `BadNotWritable` | unchanged |
|
||||
| Unimplemented data type | `BadNotSupported` | unchanged |
|
||||
| PUT/GET denied | `BadNotSupported` | `Faulted` (config alert) |
|
||||
| PUT/GET denied | `BadNotSupported` | `Faulted` (config alert) — also counts as *answered*, advances `LastSuccessfulRead` (no watchdog reconnect loop) |
|
||||
| CPU refusal of the item (object does not exist, address out of range, type inconsistent, hardware fault) | `BadDeviceFailure` | unchanged — counts as *answered*, advances `LastSuccessfulRead`; one Warning per tag |
|
||||
| Other device-layer fault (`PlcException` that is neither a refusal nor connection-fatal) | `BadDeviceFailure` | `Degraded` |
|
||||
| Socket / timeout | `BadCommunicationError` | `Degraded` |
|
||||
@@ -333,7 +333,11 @@ answers without a failure.
|
||||
**A refusal is an answer (R07:H4).** S7.Net's read paths raise the CPU's item error as a bare
|
||||
`Exception` ("Received error from PLC: Object does not exist." — live-probed on the snap7 fixture), its
|
||||
write paths as a `PlcException(WriteData)`; both are classified over the whole exception chain, and the
|
||||
access-denied text inside either is the `Faulted` alert. A refusal is deterministic, so it neither
|
||||
access-denied text inside either (S7.Net's exact "Accessing object not allowed", and never when the chain
|
||||
also carries a transport fault) is the `Faulted` alert — which is ALSO an answer: it advances
|
||||
`LastSuccessfulRead`, so a factory-default CPU with PUT/GET disabled stays `Faulted` instead of being
|
||||
reconnected by the comms-loss watchdog every 90 s. A multi-var chunk that falls back per tag memoises the
|
||||
members refused on their own, like a refused span. A refusal is deterministic, so it neither
|
||||
degrades health nor reopens the connection, and because it proves the CPU is reachable it advances
|
||||
`LastSuccessfulRead` and restores `Healthy` after an earlier comms fault. Before this, a driver whose
|
||||
every tag was refused (a wrong DB number) sat `Degraded` with a frozen timestamp and the comms-loss
|
||||
|
||||
Reference in New Issue
Block a user