Files
mxaccessgw/CLAUDE.md
Joseph Doherty e541339c07 docs(audit): apply per-cluster judgment fixes across living docs
Resolve audit findings: correct WorkerEnvelope proto/route/metric/session
facts; rewrite auth (ZB.MOM.WW.Auth migration), dashboard (ZB.MOM.WW.Theme),
and StyleGuide (foreign-project copy-paste); document alarm subsystem, Ldap
options, and gateway alarm broker; fix client CLI flags and package paths.
2026-06-03 16:01:28 -04:00

11 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

mxaccessgw is the MXAccess Gateway: a gRPC service that gives modern (.NET, Go, Rust, Python, Java) clients full MXAccess parity without forcing them to load 32-bit MXAccess COM, run x86, or own an STA message pump.

The architecture is a two-process design — read gateway.md before making structural changes:

  • Gateway (src/MxGateway.Server, .NET 10, x64): ASP.NET Core gRPC server. Owns the public API, sessions, auth, the Blazor dashboard, and the Galaxy Repository SQL browse RPCs. Never instantiates MXAccess COM directly.
  • Worker (src/MxGateway.Worker, .NET Framework 4.8, x86): one process per session. Owns one MXAccess COM instance on a dedicated STA, pumps Windows messages, and converts COM events to protobuf.
  • IPC: gateway↔worker uses one bidirectional named pipe per worker (mxaccess-gateway-{gatewayPid}-{sessionId}) with length-prefixed WorkerEnvelope protobuf frames. Gateway hosts the pipe server and launches the worker. gRPC is not used inside the worker — .NET Framework 4.8 doesn't have a first-class gRPC stack.
  • Contracts (src/MxGateway.Contracts): multi-targets net10.0;net48 and owns the .proto files (mxaccess_gateway.proto, mxaccess_worker.proto, galaxy_repository.proto). All other projects consume the generated types from here. Do not hand-edit anything under Generated/.

The worker must do all MXAccess COM calls on its dedicated STA thread, and the STA loop must pump Windows messages (MsgWaitForMultipleObjectsEx + PeekMessage/DispatchMessage) so MXAccess events deliver. A plain blocking queue on an STA is not enough.

Build, Test, Run

# Full solution build (gateway, worker, contracts, tests)
dotnet build src/ZB.MOM.WW.MxGateway.slnx

# Worker must be built x86 — the gateway looks for MxGateway.Worker.exe under bin\x86
dotnet build src/MxGateway.Worker/MxGateway.Worker.csproj -p:Platform=x86

# Gateway tests (no MXAccess required — uses FakeWorkerHarness)
dotnet test src/MxGateway.Tests/MxGateway.Tests.csproj
dotnet test src/MxGateway.Worker.Tests/MxGateway.Worker.Tests.csproj -p:Platform=x86

# Run gateway locally (defaults bound under MxGateway:* in src/MxGateway.Server/appsettings.json)
dotnet run --project src/ZB.MOM.WW.MxGateway.Server/ZB.MOM.WW.MxGateway.Server.csproj

# API-key admin CLI (same exe, "apikey" subcommand)
dotnet run --project src/ZB.MOM.WW.MxGateway.Server/ZB.MOM.WW.MxGateway.Server.csproj -- apikey create-key --key-id dev --display-name "dev" --scopes session:open,session:close,invoke:read,invoke:write,invoke:secure,events:read,metadata:read,admin

Single test by name (xUnit --filter):

dotnet test src/MxGateway.Tests/MxGateway.Tests.csproj --filter FullyQualifiedName~GatewayEndToEndFakeWorkerSmokeTests

Live MXAccess integration tests are opt-in because they need installed MXAccess COM and live provider state:

$env:MXGATEWAY_RUN_LIVE_MXACCESS_TESTS = "1"
dotnet test src/MxGateway.IntegrationTests/MxGateway.IntegrationTests.csproj --filter FullyQualifiedName~WorkerLiveMxAccessSmokeTests

Live LDAP tests use MXGATEWAY_RUN_LIVE_LDAP_TESTS=1. See docs/GatewayTesting.md for the full opt-in matrix and LiveMxAccessFactAttribute / LiveLdapFactAttribute for the gating logic.

