Reader asymmetries: OpcUaClient legacy PascalCase NodeId is readable but unsaveable; Mqtt Sparkplug parse accepts a payloadFormat the Plain parse rejects #557
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Two runtime-reader asymmetries found during #554, both pre-existing and now explicitly exempted-by-name in the parity tests:
OpcUaClient:
TryReadNodeIdstill honours a legacy PascalCaseNodeIdkey when camelCasenodeIdis wholly absent, butOpcUaClientTagConfigModelreads onlynodeId— so a legacy blob is served by the driver yet cannot be saved untouched through the editor (authoring stricter than reader). Options: model reads both spellings at the reader's precedence and normalises on save (the #550 pattern), or the legacy spelling is formally retired from the reader with a migration note.Mqtt: the Sparkplug-mode parse accepts an invalid
payloadFormatthat the Plain-mode parse rejects, and the union verdict (used by bothInspectand the #554 authoring gate — both gates agree) therefore admits it.payloadFormatis meaningless in Sparkplug mode, so accepting it is arguably correct — but then the PLAIN parse rejecting it while the union admits it means the served behavior depends on the driver's mode config, which no tag-level gate can see. Decide the intended contract and pin it.Also worth folding in if touched:
OpcUaClientDriverBrowser.DriverTypeandGalaxyDriverBrowser.DriverTypereturn string literals rather thanDriverTypeNamesconstants (the drift class the constants exist to prevent; harmless today, unguarded) — see #558 for the guard side.Closed by merges
93a05828(branchfix/557-opcuaclient-legacy-nodeid— OpcUaClient half) andbe15ed16(branchfix/556-inspect-polish— Mqtt half), verified by the 14-suite consolidated run (4,583 tests, 0 failures).OpcUaClient legacy
NodeIdreadable-but-unsaveable — FIXED.OpcUaClientTagConfigModelgained aNodeIdKeyslist +ReadNodeIdimplementing the runtime's presence-shadows semantics —TryGetProperty("nodeId", out n) || TryGetProperty("NodeId", out n)then one ValueKind==String + non-blank check. This is deliberately NOT MTConnect'sFirstNonBlank: a present-but-blank or non-stringnodeIdshort-circuits the||and blocks fallback to the legacy spelling, so blank-nodeId-beside-valid-NodeIdis a REJECTED blob at runtime and the model now agrees.ToJsondrops non-canonical aliases keyed off the SAMENodeIdKeysarray (the #550 pattern — read and drop cannot drift). The OpcUaClient exemption inTagConfigNonEnumParityTestsis removed (the anti-vacuity controlEvery_deliberately_stricter_entry_is_still_exactly_thatwould have failed had it gone stale), and a precedence theory pins the presence-shadows cases including the blank-shadows-valid one.Mqtt union-parse asymmetry — contract DECIDED, not changed. The union verdict stands: a blob accepted by either runtime mapper is accepted, because
FromSparkplugTagConfighardcodesPayloadFormat.Jsonand never reads thepayloadFormatkey — so a typo'dpayloadFormaton a sparkplug-shaped blob is genuinely servable, and refusing it at authoring/Inspect would be stricter than runtime (the exact drift class this arc exists to kill). Mitigation: a mode-dependency advisory rides the accepted blob, stating the key only takes effect in plain-MQTT mode. Advisories ride accepted blobs by design (#554), so this never trips the rejection backstop.