Family-wide pin refresh of 2026-09-03: OtOpcUa b8b4c18f, mxgw 4f931e4, HistorianGateway d8d890c, ScadaBridge e45c4af9 (Auth already on main). Per-library narratives in ZB.MOM.WW.Auth/CLAUDE.md and ZB.MOM.WW.LocalDb/CLAUDE.md updated to match; the stale "unpushed / 0.6.0 / 0.5.1 / merge pending" notes are corrected. Claude-Session: https://claude.ai/code/session_01Wzg6NUCWHipSexLs2cgRFv
27 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What this repository is
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 nine shared libraries itself (verified
2026-07-18) — ZB.MOM.WW.Auth/ (login/identity),
ZB.MOM.WW.Theme/ (UI kit),
ZB.MOM.WW.Health/ (health checks),
ZB.MOM.WW.Telemetry/ (observability),
ZB.MOM.WW.Configuration/ (config validation),
ZB.MOM.WW.Audit/ (audit event model),
ZB.MOM.WW.Secrets/ (encrypted secret store),
ZB.MOM.WW.GalaxyRepository/ (Galaxy browse), and
ZB.MOM.WW.LocalDb/ (embedded cache + 2-node sync) — all the realized output of their
respective component normalizations (see Component normalization).
(A tenth directory, ZB.MOM.WW.SPHistorianClient/, is retired — 0 tracked files, only
gitignored build residue left on disk.)
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
the whole family without opening each repo first.
Each sister project keeps its own authoritative CLAUDE.md. This index is a
summary of those files; when you actually work in a project, open that project's
own CLAUDE.md for the full picture. See Refreshing this index.
The project list below is curated manually. Add or remove entries as the family changes — do not assume every directory under
~/Desktop/belongs here.
Sister projects (core SCADA/OT family)
Runtime / implementation (active code)
| Project | Location | Stack | Repo | Summary |
|---|---|---|---|---|
| OtOpcUa | ~/Desktop/OtOpcUa |
.NET 10, OPC UA, gRPC | gitea.dohertylan.com/dohertj2/lmxopcua |
OPC UA server that exposes industrial data sources under a two-subtree address space (v3.0, master since 2026-07-16) — https://zb.com/otopcua/raw (device tree Folder→Driver→Device→TagGroup→Tag, NodeId s=<RawPath>) and https://zb.com/otopcua/uns (equipment tree Area→Line→Equipment→signal); every value has one source fanned to both NodeIds. This is the wire contract ScadaBridge binds against (ScadaBridge consumes v3 end-to-end; its #14 is closed). Drivers: Modbus, S7, AB CIP/Legacy, TwinCAT, FOCAS, OpcUaClient, Sql (SQL Server / PostgreSQL / Oracle, read-only poll), Mqtt (Plain + Sparkplug B, writable where a tag declares a destination), MTConnect (read-only), Calculation, and AVEVA Galaxy as a standard Equipment-kind driver via in-process GalaxyDriver → gRPC → mxaccessgw. Sole historian backend is HistorianGateway. Address-space availability guarantee: an unreadable deployment artifact is no answer, never an empty configuration — nodes hold last-known-good, and a driver-only node boots and serves from its LocalDb cache with central SQL down (ServiceLevel stays 240/250). Redundancy: one independent 2-node Akka mesh per application Cluster; Primary = oldest Up driver member (ServiceLevel 250) + one Secondary (240) per Cluster; cross-mesh commands ride gRPC with per-cluster PSK auth (Akka ClusterClient fully deleted 2026-08-06). Data plane is machine-to-machine: OPC UA UserName apikey + otua_<keyId>_<secret> minted on AdminUI /api-keys; OpcUa:AllowAnonymous defaults true (anonymous is a normal grantable principal); LDAP is AdminUI-login only. Node ACLs are enforced only behind Security:Acl:Enabled (default false); with it on, scoped NodeAcl grants decide every data-plane operation incl. browse/read/subscribe, fail closed, and reach open sessions. Every driver's authoring path now consults the driver's own runtime parse, so what authoring accepts is what the driver serves (tracker zeroed 2026-08-10, origin d06e92c6). Full change history, live-gate records and the per-issue consumer-impact record for ScadaBridge: .claude/skills/sister-project-history/otopcua.md (skill sister-project-history). |
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 | gitea.dohertylan.com/dohertj2/ScadaBridge |
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. Inbound API keys live on the shared SQL Server store on a central pair (design 2026-09-02, ScadaBridge register row 108): ScadaBridge is Auth's first consumer of the new ZB.MOM.WW.Auth.ApiKeys.SqlServer package at 0.3.0, pointing both central nodes at one zbauth schema in the configuration database instead of each node's own per-node SQLite file. |
| HistorianGateway | ~/Desktop/HistorianGateway |
.NET 10 x64, gRPC, Blazor | gitea.dohertylan.com/dohertj2/historiangw |
Single-process gRPC sidecar exposing (1) a 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 shared ZB.MOM.WW.GalaxyRepository lib. No COM, no x86 worker. Owns AVEVA.Historian.Client at histsdk/ (ordinary owned source, edited in place — the re-vendor model is retired). Dev: dashboard :5220 (HTTP/1.1) + gRPC h2c :5221; production: one Kestrel:Endpoints:Https with Protocols: Http1AndHttp2 (ALPN). Default-on leased session pools amortize the historian handshake (v6 pool + a separate v8 event-session pool). Client lib ZB.MOM.WW.HistorianGateway.Client + Contracts are on the Gitea feed at 0.3.0; OtOpcUa is the first consumer (its sole historian backend since PR #423); ScadaBridge has no coupling. Known 2023 R2 server-side gates: native event reads return 0 rows (C2, won't-fix — SQL-path ReadEvents via Runtime.dbo.Events is the shipped workaround), Int1/UInt1 analog-tag creation is degenerate (OtOpcUa maps Boolean → Int2), string historical writes non-convergent (follow-ups #18–#25). Only remaining archreview item: the secure-by-default TLS flip, deferred to 1.0.0. Full change history (ArchReview cycles 1–2, pending.md items, PR list): .claude/skills/sister-project-history/historiangateway.md. |
Cross-project relationships
The three indexed projects are separate repos and separate processes, coupled at
runtime over wire protocols (gRPC + OPC UA) — not by project/compile references. They
share the ZB.MOM.WW.* product namespace (ZB.MOM.WW.OtOpcUa.*, ZB.MOM.WW.ScadaBridge.*;
the gateway uses MxGateway.*). The common subject is AVEVA System Platform (Wonderware)
"Galaxy" data, and mxaccessgw is the linchpin that the other two connect through.
Data flow
AVEVA System Platform — Wonderware "Galaxy"
(OT source of truth: runtime tags + Galaxy Repository SQL DB)
▲
│ MXAccess COM (32-bit, STA message pump)
│
┌────────────────┴─────────────────┐
│ MxAccessGateway (mxaccessgw) │ gateway x64 .NET10 + worker x86 net48
│ gRPC service; OWNS the 32-bit │ protos: mxaccess_gateway / mxaccess_worker
│ COM bitness + STA pump │ / galaxy_repository
└──────┬─────────────────────┬──────┘
gRPC (MxCommand/MxEvent + │ gRPC (ScadaBridge "MxGateway" adapter:
GalaxyRepository browse) │ native MxAccess data + A&C alarms)
│ │
┌─────┴──────┐ │
│ OtOpcUa │ GalaxyDriver maps │
│ OPC UA srv │ Galaxy hierarchy → │
│ (.NET 10) │ OPC UA addr space │
└─────┬──────┘ │
│ OPC UA (opc.tcp; data + A&C alarms)
▼ ▼
┌──────────────────────────────────────────────┐
│ ScadaBridge — Data Connection Layer (DCL) │
│ OPC UA adapter │ MxGateway adapter │ custom │
└─────────────────────────┬────────────────────┘
▼
Instance Actors → site clusters → central cluster / UI
Edge-by-edge
- MxAccessGateway is the foundation. It is the only component that loads 32-bit MXAccess COM (its x86 net48 worker owns the COM apartment + STA pump). It exposes that to modern x64/.NET-10 callers over gRPC, and also serves Galaxy Repository SQL browse RPCs. This is why the other two exist as .NET 10 / x64 and never touch COM directly.
- OtOpcUa → MxAccessGateway (gRPC client). OtOpcUa's in-process
GalaxyDriver(src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy/) uses two gateway channels: theGalaxyRepositoryClientfor the static hierarchy, and an MXAccess session (MxCommand/MxEventprotos) for live read/write/subscribe. ADeployWatcherpolls the gateway's deploy-event signal to rebuild the OPC UA address space on Galaxy redeploy. 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.
- ScadaBridge → MxAccessGateway (gRPC client). The DCL also has a dedicated
MxGateway adapter that talks to
mxaccessgwdirectly for native MxAccess data and alarms — so ScadaBridge can reach Wonderware data directly, bypassing OtOpcUa. Both adapters implement the sameIAlarmSubscribableConnectionseam, and a read-onlyNativeAlarmActorunifies OPC-UA-A&C and MxAccess alarms onto one condition model.
Net effect
mxaccessgwis a shared dependency of both OtOpcUa and ScadaBridge.- 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 near-independent sidecar. It reaches the Historian via its owned gRPC
client (
AVEVA.Historian.Client, imported into the repo athistsdk/) and the Galaxy Repository SQL DB directly, not throughmxaccessgw. It consumes the sharedZB.MOM.WW.GalaxyRepositorylib (Gitea-feed package). Any client that needs Historian data or Galaxy browse can target it independently. As of 2026-06-27 it is a runtime dependency of OtOpcUa (ScadaBridge still has no coupling): OtOpcUa adopted HistorianGateway as its sole historian read/write backend (continuous historization + alarms via the publishedZB.MOM.WW.HistorianGateway.Client), retiring its bespoke Wonderware historian driver — merged to OtOpcUamaster(PR #423). The gateway-side follow-ups also merged tohistoriangwmain: the Plan-1 client lib + packable Contracts (PR #5) and theWriteLiveValuesUTC→server-local timestamp fix + a documentedSendEventSource_Objectprotocol limitation (PR #6,pending.mdC4). OtOpcUa pre-existing test failure tracked aslmxopcuaissue #424. - Coupling is loose: each repo references the others only as sibling context (the
## Sister Projectsnote in ScadaBridge's ownCLAUDE.mdlistsMxAccessGatewayandOtOpcUawith 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
.protofiles (mxaccess_gateway.proto,mxaccess_worker.proto,galaxy_repository.proto), thehistorian_gateway.v1proto (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
Because the sister repos re-implement the same cross-cutting concerns separately and drift
apart, components/ normalizes them: per component, the one target spec,
each project's code-verified current state, and the gaps between. See
components/README.md for the convention and workflow.
| Component | Status | Goal | Design | Implementation |
|---|---|---|---|---|
| Auth (login / identity / authz) | Adopted. Feed carries 0.3.0 (2026-09-02; 5 packages, newest ZB.MOM.WW.Auth.ApiKeys.SqlServer). All four apps pin 0.3.0 (2026-09-03, all on their default branch and pushed: OtOpcUa b8b4c18f, mxgw 4f931e4, HistorianGateway d8d890c; ScadaBridge already had it on main via the inbound-api-key-shared-store work, e45c4af9). 0.2.1 is a functional fix, not housekeeping — 0.1.5 cannot log in against an AD domain root at all. FallbackServers (0.2.0) live-gated on the redundant local GLAuth pair. Version narrative, consumer matrix, tools/LdapProbe: ZB.MOM.WW.Auth/CLAUDE.md. |
Shared ZB.MOM.WW.Auth lib |
components/auth/ |
ZB.MOM.WW.Auth/ |
| UI Theme (layout / tokens / components) | Adopted. Feed 0.2.0–0.4.1; five consumers (the ignitionoee router joined), all on 0.4.1 and pushed (2026-08-11); HistorianGateway on wonder still runs Theme 0.3.1 (its 0.4.1 is on main, not yet deployed). On bump, TRIM the local .btn block, don't delete it — mxgw's carried more than sizing. Narrative: ZB.MOM.WW.Theme/CLAUDE.md. |
Shared ZB.MOM.WW.Theme RCL |
components/ui-theme/ |
ZB.MOM.WW.Theme/ |
| Health (readiness / liveness / active-node) | Adopted — lib 0.3.0, all 4 apps reference it + wire MapZbHealth. 0.2.1/0.3.0 changed the active tier's behaviour (standby now answers 503; active = oldest Up member via ClusterActiveNode, not RoleLeader — the two diverge after any restart), and both Akka apps' private copies were deleted in its favour (merged + pushed). Narrative: ZB.MOM.WW.Health/CLAUDE.md. |
Shared ZB.MOM.WW.Health lib |
components/health/ |
ZB.MOM.WW.Health/ |
| Observability (metrics / traces / logs) | Adopted (libs 0.1.0, on the feed; all 4 apps call AddZbTelemetry; AddZbSerilog in all but ScadaBridge, which keeps its own factory by design) |
Shared ZB.MOM.WW.Telemetry lib + .Serilog |
components/observability/ |
ZB.MOM.WW.Telemetry/ |
| Config + validation (options / startup validation) | Adopted (lib 0.1.0, on the feed; all 4 apps, pushed — the "local only / not yet pushed" caveat was stale) |
Shared ZB.MOM.WW.Configuration lib |
components/configuration/ |
ZB.MOM.WW.Configuration/ |
| Audit (event model + writer seam) | Adopted (lib 0.1.0, on the feed; all 4 apps, pushed to origin) |
Shared ZB.MOM.WW.Audit lib |
components/audit/ |
ZB.MOM.WW.Audit/ |
| Galaxy Repository (object-hierarchy SQL browse + gRPC service) | Adopted (feed 0.1.0 + 0.2.0; consumed at 0.2.0 by HistorianGateway and mxaccessgw, whose Server wires AddZbGalaxyRepository — verified 2026-07-18; the prior "mxaccessgw adoption is a follow-on" claim was stale) |
Shared ZB.MOM.WW.GalaxyRepository lib |
(design in histsdk + design doc 2026-06-23) | ZB.MOM.WW.GalaxyRepository/ |
Secrets (encrypted store + ${secret:} resolution) |
Built; family version 0.6.2, published. Pins: all four apps 0.6.2 (verified against each repo's checked-in pins AND its origin default branch 2026-09-03 — the earlier "OtOpcUa unpushed / mxgw 0.6.0 / ScadaBridge 0.5.1" notes were stale). Target 0.6.1+, never 0.6.0, and use the 4-arg AddZbSecrets(..., contentRootPath) overload at every pre-host ${secret:} expander call site — the pin alone does not close the hole. Secrets:SqlitePath must be absolute and outside the content root (fail-closed, no opt-out; the 2026-08-09 mxgw outage). Production topologies decided (scadaproj#3/#4 closed): OtOpcUa = Akka pair-local, ScadaBridge = gRPC hub over ONE shared central SQL store (0.5.1 minimum for central pairs); both default-off. Full narrative (0.1.x→0.6.2, incidents, live gates, container caveat, NU1102 note): ZB.MOM.WW.Secrets/CLAUDE.md. |
Shared ZB.MOM.WW.Secrets lib (5 packages + CLI) |
components/secrets/ |
ZB.MOM.WW.Secrets/ |
| LocalDb (embedded cache + 2-node sync) | Adopted by ScadaBridge (Phases 1+2 merged; pins 0.3.4) and OtOpcUa (Phases 1+2 merged; pins 0.3.4 since 2026-09-03, b8b4c18f) — no version skew remains (every release is wire-compatible, so one would be supported). Replication is default-OFF; enabled only on the docker rigs' site-a pairs, no production deployment has it enabled. Operational constraints: a pair stops/starts together (no rolling upgrade), a node offline > TombstoneRetention (7 d) can resurrect deleted rows. Full release narrative (0.1.1→0.3.4) + gates: ZB.MOM.WW.LocalDb/CLAUDE.md. |
Shared ZB.MOM.WW.LocalDb lib |
docs/plans/2026-07-17-localdb-design.md + the two adoption designs (ScadaBridge, OtOpcUa) |
ZB.MOM.WW.LocalDb/ |
Each hosted library directory carries its own CLAUDE.md with the full version narrative, consumer matrix and
adoption record — it loads automatically when you work under that directory. Health, Telemetry and Configuration
had one already; Auth, Theme, Audit, Secrets, GalaxyRepository, LocalDb and Overview got theirs on 2026-09-03
when this history was split out of the index. Per-component specs, current-state docs and GAPS backlogs live under
components/<name>/.
Hosted application: ZB.MOM.WW.Overview (family dashboard)
Besides the shared libraries, this repo hosts one application:
ZB.MOM.WW.Overview/ — a Blazor Server single-pane dashboard that polls every registered
instance's anonymous /health/ready + /health/active and renders per-cluster state including the Akka leader chip.
No login, strictly read-only, holds no credentials — a requirement, not an omission. Deployed on wonder-app-vd03:5320
(DEV) and on both QA pair nodes wonder-app-vq03/vq04:5320. Design docs, the two deploy constraints (self-contained
publish; registry BaseUrl host rule) and the QA topology note: ZB.MOM.WW.Overview/CLAUDE.md.
⚠️
wonder-app-vd03is the DEV box, not production (vd= DEV,vq= QA — confirmed bymes-delmia-integration-api.md§1.1). ItsASPNETCORE_ENVIRONMENTgenuinely isProduction, but that is a .NET config-overlay name and is orthogonal to the deployment tier. No production box has been identified. Full host/port inventory for both tiers:deployment.md§2.1 + §7.1.
Per-project primary commands
Run these from inside each project directory (not from scadaproj).
# OtOpcUa
dotnet build ZB.MOM.WW.OtOpcUa.slnx
dotnet test ZB.MOM.WW.OtOpcUa.slnx
dotnet test --filter "FullyQualifiedName~MyTestClass.MyMethod" # single test
# Docker fixtures run on shared host 10.100.0.35; control via `lmxopcua-fix` (in ~/bin)
# MxAccessGateway (PowerShell on Windows)
dotnet build src/MxGateway.sln
dotnet build src/MxGateway.Worker/MxGateway.Worker.csproj -p:Platform=x86 # worker MUST be x86
dotnet test src/MxGateway.Tests/MxGateway.Tests.csproj # no MXAccess needed (fake worker)
dotnet run --project src/MxGateway.Server/MxGateway.Server.csproj
# ScadaBridge (~/Desktop/ScadaBridge)
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
# Dev: dashboard on :5220 (HTTP/1.1), gRPC h2c on :5221 (from appsettings.Development.json)
# Production: single Kestrel:Endpoints:Https with Protocols=Http1AndHttp2 (ALPN, one TLS port)
# 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"
# Local Docker (HistorianGateway/docker/): publish FDD on the host (authed Gitea feed → no Docker restore),
# runtime-only aspnet:10.0 image; login + gRPC API-key auth disabled; points at the REAL wonder datasources.
dotnet publish src/ZB.MOM.WW.HistorianGateway.Server/ZB.MOM.WW.HistorianGateway.Server.csproj -c Release -o docker/publish -p:UseAppHost=false
cd docker && docker compose up -d --build # dashboard/health/metrics :5220, gRPC h2c :5221 (needs host VPN for egress)
Shared GLAuth (all three apps + HistorianGateway): LDAP auth for every local dev/test stack is provided by a single
zb-shared-glauthcontainer on the Linux fixture host10.100.0.35:3893(baseDN dc=zb,dc=local, Transport=None). Source of truth and deploy runbook:scadaproj/infra/glauth/(config.toml+docker-compose.yml+README.md).
Refreshing this index
This file is meant to be re-scanned when scadaproj is opened in Claude Code:
- List sibling SCADA/OT directories:
find ~/Desktop -maxdepth 2 -iname "claude.md". - For each project the user wants indexed, read the top of its
CLAUDE.md(project overview + build/run sections) and update its row above. - Keep the project set curated — only the SCADA/OT/Wonderware/OPC-UA family belongs here.
- Flag new duplicates/overlaps and namespace mismatches rather than silently merging them.
- Keep table rows short. Detailed per-project history lives in
.claude/skills/sister-project-history/<project>.md(sister repos) and in<lib>/CLAUDE.md(hosted libraries + Overview) — append there and keep the row here to a few lines. Sister repos whose ownCLAUDE.mdsays "update its entry in scadaproj/CLAUDE.md" should land detail in those files.
Index edits go on main, and get pushed
Every sister project's CLAUDE.md tells it to update its entry here whenever a fact this
index records changes. That edit lands in this repo, which has its own checkout — so a
naive git commit writes it onto whatever branch scadaproj happens to be on, usually an
unrelated feature branch, sometimes one with no upstream. The index then disagrees with
reality for as long as that branch stays unmerged, which is the exact drift the rule exists
to prevent, and it is invisible unless someone goes looking.
So: commit index edits on main and push them. Stash or set aside unrelated work first
— never commit an index edit onto a dirty branch — and restore the original branch after:
git rev-parse --abbrev-ref HEAD # note it
git checkout main && git pull
# …edit the entry…
git commit -am "docs(index): <what changed about <project>>" && git push origin main
git checkout - # restore
Observed 2026-07-27: the OtOpcUa Sql poll driver's entry had sat unpushed on
feat/overview-dashboard since 2026-07-24 for exactly this reason, and the Mqtt driver's
entry was about to join it. Both were recovered by cherry-picking onto main (6ab50fe,
591eaac, 7dcd93c) — cherry-pick stranded index commits rather than merging the branch
they are stuck on, since that branch usually carries unrelated in-progress work.
Other workspace projects with a CLAUDE.md (not indexed — promote on request)
Listed so they can be pulled into the index above if you decide they belong.
SCADA/OT, de-indexed (still have a CLAUDE.md under ~/Desktop/):
- OpcUaTestServer — dual-instance OPC UA test server (.NET 10) for testing OPC UA clients / simulating automation.
- scada (ScadaLink) — design docs + scaffolding for the distributed SCADA platform (
ZB.MOM.ScadaLink.*). - scadalink-design-opcua-browser — ScadaBridge variant focused on OPC UA browser / Data Connection Layer work.
- DARS — DARS → Wonderware SCADA migration (design phase, Q2 2026 go-live).
- DARS_BU — backup snapshot of DARS.
- plan — 3-year SCADA IT/OT modernization roadmap (markdown).
Outside the SCADA family:
- delmia — DELMIA / Apriso (Intercim Velocity) MES customization export (ASP.NET
.asmx); MES-adjacent. - lightctrl — Raspberry Pi Python I/O control (edge hardware).
- codestats — Rust CLI for code statistics on .NET solutions/dirs (dev tooling for the .NET repos).
- servecli — Rust portable SSH/SFTP server for Windows remote task management (greenfield).
- JdeScopingTool — JD Edwards "LotFinder" .NET 4.8 → .NET 10 migration (ERP, not SCADA).
- chat — local-first roleplay chat engine.
- candy2 — candy.ai chat scraping / browser automation.
- menardslist — Menards.com cart → printable picklist PDF.