Clients

Each language client is in clients/<lang>/ with its own README. They all consume the shared .proto files in src/MxGateway.Contracts/Protos:

  • clients/dotnet: dotnet build clients/dotnet/ZB.MOM.WW.MxGateway.Client.slnx
  • clients/python: python -m pip install -e ".[dev]"; python -m pytest
  • clients/rust: cargo test --workspace; cargo clippy --workspace --all-targets -- -D warnings
  • clients/java: gradle test (Java 21)
  • Go client lives alongside as mxgw-go in the cross-language matrix

End-to-end matrix runner (needs running gateway + worker + valid API key):

$env:MXGATEWAY_API_KEY = "<api-key>"
powershell -ExecutionPolicy Bypass -File scripts/run-client-e2e-tests.ps1

Repository-Specific Conventions

  • Build properties (src/Directory.Build.props) enforce Nullable=enable, TreatWarningsAsErrors=true, latest analyzers, and EnforceCodeStyleInBuild=true. New warnings break the build — fix them, don't suppress unless the suppression has a narrow reason.
  • Style guides in docs/style-guides/ are authoritative. Follow CSharpStyleGuide.md for gateway/worker/.NET-client code: file-scoped namespaces, sealed by default, Async suffix on Task-returning methods, MXAccess-aligned names (MxStatusProxy, ServerHandle, ItemHandle, HResult).
  • MXAccess parity is the contract. Don't "fix" surprising MXAccess behavior (e.g., WriteSecured failing before a value-bearing NMX body, distinct OperationComplete semantics, invalid-handle exceptions) unless the client explicitly opts into a non-parity mode. The installed MXAccess COM component is the baseline.
  • Don't synthesize events. The gateway forwards only events the worker emits; it never invents OperationComplete from write completion or command replies.
  • One worker per session, one event subscriber per session (v1). Multi-subscriber fan-out and reconnectable sessions are explicitly out of scope — see docs/DesignDecisions.md.
  • Gateway restart does not reattach orphan workers. The first version terminates orphaned workers on startup; do not design code paths that assume reattachment.
  • No Blazor UI component libraries. Dashboard uses local Bootstrap CSS/JS only — do not introduce MudBlazor, Radzen, FluentUI, etc.
  • Don't log secrets or full tag values by default. API keys, passwords, WriteSecured payloads, and AuthenticateUser credentials must never reach logs. Value logging is opt-in and redacted.
  • Generated code under src/MxGateway.Contracts/Generated/, clients/*/generated*/, clients/python/src/zb_mom_ww_mxgateway/generated/, etc., is build output. Don't hand-edit. To regenerate, build the contracts project (dotnet build src/MxGateway.Contracts/MxGateway.Contracts.csproj) or run the per-client generation step in that client's README.
  • Documentation style (StyleGuide.md): PascalCase filenames, no marketing language, present tense, explain why not what.
  • Update docs in the same change as the source. When public APIs, contracts, configuration, build steps, security behavior, event shapes, value conversion, status mapping, or lifecycle rules change, the affected docs (gateway.md, docs/, client READMEs, design docs) must change in the same commit. Don't leave stale prose describing old behavior.

Source Update Workflow

When source code changes, build and test the affected component before reporting work done. If the change crosses component boundaries, build each affected component — don't rely on a single top-level build:

Changed area Required verification
Contracts or .proto files regenerate generated code, then build gateway, worker, and every generated client touched by the contract
Gateway server, sessions, workers, gRPC, dashboard, metrics dotnet build src/MxGateway.Server and run affected gateway / fake-worker tests
Worker IPC, STA, MXAccess, conversion dotnet build src/MxGateway.Worker -p:Platform=x86 and run worker tests
.NET client dotnet build clients/dotnet/ZB.MOM.WW.MxGateway.Client.slnx and run its tests
Go client gofmt, go build ./..., go test ./... from clients/go
Rust client cargo fmt, cargo check --workspace, cargo test --workspace, cargo clippy --all-targets -- -D warnings from clients/rust
Python client python -m pytest from clients/python
Java client gradle test from clients/java
Integration tests run only when MXAccess COM, provider state, and external services are available; otherwise document why skipped

