Files
lmxopcua/docs
Joseph Doherty f50b29abae
v2-ci / unit-tests (push) Failing after 1h12m10s
feat(scripting): PE cache, quarantine-race fix, recovery, readiness for the out-of-process runner (lmxopcua#780 P1)
Second P1 slice of docs/plans/2026-11-09-out-of-process-script-runner-design.md; still opt-in behind
Scripting:Runner=OutOfProcess (InProcess unchanged).

- Compiled-PE cache (design §7, §14 #8): Scripting:Worker:PeCacheDir holds <dir>/pe/<id>.pe entries authenticated
  with HMAC-SHA256 under a per-node random key (<dir>/pe-cache.key, created by the host, 0600/0700 on Unix), handed to
  the worker in Hello. Entry id = SHA-256(compile fingerprint + kind + source); the fingerprint covers the Roslyn
  version, the Core.Scripting build (analyzer, wrapper, stack guard), the sandbox reference set and the runtime. Edited,
  truncated, other-key or renamed entries are rejected, deleted and recompiled; the worker never loads unverified bytes.
  Pruned by recency at startup (30 d / 50 000). ScriptEvaluator splits into CompileToImage + FromImage. Measured on
  40 scripts, restart to every first result: macOS 820 ms no cache / 840 ms empty / 85 ms warm;
  windev (Windows) 9.5 s / 9.4 s / 1.0 s.
- Quarantine race: WorkerConnection runs exit attribution synchronously BEFORE completing the evaluations/loads it held
  (and before Exited), and the shard passes it straight to the runner — a kill strike is in place before the killed
  key's slot is released.
- Supervision: a dead worker restarts by itself after its backoff; Unavailable outcomes carry ScriptRunOutcome.Recovery
  (-> VirtualTagEvalResult.RetryWhen) and the VT actor / Calculation driver re-evaluate once against current inputs when
  it completes. Isolation worker stops after Scripting:Worker:IsolationIdleTimeout (10 min). Keys compiling when a
  worker dies are suspects too (isolation finds a poison compile and quarantines it). Startup orphan sweep by launch
  marker: kills only a process whose marker's host is gone and whose pid + start time + image match.
- Crash-loop circuit (Scripting:Worker:CircuitOpenAfter, 2 min): runner Unavailable, one Error, evaluations answered
  at once while shards keep retrying at the cap.
- Observability (§10): otopcua.script.runner.state, worker.up/inflight/queue_depth/heap_bytes (shard),
  ipc.roundtrip + eval.duration (kind), compile{cache}, isolation.suspects, exits reason=idle. `script-runner` health
  check on the ready tier, at worst Degraded (§14 #12: degraded but still ready).

Left on #780: self-contained worker publish, installer (account, pipe ACL, firewall), docker-dev live drill,
scripted-alarm seam, the §4.5 per-seam batch submit, the §8 perf harness.

Tests: SupervisionTests, PeCacheStoreTests/PeCacheWorkerTests (real workers), ScriptRunnerHealthCheckTests, VT + Calc
recovery seam tests; 14 mutations each turned their test red.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
2026-11-10 00:20:24 -05: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, 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 Historical v1 design: history as a per-driver capability (retired; history is now server-side, see Historian.md)
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.