merge: #557 (OpcUaClient half) — editor reads the legacy PascalCase NodeId at the reader's presence-shadows precedence and drops it on save; #554 stale exemption removed with the anti-vacuity guard as control

This commit is contained in:
Joseph Doherty
2026-08-10 03:02:12 -04:00
6 changed files with 255 additions and 17 deletions
@@ -32,6 +32,15 @@ public static class OpcUaClientTagDefinitionFactory
/// Read the upstream node id string from an authored <c>TagConfig</c> blob's <c>nodeId</c> key
/// (camelCase, per the AdminUI <c>OpcUaClientTagConfigModel</c>; a legacy PascalCase <c>NodeId</c>
/// is also accepted). Never throws — a malformed blob yields <see langword="false"/>.
/// <para>
/// ⚠️ <b>The two spellings are PRESENCE-shadows, not first-non-blank.</b> The <c>||</c> below
/// short-circuits on the mere presence of <c>nodeId</c> — whatever it holds — and the string /
/// non-blank check then runs ONCE, on that one element. So the legacy <c>NodeId</c> is consulted
/// only when <c>nodeId</c> is <i>wholly absent</i>: a blank or non-string <c>nodeId</c> beside a
/// valid legacy <c>NodeId</c> is a rejected blob, not a fallback. The AdminUI model mirrors this
/// walk exactly (Gitea #557 item 1) rather than the looser first-non-blank shape MTConnect's
/// reader uses, which would let authoring accept a blob this reader refuses.
/// </para>
/// </summary>
/// <param name="tagConfig">The authored equipment-tag TagConfig JSON.</param>
/// <param name="nodeId">The upstream node id string when this returns <see langword="true"/>.</param>