diff --git a/CLAUDE.md b/CLAUDE.md index a987ac6..10dc8ff 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,12 +6,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co `scadaproj` is primarily an umbrella/index workspace that aggregates a family of related SCADA / OT / Wonderware / OPC UA "sister projects" that live as **sibling -directories under `~/Desktop/`**. It now also **hosts five pieces of source itself** — +directories under `~/Desktop/`**. It now also **hosts six pieces of source itself** — the shared [`ZB.MOM.WW.Auth/`](ZB.MOM.WW.Auth/) library, the shared [`ZB.MOM.WW.Theme/`](ZB.MOM.WW.Theme/) UI kit, the shared [`ZB.MOM.WW.Health/`](ZB.MOM.WW.Health/) health-check library, the shared -[`ZB.MOM.WW.Telemetry/`](ZB.MOM.WW.Telemetry/) observability library, and the shared -[`ZB.MOM.WW.Configuration/`](ZB.MOM.WW.Configuration/) config-validation library — all the realized output of their +[`ZB.MOM.WW.Telemetry/`](ZB.MOM.WW.Telemetry/) observability library, the shared +[`ZB.MOM.WW.Configuration/`](ZB.MOM.WW.Configuration/) config-validation library, and the new +[`ZB.MOM.WW.GalaxyRepository/`](ZB.MOM.WW.GalaxyRepository/) Galaxy browse library — all the realized output of their respective component normalizations (see [Component normalization](#component-normalization)). The point of this file is to give a high-level scan of each sister project — its purpose, location, stack, and primary commands — so a fresh Claude Code session can orient across @@ -30,9 +31,10 @@ own `CLAUDE.md` for the full picture. See [Refreshing this index](#refreshing-th | Project | Location | Stack | Repo | Summary | |---|---|---|---|---| -| **OtOpcUa** | `~/Desktop/OtOpcUa` | .NET 10, OPC UA, gRPC | `gitea.dohertylan.com/dohertj2/lmxopcua` | OPC UA server that exposes AVEVA System Platform (Wonderware) Galaxy tags as an OPC UA address space. Galaxy access flows through an in-process `GalaxyDriver` → gRPC → the **mxaccessgw** gateway. | +| **OtOpcUa** | `~/Desktop/OtOpcUa` | .NET 10, OPC UA, gRPC | `gitea.dohertylan.com/dohertj2/lmxopcua` | OPC UA server that exposes industrial data sources under a **unified Equipment-based address space** — native-protocol drivers (Modbus, S7, AB CIP/Legacy, TwinCAT, FOCAS, OpcUaClient) **and AVEVA System Platform (Wonderware) Galaxy, now a standard Equipment-kind driver** (the old SystemPlatform mirror / alias-tag model was retired ~2026-06-12). Galaxy access flows through the in-process `GalaxyDriver` → gRPC → the **mxaccessgw** gateway. Surfaces live read + authorized write, native OPC UA Part 9 alarms, and server-side HistoryRead. | | **MxAccessGateway** (`mxaccessgw`) | `~/Desktop/MxAccessGateway` | .NET 10 gateway (x64) + .NET 4.8 worker (**x86**), gRPC | `gitea.dohertylan.com/dohertj2/mxaccessgw` | gRPC gateway giving modern clients full MXAccess parity without loading 32-bit COM. Two-process: gateway (ASP.NET Core gRPC + Blazor dashboard) + per-session x86 worker that owns the MXAccess COM STA. **OtOpcUa depends on this.** | | **ScadaBridge** | `~/Desktop/ScadaBridge` | .NET 10, Akka.NET, Docker | _git_ | Full implementation of the distributed SCADA platform — hub-and-spoke (1 central cluster + N site clusters). Projects prefixed `ZB.MOM.WW.ScadaBridge.*`; solution `ZB.MOM.WW.ScadaBridge.slnx`. Ships `src/`, `tests/`, `docker/` topology, and the design docs that are the spec. | +| **HistorianGateway** | `~/Desktop/HistorianGateway` | .NET 10 x64, gRPC, Blazor | _local only (not yet pushed to gitea)_ | Single-process gRPC sidecar exposing (1) full read/write API to the AVEVA Historian (5 gRPC services; 15 retrieval modes; historical/backfill writes; tag-config lifecycle; SQL live-value path; store-forward + redundancy resilience; all default-disabled) and (2) read-only Galaxy object-hierarchy browse via the new shared `ZB.MOM.WW.GalaxyRepository` lib. No COM, no x86 worker. Dashboard on `:5220` (HTTP/1.1); gRPC h2c on `:5221`. Vendors `AVEVA.Historian.Client` from `histsdk`. 584 tests green. | ## Cross-project relationships @@ -84,8 +86,10 @@ the gateway uses `MxGateway.*`). The common subject is **AVEVA System Platform ( `GalaxyRepositoryClient` for the static hierarchy, and an MXAccess session (`MxCommand`/`MxEvent` protos) for live read/write/subscribe. A `DeployWatcher` polls the gateway's deploy-event signal to rebuild the OPC UA address space on Galaxy redeploy. - OtOpcUa's job is purely a **protocol bridge**: it republishes Galaxy as an OPC UA address - space for *any* OPC UA client. + OtOpcUa's job is a **protocol bridge**: it republishes Galaxy — now bound as a *standard + Equipment-kind driver* alongside its native-protocol drivers, not a special SystemPlatform + mirror — as an OPC UA address space (live values, Part 9 alarms, HistoryRead) for *any* OPC + UA client. - **ScadaBridge → OPC UA** (OPC UA client). ScadaBridge's DCL has an OPC UA adapter that collects data and mirrors native OPC UA Alarms & Conditions. OtOpcUa is exactly such a server, so ScadaBridge can ingest Wonderware data **indirectly via OtOpcUa**. @@ -101,15 +105,21 @@ the gateway uses `MxGateway.*`). The common subject is **AVEVA System Platform ( - ScadaBridge has **two paths** to the same Wonderware data: (1) OPC UA → OtOpcUa → gateway, or (2) MxGateway adapter → gateway directly. Path 1 gives standards-based OPC UA decoupling; path 2 gives a more direct/native feed. +- **HistorianGateway is a new, independent sidecar** (no runtime coupling to the three above). + It reaches the Historian via its vendored gRPC client and the Galaxy Repository SQL DB directly, + not through `mxaccessgw`. It consumes the shared `ZB.MOM.WW.GalaxyRepository` lib + (cross-repo `ProjectReference`). Any client that needs Historian data or Galaxy browse can + target HistorianGateway independently; it is not a dependency of OtOpcUa or ScadaBridge today. - Coupling is loose: each repo references the others only as **sibling context** (the `## Sister Projects` note in ScadaBridge's own `CLAUDE.md` lists `MxAccessGateway` and `OtOpcUa` with their Gitea URLs but states they are *not part of its solution*). - **The break surface is the wire contracts, not code.** Because coupling is by network protocol, the things that break across repo boundaries are: the gateway's `.proto` files - (`mxaccess_gateway.proto`, `mxaccess_worker.proto`, `galaxy_repository.proto`), and the - OPC UA address-space shape OtOpcUa publishes (browse paths, node IDs, A&C alarm model). - Changes to any of these must be coordinated across the affected repos — a green build in - one repo does not prove the others still interoperate. + (`mxaccess_gateway.proto`, `mxaccess_worker.proto`, `galaxy_repository.proto`), the + `historian_gateway.v1` proto (HistorianGateway's own contract), and the OPC UA address-space + shape OtOpcUa publishes (browse paths, node IDs, A&C alarm model). Changes to any of these + must be coordinated across the affected repos — a green build in one repo does not prove the + others still interoperate. ## Component normalization @@ -126,6 +136,7 @@ each project's **code-verified current state**, and the **gaps** between. See | Observability (metrics / traces / logs) | Built (lib `0.1.0`) | Shared `ZB.MOM.WW.Telemetry` lib + `.Serilog` | [`components/observability/`](components/observability/) | [`ZB.MOM.WW.Telemetry/`](ZB.MOM.WW.Telemetry/) | | Config + validation (options / startup validation) | Adopted (lib `0.1.0`; all 3 apps, local) | Shared `ZB.MOM.WW.Configuration` lib | [`components/configuration/`](components/configuration/) | [`ZB.MOM.WW.Configuration/`](ZB.MOM.WW.Configuration/) | | Audit (event model + writer seam) | Adopted (lib `0.1.0`; all 3 apps, merged to **local default** main/master + **pushed to origin** (gitea)) | Shared `ZB.MOM.WW.Audit` lib | [`components/audit/`](components/audit/) | [`ZB.MOM.WW.Audit/`](ZB.MOM.WW.Audit/) | +| Galaxy Repository (object-hierarchy SQL browse + gRPC service) | Built (lib `0.1.0`; consumed by HistorianGateway via ProjectReference) | Shared `ZB.MOM.WW.GalaxyRepository` lib | _(design in histsdk + design doc 2026-06-23)_ | [`ZB.MOM.WW.GalaxyRepository/`](ZB.MOM.WW.GalaxyRepository/) | The auth component is fully populated: a normalized [`spec`](components/auth/spec/SPEC.md), a proposed [`shared-contract`](components/auth/shared-contract/ZB.MOM.WW.Auth.md), three @@ -261,6 +272,24 @@ migration, MSSQL-verified). Phase 3 wires `Actor` from the Auth principal at aut Build/test from `ZB.MOM.WW.Audit/`: `dotnet test`. Consumer matrix: all three apps consume the single `ZB.MOM.WW.Audit` package (OtOpcUa, MxAccessGateway, ScadaBridge — DEEP-adopted as the canonical record). +The Galaxy Repository component normalizes the **Galaxy object-hierarchy SQL browse + reusable gRPC service** +that was previously embedded in `mxaccessgw`. Shared = canonical `galaxy_repository.v1` proto (wire-compatible +with `mxaccessgw`'s existing contract so OtOpcUa's `GalaxyRepositoryClient` is unaffected), the SQL browse +provider (`HierarchySql` / `AttributesSql` validated reverse-engineered queries), in-memory hierarchy cache + +snapshot + deploy-poll refresh `BackgroundService`, `GalaxyHierarchyProjector`, and `AddZbGalaxyRepository` / +`MapZbGalaxyRepository` DI extension. Left per-consumer = section path, subtree auth filtering, and any +app-specific paging defaults. + +The shared library is **built and lives in this repo** at [`ZB.MOM.WW.GalaxyRepository/`](ZB.MOM.WW.GalaxyRepository/) +(.NET 10; single package `ZB.MOM.WW.GalaxyRepository`; `dotnet pack` → 1 nupkg @ 0.1.0, **locally built, +NOT yet published to the Gitea feed**). The design doc is at +[`docs/plans/2026-06-23-historian-gateway-design.md`](docs/plans/2026-06-23-historian-gateway-design.md) (§10, component 1). +**Consumed by HistorianGateway from the start** (via cross-repo `ProjectReference` to this scadaproj tree). +**mxaccessgw adoption is a tracked follow-on** — once adopted, mxaccessgw's inline Galaxy browse code is replaced +by the shared lib (the `galaxy_repository.v1` wire contract is already identical, so OtOpcUa and ScadaBridge +clients are unaffected). Build/test from `ZB.MOM.WW.GalaxyRepository/`: `dotnet test`. +Consumer matrix: HistorianGateway (initial); mxaccessgw (follow-on adoption). + ## Per-project primary commands Run these from inside each project directory (not from `scadaproj`). @@ -282,9 +311,17 @@ dotnet run --project src/MxGateway.Server/MxGateway.Server.csproj dotnet build ZB.MOM.WW.ScadaBridge.slnx bash docker/deploy.sh # rebuild + redeploy the 8-node cluster cd infra && docker compose up -d # local test services (SQL, OPC UA, SMTP, REST, Traefik) — LDAP is NOT here + +# HistorianGateway (~/Desktop/HistorianGateway) +dotnet build ZB.MOM.WW.HistorianGateway.slnx +dotnet test ZB.MOM.WW.HistorianGateway.slnx # unit + golden; live integration tests skip without env vars +dotnet run --project src/ZB.MOM.WW.HistorianGateway.Server/ZB.MOM.WW.HistorianGateway.Server.csproj +# dashboard on :5220, gRPC h2c on :5221 +# Live integration (need HISTORIAN_GRPC_HOST + HISTORIAN_GRPC_WRITE_SANDBOX_TAG + GALAXY_SQL_CONNSTR set) +dotnet test ZB.MOM.WW.HistorianGateway.slnx --filter "Category=LiveIntegration" ``` -> **Shared GLAuth (all three apps):** LDAP auth for every local dev/test stack is provided by a +> **Shared GLAuth (all three apps + HistorianGateway):** LDAP auth for every local dev/test stack is provided by a > single `zb-shared-glauth` container on the Linux fixture host **`10.100.0.35:3893`** > (`baseDN dc=zb,dc=local`, Transport=None). Source of truth and deploy runbook: > [`scadaproj/infra/glauth/`](infra/glauth/) (`config.toml` + `docker-compose.yml` + `README.md`).