fix(ablegacy): deterministic NotSupported refusals are BadNotSupported with no health write

Review 07 H16: an array of String/Timer/Counter/Control passed the mapper, so
every poll created a handle (one init read), the decode threw
NotSupportedException, the generic catch evicted the handle and latched
Degraded. FromTagConfig now rejects that combination (the skip carries the
reason, Inspect/deploy gate and the AdminUI model state it), and a runtime
NotSupportedException on read or write is BadNotSupported: handle kept, no
health write, one Warning per tag and operation. Write refusals (native PLC-5
and status-bit writes, libplctag translated addresses) now warn once per tag too.

Refs #738
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
Joseph Doherty
2026-10-22 14:20:17 -04:00
parent 7377daa9b0
commit 8b50b43048
5 changed files with 215 additions and 5 deletions
@@ -122,6 +122,20 @@ public sealed class AbLegacyTagConfigModelTests
m.Validate().ShouldBeNull();
}
/// <summary>Review 07 H16: an array of a structured element type is refused with the reader's own reason.</summary>
[Theory]
[InlineData("String", "ST9:0")]
[InlineData("TimerElement", "T4:0")]
public void Validate_refuses_an_array_of_a_structured_element_type(string dataType, string address)
{
var m = AbLegacyTagConfigModel.FromJson(
$$"""{"address":"{{address}}","dataType":"{{dataType}}","isArray":true,"arrayLength":4}""");
m.Validate().ShouldNotBeNull().ShouldContain("not supported as an array");
// Positive control: the same type as a scalar validates.
AbLegacyTagConfigModel.FromJson($$"""{"address":"{{address}}","dataType":"{{dataType}}"}""").Validate().ShouldBeNull();
}
[Fact]
public void Validate_returns_null_for_scalar_tag_regardless_of_no_array_length()
{