fix(opcua): address code review on write-outcome surfacing
v2-ci / build (push) Failing after 35s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped

- A.1 (false-rejection safety): restrict the structural fail-fast's confident-mismatch check to
  the CLOSED set of built-in types ResolveBuiltInDataType emits (numeric families + Boolean/
  String/DateTime/ByteString). Any other expected type (Enumeration, Guid, …) now defers to the
  SDK, so a coercible write (Int32→Enumeration) is never false-rejected. + A7/A8 regression tests.
- C.1: guard BuildWriteFailureAuditEvent (under Lock) in try/catch like ReportAuditEvent, so a
  SetChildValue surprise is swallowed+logged, never thrown out of the fire-and-forget continuation.
This commit is contained in:
Joseph Doherty
2026-06-15 02:45:51 -04:00
parent bb59fd4e75
commit a5c0c82661
2 changed files with 47 additions and 1 deletions
@@ -169,6 +169,26 @@ public sealed class EquipmentWriteGateTests
OtOpcUaNodeManager.EvaluateEquipmentWriteStructure(value: 1, folder).ShouldBeNull();
}
/// <summary>(A7) Closed-set safety boundary: an Enumeration-typed node with an Int32 payload DEFERS
/// (returns null) rather than false-rejecting — an enum write normally carries an Int32 the SDK coerces.
/// Before the closed-set gate this returned <c>BadTypeMismatch</c> — a false rejection of a valid write.</summary>
[Fact]
public void Structure_enumeration_datatype_defers_to_sdk()
{
var node = ValueNode(DataTypeIds.Enumeration);
OtOpcUaNodeManager.EvaluateEquipmentWriteStructure(value: 2, node).ShouldBeNull();
}
/// <summary>(A8) Closed-set safety boundary: any expected built-in type outside the materialiser's emitted
/// set (here Guid) DEFERS — only the numeric families + Boolean/String/DateTime/ByteString are ever rejected,
/// so the fail-fast can never reject a write the SDK would coerce.</summary>
[Fact]
public void Structure_noncheckable_expected_type_defers()
{
var node = ValueNode(DataTypeIds.Guid);
OtOpcUaNodeManager.EvaluateEquipmentWriteStructure(value: "any", node).ShouldBeNull();
}
private static BaseDataVariableState ValueNode(NodeId dataType) => new(null)
{
NodeId = new NodeId("eq/x", 2),