Task 14 — the P1 milestone gate. Ran the live /run verification on a docker-dev
rig against the real Mosquitto TLS+auth fixture, and recorded the result.
The gate did its job: it found MQTT was unauthorable in production.
RawDriverTypeDialog's driver-type list is a hand-maintained array that nobody
added MQTT to, so the /raw "New driver" picker never offered it — with every
other layer (contracts, driver, browser, factory, host registration, typed tag
editor) complete and 266 green unit tests. Nothing tied that array to
DriverTypeNames and AdminUI has no bUnit, so no offline test could see it.
Fixed, plus RawDriverTypeDialogParityTests: a reflection parity guard that
fails for ANY DriverTypeNames entry missing from the picker. Verified
falsifiable — RED with "…cannot be authored from the /raw New-driver picker,
so they are unreachable in production: Mqtt" before the one-line fix.
A second gap is left OPEN and documented, not fixed (no task ever covered it):
MqttDriverForm / MqttDeviceForm do not exist, and DriverConfigModal/DeviceModal
have no raw-JSON fallback — so an operator can create an MQTT driver but cannot
author its broker endpoint or credentials. P1 is not operator-complete until
those two razor forms land. The gate authored config via SQL to get past it.
Live gate result (isolated 2-node MAIN stack, image built from this branch,
fixture at 10.100.0.35:8883, AllowUntrustedServerCertificate rather than
pinning the CA into the rig):
- typed MQTT tag editor renders and round-trips; Json shows the JSON-path
field, Raw/Scalar hide it; wildcard topic a/+/c blocked at save; data-type
list offers Float64 and no Double; qos/retainSeed absent on "(driver
default)" and qos:2 written as a number; isHistorized/historianTagname
survive a topic-only edit; SparkplugB renders the P2 placeholder and
switching back keeps Plain values; jsonPath is guidance not a gate in all
three shapes ($ seeded only for a brand-new blank tag, blank stays absent,
clearing saves fine)
- deployed, and both editor-written blobs resolved and served changing live
values through OPC UA (22.8 / 1629, StatusCode Good) — no BadNodeIdUnknown
- the bespoke #-observation browser drove a real broker session and rendered
the fixture's actual topic tree (line1/{counter,state,temperature},
noise/chatter, retained/seed); a Modbus device's picker still correctly
reports "Browsing unavailable"
Also found live and documented (not a code change): both nodes of a redundant
pair run the driver, so a FIXED clientId makes them evict each other forever —
the broker logs "already connected, closing old connection" and both nodes
reconnect every ~2s while still reporting Healthy. Unset (the default) is
correct; confirmed 0 reconnects/60s on both nodes after removing it.
Suites: offline 1134 passed / 0 failed (266 Driver.Mqtt + 730 AdminUI incl. the
3 new guards + 138 Core.Abstractions); live 7/7 against the fixture.
Docs: new docs/drivers/Mqtt.md + Mqtt-Test-Fixture.md (the per-driver + fixture
convention every other driver follows), MQTT rows in docs/drivers/README.md,
TestConnectProbes.md, root README.md, and infra/README.md §3.
CLAUDE.md drift corrected while adding the MQTT endpoints:
- the "every fixture carries project: lmxopcua" claim was false — only the
MQTT fixture does; the filter returns one stack, not the fleet
- lmxopcua-fix.ps1 is Windows-VM-only; on macOS drive the host over ssh/rsync
(and the docker-dev rig itself runs locally under OrbStack)
Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
OtOpcUa documentation
Two tiers of documentation live here:
- Current reference at the top level (
docs/*.md) — describes what's shipped today. Start here for operator + integrator reference. - Implementation history + design notes at
docs/v2/*.md— the authoritative plan + decision log the current reference is built from. Start here when you need the why behind an architectural choice, or when a top-level doc says "see plan.md § X".
The project was originally called LmxOpcUa (a single-driver Galaxy/MXAccess OPC UA server) and has since become OtOpcUa, a multi-driver OPC UA server platform. Any lingering LmxOpcUa-string in a path you see in docs is a deliberate residual (executable name lmxopcua-cli, client PKI folder {LocalAppData}/LmxOpcUaClient/) — fixing those requires migration shims + is tracked as follow-ups.
Platform overview
v2 (2026-05-26): the separate
OtOpcUa.Server+OtOpcUa.Adminservices fused into a single role-gatedOtOpcUa.Hostbinary, joined by an Akka.NET cluster. See v2 design for the architectural decision.
- Core owns shared abstractions (driver capability contracts, scripting, virtual tags, alarm historian).
- Drivers plug in via capability interfaces in
ZB.MOM.WW.OtOpcUa.Core.Abstractions:IDriver,IReadable,IWritable,ITagDiscovery,ISubscribable,IHostConnectivityProbe,IAlarmSource,IHistoryProvider,IPerCallHostResolver. Each driver opts into whichever it supports. - Host (
src/Server/ZB.MOM.WW.OtOpcUa.Host) is the single fused binary (.NET 10, AnyCPU).OTOPCUA_ROLESenv decides what to mount:admin(Blazor + control-plane singletons),driver(OPC UA endpoint + per-node actors), or both. See ServiceHosting.md. - Cluster + ControlPlane + Runtime + AdminUI + Security sit between Core and Host. The cluster glues per-node actors into one logical fleet; the control-plane singletons (deploy coordinator, audit writer, redundancy state) live on the admin role-leader. See Redundancy.md.
- The Galaxy driver still reaches MXAccess via gRPC to a separately-installed mxaccessgw sidecar (sibling repo).
Where to find what
Architecture + data-path reference
| Doc | Covers |
|---|---|
| OpcUaServer.md | Top-level server architecture — Core, driver dispatch, Config DB, generations |
| AddressSpace.md | GenericDriverNodeManager + ITagDiscovery + IAddressSpaceBuilder |
| ReadWriteOperations.md | OPC UA Read/Write → CapabilityInvoker → IReadable/IWritable |
| DriverLifecycle.md | Server-side driver lifecycle + infrastructure contracts (IDriverFactory, IDriverProbe, IDriverSupervisor, IDriverHealthPublisher, IDriverConfigEditor, IHistorianDataSource) + the Commons library |
| Subscriptions.md | Monitored items → ISubscribable + per-driver subscription refcount (v1 archive) |
| AlarmTracking.md | IAlarmSource + AlarmSurfaceInvoker + OPC UA alarm conditions — native Galaxy alarms end-to-end (live) |
| AlarmTracking.md | Original alarm-tracking write-up (v1 archive) |
| AlarmHistorian.md | Core.AlarmHistorian store-and-forward sink — LocalDbStoreAndForwardSink, the replicated alarm_sf_events buffer + Primary-only drain, IAlarmHistorianWriter, dead-letter/retry/eviction |
| DataTypeMapping.md | Per-driver DriverAttributeInfo → OPC UA variable types (v1 archive — live mapping is in src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy/Browse/DataTypeMap.cs) |
| IncrementalSync.md | Address-space rebuild on redeploy + sp_ComputeGenerationDiff |
| HistoricalDataAccess.md | IHistoryProvider as a per-driver optional capability (v1 archive) |
| VirtualTags.md | Core.Scripting + Core.VirtualTags — Roslyn script sandbox, engine, dispatch alongside driver tags |
| ScriptEditor.md | Monaco script editor — Roslyn-backed IntelliSense (completions, diagnostics, hover, tag-path) for C# virtual-tag scripts; AdminUI/ScriptAnalysis/ backend |
| ScriptedAlarms.md | Core.ScriptedAlarms — script-predicate IAlarmSource + Part 9 state machine |
One Core subsystem is shipped without a dedicated top-level doc; see the section in the linked doc:
| Project | See |
|---|---|
Analyzers (Roslyn OTOPCUA0001) |
security.md § OTOPCUA0001 Analyzer |
Drivers
| Doc | Covers |
|---|---|
| drivers/README.md | Index of the eight shipped drivers + capability matrix |
| drivers/Galaxy.md | Galaxy driver — in-process gRPC client to the mxaccessgw sidecar |
| v1/drivers/Galaxy-Repository.md | Galaxy-specific discovery via the ZB SQL database (v1 archive — the gateway owns this path now) |
For Modbus / S7 / AB CIP / AB Legacy / TwinCAT / FOCAS / OPC UA Client specifics, see v2/driver-specs.md.
Operational
| Doc | Covers |
|---|---|
| Configuration.md | Live appsettings + environment-variable reference (current state) |
| Configuration.md | appsettings bootstrap + Config DB + Admin UI draft/publish (v1 archive — OTOPCUA_GALAXY_* env vars now live in mxaccessgw config) |
| Uns.md | The global /uns master tree — manage Area/Line/Equipment/Tag/VirtualTag fleet-wide; replaces the per-cluster UNS/Equipment/Tags tabs |
| security.md | Transport security profiles, LDAP auth, ACL trie, role grants, OTOPCUA0001 analyzer |
| Redundancy.md | RedundancyCoordinator, ServiceLevelCalculator, apply-lease, Prometheus metrics |
| Reservations.md | Fleet-wide ZTag / SAPID external-ID reservations — publish-time claim, release flow |
| ServiceHosting.md | Single fused OtOpcUa.Host binary install/uninstall with OTOPCUA_ROLES gating; the historian backend is the external HistorianGateway |
| StatusDashboard.md | Pointer — superseded by v2/admin-ui.md |
Client tooling
| Doc | Covers |
|---|---|
| Client.CLI.md | otopcua-cli — OPC UA command-line client |
| Client.UI.md | Avalonia desktop client |
| DriverClis.md | Driver test-client CLIs — index + shared commands |
| Driver.Modbus.Cli.md | otopcua-modbus-cli — Modbus-TCP |
| Driver.AbCip.Cli.md | otopcua-abcip-cli — ControlLogix / CompactLogix / Micro800 / GuardLogix |
| Driver.AbLegacy.Cli.md | otopcua-ablegacy-cli — SLC / MicroLogix / PLC-5 (PCCC) |
| Driver.S7.Cli.md | otopcua-s7-cli — Siemens S7-300 / S7-400 / S7-1200 / S7-1500 |
| Driver.TwinCAT.Cli.md | otopcua-twincat-cli — Beckhoff TwinCAT 2/3 ADS |
| Driver.FOCAS.Cli.md | otopcua-focas-cli — Fanuc FOCAS/2 CNC |
Requirements
| Doc | Covers |
|---|---|
| reqs/HighLevelReqs.md | HLRs — numbered system-level requirements |
| reqs/OpcUaServerReqs.md | OPC UA server-layer reqs |
| v1/reqs/ServiceHostReqs.md | Per-process hosting reqs (v1 archive — only OtOpcUa server hosting remains in scope post-PR-7.2) |
| reqs/ClientRequirements.md | Client CLI + UI reqs |
| v1/reqs/GalaxyRepositoryReqs.md | Galaxy-scoped repository reqs (v1 archive — owned by mxaccessgw today) |
| v1/reqs/MxAccessClientReqs.md | Galaxy-scoped MXAccess reqs (v1 archive — owned by mxaccessgw today) |
| reqs/StatusDashboardReqs.md | Pointer — superseded by Admin UI |
Implementation history (docs/v2/)
Design decisions + phase plans + execution notes. Load-bearing cross-references from the top-level docs:
- v2/plan.md — authoritative v2 vision doc + numbered decision log (referenced as "decision #N" elsewhere)
- v2/admin-ui.md — Admin UI spec
- v2/acl-design.md — data-plane ACL + permission-trie design (Phase 6.2)
- v2/config-db-schema.md — Config DB schema reference
- v2/driver-specs.md — per-driver addressing + quirks for every shipped protocol
- v2/dev-environment.md — dev-box bootstrap
- v2/test-data-sources.md — integration-test simulator matrix (includes the pinned libplctag
ab_serverversion for AB CIP tests) - v2/multi-host-dispatch.md — per-PLC circuit breakers (Phase 6.1 decision #144)
- v2/v2-release-readiness.md — release-readiness tracker
- v2/phase-7-status.md — Phase 7 reconciliation: what shipped vs. the plan, and the five remaining gaps
- v2/implementation/phase--.md — per-phase execution plans with exit-gate evidence
v1 archive
The v1 in-process MXAccess architecture (Galaxy.Host + Galaxy.Proxy + Galaxy.Shared, .NET 4.8 x86 COM, the OtOpcUaGalaxyHost Windows service) was retired in PR 7.2 (2026-04-30, commit ae7106d). Docs that described that shape are kept under v1/ as historical record — see v1/README.md for the index.