Coordinator ruling: a delivered chunk proves the Agent is alive (the analogue of
OpcUaClient's good keep-alive), so TryMarkStreamDelivered - generation-checked
under _subscriptionLock - also clears _writeDegraded. The degradation no longer
sticks forever on a subscription-only driver. Test: unanswered write -> Degraded,
stays Degraded with nothing else happening (positive control), the next chunk ->
Healthy; red with the clear removed. Round-2 doc text corrected.
Refs #743
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
- N-4: the /sample Unsupported latch is set inside TryDegradeStream's
generation-checked critical section (latchUnsupported), so a superseded pump can
no longer latch it on the session that replaced it.
- N-5: a read served from the live pump index clears only the read degradation -
it is stream evidence, like a chunk, which by the I-3 ruling does not clear an
unanswered write. The 'cannot stick' wording in the XML doc and
docs/drivers/MTConnect.md now states the latch-until-next-answered-/current-read-
or-write behaviour, incl. the subscription-only consequence. Tested (chunk +
live-index read keep Degraded; an answered write clears it).
Refs #743
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
Coordinator ruling on F3: a write with no reply (transport failure / deadline)
sets Degraded with the stamp preserved and a type-only LastError, through a new
_writeDegraded flag folded into PublishHealthy. One success writer is shared:
RecordReadAnswered (a /current answer, an Agent refusal, a live-index read)
clears the read AND write degradations; RecordWriteAnswered clears the write
one; neither clears the other path's stream degradation. Tests both directions
(answered write clears it, good read clears it) plus the converse (an answered
write does not clear a failed read); red with each half removed.
m7: a good /current resets the refusal-dedup latch, so a refusal recurring after a
recovery is logged again (tested).
Refs #743
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
Writes were one POST per slot although the Agent's PUT surface accepts several
id=value pairs. The slots surviving the local refusals now go out as one
WriteObservationsAsync(device, pairs) per device (new IMTConnectAgentClient method;
the default loops the single-pair call). A batch closes at another device or a
repeated DataItem so same-item writes keep request order; a refused multi-slot
request is retried per slot so one refused DataItem costs only its own slot.
Health contract: any Agent answer (accepted or refused) advances
LastSuccessfulRead with the state following the read / stream flags.
Refs #743
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT