feat(address-space): server-side array writes gated per driver capability (#610)
Array nodes were forced read-only for every driver. A new single authority, Core.Abstractions DriverArrayWriteSupport, says which (driver, element type) pairs encode a whole-array write: Modbus (Coils / numeric HoldingRegisters), S7 (Byte/Int16/UInt16/Int32/UInt32/Float32), OpcUaClient (pass-through) and Galaxy (Boolean/Int32/Int64/Float/Double/String/DateTime). Everything else stays read-only, so the server never advertises a write the driver refuses. - Composer derives RawTagPlan.ArrayWritable + UnsReferenceVariable .BackingArrayWritable at compose time (both seams, byte-parity); the applier's one writability rule is Writable && (!IsArray || ArrayWritable) for materialise and surgical paths, raw and UNS alike. - ArrayWritable is on the classifier's surgical raw-tag whitelist and mirrored on the UNS record (UnsReferenceBackingParityTests updated). - Node manager refuses a wrong-length / non-array value on an array node (BadTypeMismatch) and any IndexRange write (BadWriteNotSupported) inline, before dispatch; WriteOperate/ACL gates and the revert are unchanged. - Per-driver parity tests pin the table against the real Modbus write path, S7 EncodeArrayBlock and Galaxy MxValueEncoder (UInt32/UInt64 excluded: array covariance reinterprets their bits). - Over-the-wire proof in OpcUaServer.IntegrationTests (ArrayWriteWireTests). - ValueRank > 1 documented as out of scope by decision (docs/Uns.md). Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
+5
-2
@@ -97,7 +97,7 @@ public sealed class AddressSpaceChangeClassifierDualNamespaceTests
|
||||
}
|
||||
|
||||
/// <summary>The in-place-updatable attribute whitelist (Writable / IsHistorized / HistorianTagname /
|
||||
/// DataType / IsArray / ArrayLength) on a non-alarm raw tag is surgical, not a rebuild.</summary>
|
||||
/// DataType / IsArray / ArrayLength / ArrayWritable) on a non-alarm raw tag is surgical, not a rebuild.</summary>
|
||||
[Theory]
|
||||
[InlineData("writable")]
|
||||
[InlineData("historized")]
|
||||
@@ -105,12 +105,14 @@ public sealed class AddressSpaceChangeClassifierDualNamespaceTests
|
||||
[InlineData("dataType")]
|
||||
[InlineData("array")]
|
||||
[InlineData("arrayLength")]
|
||||
[InlineData("arrayWritable")]
|
||||
public void Attribute_raw_tag_delta_is_not_a_rebuild(string field)
|
||||
{
|
||||
var baseline = RawTag("Plant/Modbus1/PLC-A/Speed");
|
||||
// ArrayLength moving ALONE (a resize of an already-array tag) — the whitelist must carry ArrayLength
|
||||
// in its own right, not only as a passenger of an IsArray flip. Its "previous" is already an array.
|
||||
var prev = field == "arrayLength" ? baseline with { IsArray = true, ArrayLength = 8 } : baseline;
|
||||
// ArrayWritable (#610) likewise moves on an already-array tag (a driver-type or element-type edit).
|
||||
var prev = field is "arrayLength" or "arrayWritable" ? baseline with { IsArray = true, ArrayLength = 8 } : baseline;
|
||||
var cur = field switch
|
||||
{
|
||||
"writable" => prev with { Writable = true },
|
||||
@@ -119,6 +121,7 @@ public sealed class AddressSpaceChangeClassifierDualNamespaceTests
|
||||
"dataType" => prev with { DataType = "Double" },
|
||||
"array" => prev with { IsArray = true, ArrayLength = 8 },
|
||||
"arrayLength" => prev with { ArrayLength = 16 },
|
||||
"arrayWritable" => prev with { ArrayWritable = true },
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(field)),
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user