Design Sources To Consult Before Non-Trivial Changes

  • gateway.md — top-level architecture, command/event surface, IPC envelope, STA thread model, fault handling.
  • glauth.md — local LDAP server (GLAuth on localhost:3893, base DN dc=zb,dc=local) used for dev authn. Pre-provisioned users (admin/admin123, readonly/readonly123, etc.) and the role→capability mapping live there.
  • docs/DesignDecisions.md — v1 choices (MXAccess COM target LMXProxyServerClass from C:\Program Files (x86)\ArchestrA\Framework\Bin\ArchestrA.MXAccess.dll, API-key-in-SQLite auth, fail-fast event backpressure, etc.).
  • docs/GatewayProcessDesign.md, docs/MxAccessWorkerInstanceDesign.md, docs/WorkerFrameProtocol.md, docs/WorkerProcessLauncher.md — detailed component designs.
  • docs/GatewayConfiguration.md — full MxGateway:* options bound by GatewayOptions and validated at startup by GatewayOptionsValidator.
  • docs/GatewayTesting.md — fake worker harness, live MXAccess smoke, parity matrix, cross-language smoke matrix.
  • docs/ToolchainLinks.md — installed compiler/SDK paths on this dev box (.NET 10.0.201, Go 1.26.2, Rust 1.95, Python 3.12.10, Temurin 21, protoc 34.1, etc.).

External analysis sources referenced by design docs:

  • C:\Users\dohertj2\Desktop\mxaccess — MXAccess analysis project. Key files: docs/MXAccess-Public-API.md (COM class, ProgID, CLSID, method list, event signatures, MxDataType, MxStatus, MXSTATUS_PROXY), docs/MXAccess-Reverse-Engineering.md (installed runtime path, x86 COM constraints), docs/Current-Sprint-State.md (parity gaps), src/MxTraceHarness/ (x86 harness using the real COM interop), captures/ and analysis/ (observed native behavior).
  • C:\Users\dohertj2\Desktop\lmxopcua\gr — Galaxy Repository (ZB SQL DB) notes. Key files: connectioninfo.md, layout.md, schema.md, queries/hierarchy.sql, queries/attributes.sql, queries/attributes_extended.sql, queries/change_detection.sql. Connection is SQL Server localhost, database ZB, Windows Auth.

Authentication

Gateway gRPC clients authenticate with an API key in metadata: authorization: Bearer mxgw_<key-id>_<secret>. Keys are stored hashed (with a peppered SHA) in a gateway-owned SQLite DB (default C:\ProgramData\MxGateway\gateway-auth.db). Scopes (session:open, session:close, invoke:read, invoke:write, invoke:secure, events:read, metadata:read, admin) gate specific RPCs; missing → Unauthenticated, insufficient → PermissionDenied. The apikey subcommand on the server exe manages keys; see src/MxGateway.Server/Security/Authentication/.

Dashboard auth is LDAP-backed (separate from the gRPC API-key model). /login binds against MxGateway:Ldap and maps the user's LDAP groups to Administrator or Viewer via MxGateway:Dashboard:GroupToRole, then issues an HTTP-only secure MxGatewayDashboard cookie. SignalR hubs at /hubs/{snapshot,alarms,events} accept either the cookie or a 30-minute bearer minted at /hubs/token. Dashboard:AllowAnonymousLocalhost bypasses auth on loopback when enabled.

Process / Platform Notes

  • Working tree is on Windows (C:\Users\dohertj2\Desktop\mxaccessgw). PowerShell is the native shell for tooling commands; bash is fine for git/grep/find.
  • The worker reference to ArchestrA.MXAccess.dll uses an absolute HintPath to C:\Program Files (x86)\ArchestrA\Framework\Bin\ArchestrA.MXAccess.dll. The worker only builds where MXAccess is installed (this dev box).
  • The repo is not a git repository at the top level — there's no .git directory in the working tree.