fix(mtconnect): bind RawTags and key the data plane by RawPath

The driver could not receive data after a real deploy. The runtime hands every
driver its authored tags as a RawTags array (RawPath identity + TagConfig blob,
injected by DriverDeviceConfigMerger) and then subscribes, reads, and routes
published values by RawPath. MTConnectDriverConfigDto declared no RawTags
property, so System.Text.Json silently dropped the array, and every reference
the server passed missed an index keyed by DataItem id: every value came back
BadNodeIdUnknown, with the whole suite green because every test handed the
driver its own Tags array directly.

- MTConnectDriverOptions gains RawTags; the config DTO binds it (verbatim — a
  bad blob is skipped per-tag at binding time, never fails the deployment).
- A new TagBinding, derived from the options and swapped with them in one write,
  carries RawPath -> dataItemId, its one-to-many reverse, and the definition set
  the observation index coerces against. Subscribe/Read resolve through it; the
  fan-out expands each touched dataItemId into every RawPath bound to it and
  publishes under THAT reference — the line the blocker turned on.
- Coercion type precedence: the blob's driverDataType/dataType (both spellings —
  the AdminUI's MTConnectTagConfigModel writes the latter), else a Tags entry for
  the same DataItem, else the Agent's own /probe declaration via
  MTConnectDataTypeInference (what makes a browse-committed {"address": id} blob
  publish as a number), else String. A present-but-invalid type rejects that tag.
- An unmapped reference falls through unchanged, so the dataItemId-keyed CLI
  surface and every existing test keep working, and an unclaimed RawPath reports
  Bad rather than throwing.

15 new tests build their config through the real DriverDeviceConfigMerger; all
four mutations (publish the dataItemId, drop the RawTags binding, drop the
derived coercion type, drop the reverse-map expansion) are caught.
This commit is contained in:
Joseph Doherty
2026-07-24 18:01:18 -04:00
parent b0046d6959
commit 51c8c7f30b
3 changed files with 893 additions and 11 deletions
@@ -1,3 +1,5 @@
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Driver.MTConnect;
/// <summary>
@@ -69,6 +71,32 @@ public sealed class MTConnectDriverOptions
/// </summary>
public IReadOnlyList<MTConnectTagDefinition> Tags { get; init; } = [];
/// <summary>
/// <b>The v3 data-plane binding: the authored raw tags the deploy artifact delivers.</b> Each
/// <see cref="RawTagEntry"/> pairs the tag's <b>RawPath</b> — the driver's wire reference for
/// read / subscribe / publish, and the key <c>DriverHostActor</c> routes published values by —
/// with the driver-specific <c>TagConfig</c> blob naming the Agent DataItem <c>id</c> it binds.
/// Injected into every driver's merged config by <c>DriverDeviceConfigMerger</c>, so this is the
/// collection a deployed instance actually serves from.
/// <para>
/// <b>Relationship to <see cref="Tags"/>.</b> The driver builds ONE observation index and
/// ONE <c>RawPath → dataItemId</c> table from both collections:
/// <list type="bullet">
/// <item>A tag in <c>RawTags</c> is reachable by its RawPath (production). Its coercion
/// type comes from the blob's <c>driverDataType</c>; failing that, from a
/// <see cref="Tags"/> entry naming the same DataItem id; failing that, from the Agent's
/// own <c>/probe</c> declaration (<see cref="MTConnectDataTypeInference"/>); failing
/// that, <see cref="DriverDataType.String"/> — the coercion that cannot fail.</item>
/// <item>A tag in <see cref="Tags"/> only is reachable by its DataItem <c>id</c>
/// (the driver CLI's authoring surface, and every pre-v3 test) and still contributes
/// its coercion type to the index.</item>
/// </list>
/// Both default to empty, so neither collection is required and a driver instance
/// authored with no tags yet starts cleanly.
/// </para>
/// </summary>
public IReadOnlyList<RawTagEntry> RawTags { get; init; } = [];
/// <summary>
/// Background connectivity-probe settings. When <see cref="MTConnectProbeOptions.Enabled"/>
/// is true the driver periodically issues a cheap <c>/probe</c> request and raises