refactor(opcuaclient): move NamespaceMap to a .Session leaf so Contracts is dependency-light

G-7 wired OpcUaClientTagDefinitionFactory.Inspect into the deploy-time TagConfig
gate, which gave ControlPlane a ProjectReference to
Driver.OpcUaClient.Contracts — and with it, transitively, the whole OPC UA client
stack (5 OPCFoundation packages) into every host that loads the control plane.

NamespaceMap was the only reason Contracts carried
OPCFoundation.NetStandard.Opc.Ua.Client: its entire public surface is
ISession/NamespaceTable/NodeId-typed. It moves to a new leaf project,
ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Session, referenced by the runtime .Driver
and by .Browser. Contracts is left with Core.Abstractions and NOTHING else —
the same shape as Calculation.Contracts.

The namespace stays ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient, so no consumer's
`using` changed. The .Session -> .Contracts direction is deliberate: the blob
read NamespaceMap needs is OpcUaClientTagDefinitionFactory.TryReadNodeId, the
one authority it shares with the deploy gate, and that must stay in the
dependency-light half.

Why a new project rather than folding NamespaceMap into the runtime .Driver and
having .Browser reference it (the Driver.Mqtt.Browser #512-style deliberate
exception): the AdminUI project-references .Browser and NOT .Driver, so that
shape would pull the OpcUaClient driver runtime — plus Core,
Opc.Ua.Configuration and Secrets.Abstractions — into the AdminUI's build/deploy
graph. That is exactly the violation #512 spent two extractions removing from
Mqtt.Browser; re-introducing it to save one csproj is a bad trade.

Guard: ContractsIsDependencyLightTests (Driver.OpcUaClient.Tests) asserts the
Contracts manifest names no Opc.Ua* assembly, plus a counterpart asserting
.Session does. An ABSENCE assertion is sound on the manifest where a presence
one is not — the compiler elides an unused reference, but it MUST emit one the
moment Opc.Ua-typed code is added. Falsified by temporarily re-adding the
package plus an Opc.Ua.NodeId-typed member: red on Opc.Ua.Types, green again on
removal.

Evidence — `dotnet list ControlPlane package --include-transitive | grep -i opc`:
  before: OPCFoundation.NetStandard.Opc.Ua.{Client,Configuration,Core,
          Security.Certificates,Types} 1.5.378.106
  after:  none (and 0 Opc.Ua*.dll in ControlPlane's output directory)
This commit is contained in:
Joseph Doherty
2026-08-09 21:56:29 -04:00
parent fefcd2c872
commit b4f8cb8de7
11 changed files with 161 additions and 14 deletions
@@ -31,7 +31,8 @@ public sealed record OpcUaClientDriverOptions
/// with the cluster's raw OPC UA Client tags. Each <see cref="RawTagEntry"/> carries the tag's
/// <b>RawPath</b> identity plus a driver <c>TagConfig</c> blob whose <c>nodeId</c> key holds the
/// upstream OPC UA node id string the driver dials. The driver builds a <c>RawPath → nodeId</c>
/// resolution table (via <see cref="NamespaceMap"/>) from this list at connect time; a
/// resolution table (via <c>NamespaceMap</c>, in the sibling <c>.Session</c> project) from this
/// list at connect time; a
/// read/write/subscribe/history reference is always a RawPath resolved through that table.
/// </summary>
public IReadOnlyList<RawTagEntry> RawTags { get; init; } = [];