Files
lmxopcua/docs
Joseph Doherty f88eb49bd1 feat(acl): ApiKey entity + key format, and put Core on the server's graph (#520)
Stage 1b. Still no enforcement wired — this adds the credential model the
identity cutover (stage 2) will read.

ApiKey holds one principal per key, deliberately. The reason to separate keys
from principals is so many keys share a grant set and rotating a key never
touches an ACL row, and that holds with one principal per key since nothing stops
two keys naming the same principal. A key belonging to several principals at once
buys little and costs a join table. UserAuthorizationState.Principals stays a
list, so adding it later needs no ACL-side change.

ApiKeySecret is the security-critical half and is pure, so it is tested on its
own: otua_<keyId>_<secret>, peppered HMAC-SHA256, FixedTimeEquals. Notes why a
plain HMAC rather than an iterated KDF (the secret is 32 bytes of CSPRNG, and the
call runs inline on session activation, which the SDK serializes server-wide
behind its SessionManager event lock), and fails closed on an unset pepper rather
than hashing under an empty key.

Found and fixed a bug in my own first draft: the key id was url-safe base64,
whose alphabet maps '/' to '_' — the very character TryParse splits on. Roughly
one key in five would have had its id truncated at a random position, an
intermittent auth failure reproducing only for SOME keys. The id is hex now, and
the guard is falsified: reverting the alphabet reds both
The_generated_key_id_never_contains_the_separator and, as a bonus signal,
Two_generated_keys_never_collide, because truncated ids collide.

ApiKeyId is NOT NULL, unlike the nullable NodeAclId precedent whose filtered
unique index would admit two NULL ids. Fine for a config row, not for a
credentials lookup key.

OpcUaServer.csproj now references Core. Until now it did not, so
IPermissionEvaluator was literally uncallable from the node manager — the place
enforcement has to happen. No new transitive package surface.

Schema doc updated for both, and its NodeAcl block flagged for pre-existing drift
(it still documents the GenerationId column v3 dropped) without fixing it here.

Core 247, OpcUaServer 364, Configuration 128 — all green.

Claude-Session: https://claude.ai/code/session_015p7wGqy3YpZNCpDzTpGMKo
2026-07-31 12:27:28 -04:00
..

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.Admin services fused into a single role-gated OtOpcUa.Host binary, 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_ROLES env 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, 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 RedundancyStateActor (cluster-scoped singleton), ServiceLevelCalculator, IRedundancyRoleView, per-cluster meshes, 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:

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.