# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## v2 Galaxy backend (default since PR 7.1) The OPC UA server is now a .NET 10 host. Galaxy access flows through the in-process `GalaxyDriver` (`src/ZB.MOM.WW.OtOpcUa.Driver.Galaxy/`) talking gRPC to a separate **mxaccessgw** gateway process — see `lmx_mxgw.md` for the design and `docs/v2/Galaxy.Performance.md` for the perf surface (tracing, metrics, soak). The legacy out-of-process `Galaxy.Host` (.NET 4.8 x86 + MXAccess COM, fronted by `GalaxyProxyDriver`) still ships and registers under DriverType `"Galaxy"` for parity testing — it retires in PR 7.2 once the parity matrix in `docs/v2/Galaxy.ParityMatrix.md` is fully green on the parity rig. The v1 description below covers the Galaxy.Host backend that is now one of two backends; everything from "Project Goal" through the "Library Preferences" section is still accurate for that path. ## Project Goal Build an OPC UA server on .NET Framework 4.8 (32-bit) that exposes AVEVA System Platform (Wonderware) Galaxy tags via the MXAccess toolkit. The server mirrors the Galaxy object hierarchy as an OPC UA address space, translating between contained-name browse paths and tag-name runtime references. ## Architecture Overview ### Data Flow 1. **Galaxy Repository DB (ZB)** — SQL Server database holding the deployed object hierarchy and attribute definitions. Queried at startup and on change detection to build/rebuild the OPC UA address space. 2. **MXAccess COM API** — Runtime data access layer. Subscribes to Galaxy tag attributes for live read/write. Requires a dedicated STA thread with a Win32 message pump for COM callbacks. 3. **OPC UA Server** — Exposes the hierarchy as browse nodes and attributes as variable nodes. Clients browse via contained names but reads/writes are translated to `tag_name.AttributeName` format for MXAccess. ### Key Concept: Contained Name vs Tag Name Galaxy objects have two names: - **contained_name** — human-readable name scoped to parent (used for OPC UA browse tree) - **tag_name** — globally unique system name (used for MXAccess read/write) Example: browsing `TestMachine_001/DelmiaReceiver/DownloadPath` translates to MXAccess reference `DelmiaReceiver_001.DownloadPath`. See `gr/layout.md` for the full mapping and target OPC UA structure. ### Data Type Mapping Galaxy `mx_data_type` values map to OPC UA types (Boolean, Int32, Float, Double, String, DateTime, etc.). Array attributes use ValueRank=1 with ArrayDimensions from the Galaxy attribute definition. Full mapping in `gr/data_type_mapping.md`. ### Change Detection Poll `galaxy.time_of_last_deploy` in the ZB database to detect redeployments, then rebuild the address space. See `gr/build_layout_plan.md` for the step-by-step plan. ## Reference Implementation An existing MXAccess client implementation is at: `C:\Users\dohertj2\Desktop\scadalink-design\lmxproxy\src\ZB.MOM.WW.LmxProxy.Host` Key patterns from that codebase: - **StaComThread** — Dedicated STA thread with Win32 message pump (`GetMessage`/`DispatchMessage` loop). All MXAccess COM objects must be created and called on this thread. Uses `PostThreadMessage(WM_APP)` to marshal work items. - **LMXProxyServer COM object** — `Register(clientName)` returns a connection handle. `AddItem(handle, address)` + `AdviseSupervisory(handle, itemHandle)` for subscriptions. `OnDataChange`/`OnWriteComplete` events for callbacks. - **Reconnect** — Stored subscriptions are replayed after reconnect. A probe tag subscription monitors connection health. - **COM cleanup** — `Marshal.ReleaseComObject()` on disconnect. Event handlers must be unwired before unregister. ## MXAccess Documentation `mxaccess_documentation.md` in the project root contains the full ArchestrA MXAccess Toolkit User's Guide. Key API: `ArchestrA.MxAccess` namespace, `LMXProxyServer` class. The toolkit DLLs are in `Program Files (x86)\ArchestrA\Framework\bin`. ## Galaxy Repository Database Connection: `sqlcmd -S localhost -d ZB -E` (Windows Auth). See `gr/connectioninfo.md`. The `gr/` folder contains: - `queries/` — SQL for hierarchy extraction, attribute lookup, and change detection - `ddl/tables/` and `ddl/views/` — Schema definitions - `schema.md` — Full table/view reference - `build_layout_plan.md` — Step-by-step plan for building the OPC UA address space from DB queries - `gr/CLAUDE.md` — Detailed guidance for working within the `gr/` subfolder Key tables: `gobject` (hierarchy/deployment), `template_definition` (object categories), `dynamic_attribute` (user-defined attributes), `primitive_instance` (primitive-to-attribute links), `galaxy` (change detection). ## Build Commands ```bash dotnet restore ZB.MOM.WW.OtOpcUa.slnx dotnet build ZB.MOM.WW.OtOpcUa.slnx dotnet test ZB.MOM.WW.OtOpcUa.slnx # all tests dotnet test tests/ZB.MOM.WW.OtOpcUa.Tests # unit tests only dotnet test tests/ZB.MOM.WW.OtOpcUa.IntegrationTests # integration tests only dotnet test --filter "FullyQualifiedName~MyTestClass.MyMethod" # single test ``` ## Docker Workflow (driver fixtures + central SQL Server) > **Migrated 2026-04-28**: Docker config + host moved off this dev VM (DESKTOP-6JL3KKO) onto the shared Linux Docker host (`DOCKER`, 10.100.0.35) so the dev VM could shed WSL2/Hyper-V and have its GPU re-attached via ESXi passthrough. Docker Desktop is no longer installed here. All checked-in `appsettings.json` defaults, fixture-class default endpoints, and `e2e-config.sample.json` were rewritten to target `10.100.0.35`. The driver fixture compose files under `tests/.../Docker/docker-compose.yml` now carry a `project: lmxopcua` label on every service. See `docs/v2/dev-environment.md` for the full rewrite (header dated 2026-04-28). Docker workloads run on a shared Linux host at **`10.100.0.35`** — not on this VM. Stacks live at `/opt/otopcua-/` on the host and carry the `project=lmxopcua` label so they're discoverable via `docker ps --filter label=project=lmxopcua`. **`docker -H ssh://...` does NOT work from this VM.** Windows OpenSSH ↔ docker.exe stdio bridging hangs (`docker system dial-stdio` runs server-side but no API data flows). Use the helper below — it SSHes into the docker host and runs `docker compose` server-side. **Use `lmxopcua-fix.ps1` (in `~/bin`) to control fixtures from this VM:** ```powershell lmxopcua-fix ls # list all lmxopcua-tagged containers on the host lmxopcua-fix up modbus standard # bring a profile up lmxopcua-fix up abcip controllogix lmxopcua-fix up s7 s7_1500 lmxopcua-fix up opcuaclient # single-service stack, no profile arg lmxopcua-fix down modbus # tear stack down lmxopcua-fix logs modbus lmxopcua-fix sync modbus # rsync this repo's tests/.../Docker/ → /opt/otopcua-modbus/ ``` **`sync` is the deployment step.** When you edit a fixture's compose file or Dockerfile under `tests/.../Docker/`, run `lmxopcua-fix sync ` to push the changes to the docker host before bringing the stack up. The repo files are the source of truth; `/opt/otopcua-/` is a mirrored deployment. **Endpoints (defaults already point at the docker host):** - SQL Server (always-on): `10.100.0.35,14330` — used by `appsettings.json` for `ConfigDb`. - Modbus: `10.100.0.35:5020` (`MODBUS_SIM_ENDPOINT`) - AB CIP: `10.100.0.35:44818` (`AB_SERVER_ENDPOINT`) - S7: `10.100.0.35:1102` (`S7_SIM_ENDPOINT`) - OPC UA reference (opc-plc): `opc.tcp://10.100.0.35:50000` (`OPCUA_SIM_ENDPOINT`) Override any endpoint via the env var to point at a real PLC. The local OtOpcUa server runs on this VM at `opc.tcp://localhost:4840` — **that's not on the docker host**. See `docs/v2/dev-environment.md` for the full inventory and rationale. ## Build & Runtime Constraints - Language: C#, .NET Framework 4.8, **x86 (32-bit)** platform target — required for MXAccess COM interop - MXAccess requires a deployed ArchestrA Platform on the machine running the server - COM apartment: MXAccess objects must live on an STA thread with a message pump ## Transport Security The server supports configurable OPC UA transport security via the `Security` section in `appsettings.json`. Phase 1 profiles: `None` (default), `Basic256Sha256-Sign`, `Basic256Sha256-SignAndEncrypt`. Security profiles are resolved by `SecurityProfileResolver` at startup. The server certificate is always created even for `None`-only deployments because `UserName` token encryption depends on it. See `docs/security.md` for the full guide. ## Redundancy The server supports non-transparent warm/hot redundancy via the `Redundancy` section in `appsettings.json`. Two instances share the same Galaxy DB and MXAccess runtime but have unique `ApplicationUri` values. Each exposes `RedundancySupport`, `ServerUriArray`, and a dynamic `ServiceLevel` based on role and runtime health. The primary advertises a higher ServiceLevel than the secondary. See `docs/Redundancy.md` for the full guide. ## LDAP Authentication The server uses LDAP-based user authentication via the `Authentication.Ldap` section in `appsettings.json`. When enabled, credentials are validated by LDAP bind against a GLAuth server (installed at `C:\publish\glauth\`), and LDAP group membership maps to OPC UA permissions: `ReadOnly` (browse/read), `WriteOperate` (write FreeAccess/Operate attributes), `WriteTune` (write Tune attributes), `WriteConfigure` (write Configure attributes), `AlarmAck` (alarm acknowledgment). `LdapUserAuthenticator` (`src/ZB.MOM.WW.OtOpcUa.Server/Security/LdapUserAuthenticator.cs`) implements `IUserAuthenticator`. See `docs/Security.md` for the full guide and `C:\publish\glauth\auth.md` for LDAP user/group reference. ## Library Preferences - **Logging**: Serilog with rolling daily file sink - **Unit tests**: xUnit + Shouldly for assertions - **Service hosting (Server, Admin)**: .NET generic host with `AddWindowsService` (decision #30 — replaced TopShelf in v2; see `src/ZB.MOM.WW.OtOpcUa.Server/OpcUaServerService.cs`) - **Service hosting (Galaxy.Host)**: plain console app wrapped by NSSM (`.NET Framework 4.8 x86` — required by MXAccess COM bitness) - **OPC UA**: OPC Foundation UA .NET Standard stack (https://github.com/opcfoundation/ua-.netstandard) — NuGet: `OPCFoundation.NetStandard.Opc.Ua.Server` ## OPC UA .NET Standard Documentation Use the DeepWiki MCP (`mcp__deepwiki`) to query documentation for the OPC UA .NET Standard stack: `https://deepwiki.com/OPCFoundation/UA-.NETStandard`. Tools: `read_wiki_structure`, `read_wiki_contents`, and `ask_question` with repo `OPCFoundation/UA-.NETStandard`. ## Testing Use the Client CLI at `src/ZB.MOM.WW.OtOpcUa.Client.CLI/` for manual testing against the running OPC UA server. Supports connect, read, write, browse, subscribe, historyread, alarms, and redundancy commands. See `docs/Client.CLI.md` for full documentation. ```bash dotnet run --project src/ZB.MOM.WW.OtOpcUa.Client.CLI -- connect -u opc.tcp://localhost:4840 dotnet run --project src/ZB.MOM.WW.OtOpcUa.Client.CLI -- browse -u opc.tcp://localhost:4840 -r -d 3 dotnet run --project src/ZB.MOM.WW.OtOpcUa.Client.CLI -- read -u opc.tcp://localhost:4840 -n "ns=2;s=SomeNode" dotnet run --project src/ZB.MOM.WW.OtOpcUa.Client.CLI -- subscribe -u opc.tcp://localhost:4840 -n "ns=2;s=SomeNode" -i 500 ```