Add data-path ACL design (acl-design.md, closes corrections B1) + dev-environment inventory and setup plan (dev-environment.md), and remove consumer cutover from OtOpcUa v2 scope.

ACL design defines NodePermissions bitmask flags covering Browse / Read / Subscribe / HistoryRead / WriteOperate / WriteTune / WriteConfigure / AlarmRead / AlarmAcknowledge / AlarmConfirm / AlarmShelve / MethodCall plus common bundles (ReadOnly / Operator / Engineer / Admin); 6-level scope hierarchy (Cluster / Namespace / UnsArea / UnsLine / Equipment / Tag) with default-deny + additive grants and Browse-implication on ancestors; per-LDAP-group grants in a new generation-versioned NodeAcl table edited via the same draft → diff → publish → rollback boundary as every other content table; per-session permission-trie evaluator with O(depth × group-count) cost cached for the lifetime of the session and rebuilt on generation-apply or LDAP group cache expiry; cluster-create workflow seeds a default ACL set matching the v1 LmxOpcUa LDAP-role-to-permission map for v1 → v2 consumer migration parity; Admin UI ACL tab with two views (by LDAP group, by scope), bulk-grant flow, and permission simulator that lets operators preview "as user X" effective permissions across the cluster's UNS tree before publishing; explicit Deny deferred to v2.1 since verbose grants suffice at v2.0 fleet sizes; only denied OPC UA operations are audit-logged (not allowed ones — would dwarf the audit log). Schema doc gains the NodeAcl table with cross-cluster invariant enforcement and same-generation FK validation; admin-ui.md gains the ACLs tab; phase-1 doc gains Task E.9 wiring this through Stream E plus a NodeAcl entry in Task B.1's DbContext list.

Dev-environment doc inventories every external resource the v2 build needs across two tiers per decision #99 — inner-loop (in-process simulators on developer machines: SQL Server local or container, GLAuth at C:\publish\glauth\, local dev Galaxy) and integration (one dedicated Windows host with Docker Desktop on WSL2 backend so TwinCAT XAR VM can run in Hyper-V alongside containerized oitc/modbus-server, plus WSL2-hosted Snap7 and ab_server, plus OPC Foundation reference server, plus FOCAS TestStub and FaultShim) — with concrete container images, ports, default dev credentials (clearly marked dev-only since production uses Integrated Security / gMSA per decision #46), bootstrap order for both tiers, network topology diagram, test data seed locations, and operational risks (TwinCAT trial expiry automation, Docker pricing, integration host SPOF mitigation, per-developer GLAuth config sync, Aveva license scoping that keeps Galaxy tests on developer machines and off the shared host).

Removes consumer cutover (ScadaBridge / Ignition / System Platform IO) from OtOpcUa v2 scope per decision #136 — owned by a separate integration / operations team, tracked in 3-year-plan handoff §"Rollout Posture" and corrections §C5; OtOpcUa team's scope ends at Phase 5. Updates implementation/overview.md phase index to drop the "6+" row and add an explicit "OUT of v2 scope" callout; updates phase-1 and phase-2 docs to reframe cutover as integration-team-owned rather than future-phase numbered.

Decisions #129–137 added: ACL model (#129), NodeAcl generation-versioned (#130), v1-compatibility seed (#131), denied-only audit logging (#132), two-tier dev environment (#133), Docker WSL2 backend for TwinCAT VM coexistence (#134), TwinCAT VM centrally managed / Galaxy on dev machines only (#135), cutover out of v2 scope (#136), dev credentials documented openly (#137).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-04-17 11:58:33 -04:00
parent 2a6c9828e4
commit 4903a19ec9
8 changed files with 735 additions and 7 deletions

276
docs/v2/dev-environment.md Normal file
View File

@@ -0,0 +1,276 @@
# Development Environment — OtOpcUa v2
> **Status**: DRAFT — concrete inventory + setup plan for every external resource the v2 build needs. Companion to `test-data-sources.md` (which catalogues the simulator/stub strategy per driver) and `implementation/overview.md` (which references the dev environment in entry-gate checklists).
>
> **Branch**: `v2`
> **Created**: 2026-04-17
## Scope
Every external resource a developer needs on their machine, plus the dedicated integration host that runs the heavier simulators per CI tiering decision #99. Includes Docker container images, ports, default credentials (dev only — production overrides documented), and ownership.
**Not in scope here**: production deployment topology (separate doc when v2 ships), CI pipeline configuration (separate ops concern), individual developer's IDE / editor preferences.
## Two Environment Tiers
Per decision #99:
| Tier | Purpose | Where it runs | Resources |
|------|---------|---------------|-----------|
| **PR-CI / inner-loop dev** | Fast, runs on minimal Windows + Linux build agents and developer laptops | Each developer's machine; CI runners | Pure-managed in-process simulators (NModbus, OPC Foundation reference server, FOCAS TCP stub from test project). No Docker, no VMs. |
| **Nightly / integration CI** | Full driver-stack validation against real wire protocols | One dedicated Windows host with Docker Desktop + Hyper-V + a TwinCAT XAR VM | All Docker simulators (`oitc/modbus-server`, `ab_server`, Snap7), TwinCAT XAR VM, Galaxy.Host installer + dev Galaxy access, FOCAS TCP stub binary, FOCAS FaultShim assembly |
The tier split keeps developer onboarding fast (no Docker required for first build) while concentrating the heavy simulator setup on one machine the team maintains.
## Resource Inventory
### A. Always-required (every developer + integration host)
| Resource | Purpose | Type | Default port | Default credentials | Owner |
|----------|---------|------|--------------|---------------------|-------|
| **.NET 10 SDK** | Build all .NET 10 x64 projects | OS install | n/a | n/a | Developer |
| **.NET Framework 4.8 SDK + targeting pack** | Build `Driver.Galaxy.Host` (Phase 2+) | Windows install | n/a | n/a | Developer |
| **Visual Studio 2022 17.8+ or Rider 2024+** | IDE (any C# IDE works; these are the supported configs) | OS install | n/a | n/a | Developer |
| **Git** | Source control | OS install | n/a | n/a | Developer |
| **PowerShell 7.4+** | Compliance scripts (`phase-N-compliance.ps1`) | OS install | n/a | n/a | Developer |
| **Repo clones** | `lmxopcua` (this repo), `scadalink-design` (UI/auth reference per memory file `scadalink_reference.md`), `3yearplan` (handoff + corrections) | Git clone | n/a | n/a | Developer |
### B. Inner-loop dev (developer machines + PR-CI)
| Resource | Purpose | Type | Default port | Default credentials | Owner |
|----------|---------|------|--------------|---------------------|-------|
| **SQL Server 2022 dev edition** | Central config DB; integration tests against `Configuration` project | Local install OR Docker container `mcr.microsoft.com/mssql/server:2022-latest` | 1433 | `sa` / `OtOpcUaDev_2026!` (dev only — production uses Integrated Security or gMSA per decision #46) | Developer (per machine) |
| **GLAuth (LDAP server)** | Admin UI authentication tests; data-path ACL evaluation tests | Local binary at `C:\publish\glauth\` per existing CLAUDE.md | 3893 (LDAP) / 3894 (LDAPS) | Service principal: `cn=admin,dc=otopcua,dc=local` / `OtOpcUaDev_2026!`; test users defined in GLAuth config | Developer (per machine) |
| **Local dev Galaxy** (Aveva System Platform) | Galaxy driver tests; v1 IntegrationTests parity | Existing on dev box per CLAUDE.md | n/a (local COM) | Windows Auth | Developer (already present per project setup) |
### C. Integration host (one dedicated Windows machine the team shares)
| Resource | Purpose | Type | Default port | Default credentials | Owner |
|----------|---------|------|--------------|---------------------|-------|
| **Docker Desktop for Windows** | Host for containerized simulators | Install | (Hyper-V required; not compatible with TwinCAT runtime — see TwinCAT row below for the workaround) | n/a | Integration host admin |
| **`oitc/modbus-server`** | Modbus TCP simulator (per `test-data-sources.md` §1) | Docker container | 502 (Modbus TCP) | n/a (no auth in protocol) | Integration host admin |
| **`ab_server`** (libplctag binary) | AB CIP + AB Legacy simulator (per `test-data-sources.md` §2 + §3) | Native binary built from libplctag source; runs in a separate VM or host since it conflicts with Docker Desktop's Hyper-V if run on bare metal | 44818 (CIP) | n/a | Integration host admin |
| **Snap7 Server** | S7 simulator (per `test-data-sources.md` §4) | Native binary; runs in a separate VM or in WSL2 to avoid Hyper-V conflict | 102 (ISO-TCP) | n/a | Integration host admin |
| **TwinCAT XAR runtime VM** | TwinCAT ADS testing (per `test-data-sources.md` §5; Beckhoff XAR cannot coexist with Hyper-V on the same OS) | Hyper-V VM with Windows + TwinCAT XAR installed under 7-day renewable trial | 48898 (ADS over TCP) | TwinCAT default route credentials configured per Beckhoff docs | Integration host admin |
| **OPC Foundation reference server** | OPC UA Client driver test source (per `test-data-sources.md` §"OPC UA Client") | Built from `OPCFoundation/UA-.NETStandard` `ConsoleReferenceServer` project | 62541 (default for the reference server) | Anonymous + Username (`user1` / `password1`) per the reference server's built-in user list | Integration host admin |
| **FOCAS TCP stub** (`Driver.Focas.TestStub`) | FOCAS functional testing (per `test-data-sources.md` §6) | Local .NET 10 console app from this repo | 8193 (FOCAS) | n/a | Developer / integration host (run on demand) |
| **FOCAS FaultShim** (`Driver.Focas.FaultShim`) | FOCAS native-fault injection (per `test-data-sources.md` §6) | Test-only native DLL named `Fwlib64.dll`, loaded via DLL search path in the test fixture | n/a (in-process) | n/a | Developer / integration host (test-only) |
### D. Cloud / external services
| Resource | Purpose | Type | Access | Owner |
|----------|---------|------|--------|-------|
| **Gitea** at `gitea.dohertylan.com` | Hosts `lmxopcua`, `3yearplan`, `scadalink-design` repos | HTTPS git | Existing org credentials | Org IT |
| **Anthropic API** (for Codex adversarial reviews) | `/codex:adversarial-review` invocations during exit gates | HTTPS via Codex companion script | API key in developer's `~/.claude/...` config | Developer (per `codex:setup` skill) |
## Network Topology (integration host)
```
┌────────────────────────────────────────┐
│ Integration Host (Windows + Docker) │
│ │
│ Docker Desktop (Linux containers): │
│ ┌───────────────────────────────┐ │
│ │ oitc/modbus-server :502/tcp │ │
│ └───────────────────────────────┘ │
│ │
│ WSL2 (Snap7 + ab_server, separate │
│ from Docker Desktop's HyperV): │
│ ┌───────────────────────────────┐ │
│ │ snap7-server :102/tcp │ │
│ │ ab_server :44818/tcp │ │
│ └───────────────────────────────┘ │
│ │
│ Hyper-V VM (Windows + TwinCAT XAR): │
│ ┌───────────────────────────────┐ │
│ │ TwinCAT XAR :48898 │ │
│ └───────────────────────────────┘ │
│ │
│ Native processes: │
│ ┌───────────────────────────────┐ │
│ │ ConsoleReferenceServer :62541│ │
│ │ FOCAS TestStub :8193│ │
│ └───────────────────────────────┘ │
│ │
│ SQL Server 2022 (local install): │
│ ┌───────────────────────────────┐ │
│ │ OtOpcUaConfig_Test :1433 │ │
│ └───────────────────────────────┘ │
└────────────────────────────────────────┘
│ tests connect via the host's hostname or 127.0.0.1
┌────────────────────────────────────────┐
│ Developer / CI machine running │
│ `dotnet test --filter Category=...` │
└────────────────────────────────────────┘
```
## Bootstrap Order — Inner-loop Developer Machine
Order matters because some installs have prerequisites. ~3060 min total on a fresh machine.
1. **Install .NET 10 SDK** (https://dotnet.microsoft.com/) — required to build anything
2. **Install .NET Framework 4.8 SDK + targeting pack** — only needed when starting Phase 2 (Galaxy.Host); skip for Phase 01 if not yet there
3. **Install Git + PowerShell 7.4+**
4. **Clone repos**:
```powershell
git clone https://gitea.dohertylan.com/dohertj2/lmxopcua.git
git clone https://gitea.dohertylan.com/dohertj2/scadalink-design.git
git clone https://gitea.dohertylan.com/dohertj2/3yearplan.git
```
5. **Install SQL Server 2022 dev edition** (local install) OR start the Docker container (see Resource B):
```powershell
docker run --name otopcua-mssql -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=OtOpcUaDev_2026!" `
-p 1433:1433 -d mcr.microsoft.com/mssql/server:2022-latest
```
6. **Install GLAuth** at `C:\publish\glauth\` per existing CLAUDE.md instructions; populate `glauth-otopcua.cfg` with the test users + groups (template in `docs/v2/dev-environment-glauth-config.md` — to be added in the setup task)
7. **Run `dotnet restore`** in the `lmxopcua` repo
8. **Run `dotnet build ZB.MOM.WW.OtOpcUa.slnx`** (post-Phase-0) or `ZB.MOM.WW.LmxOpcUa.slnx` (pre-Phase-0) — verifies the toolchain
9. **Run `dotnet test`** with the inner-loop filter — should pass on a fresh machine
## Bootstrap Order — Integration Host
Order matters more here because of Hyper-V conflicts. ~half-day on a fresh machine.
1. **Install Windows Server 2022 or Windows 11 Pro** (Hyper-V capable)
2. **Enable Hyper-V** + WSL2
3. **Install Docker Desktop for Windows**, configure to use WSL2 backend (NOT Hyper-V backend — leaves Hyper-V free for the TwinCAT XAR VM)
4. **Set up WSL2 distro** (Ubuntu 22.04 LTS) for native Linux binaries that conflict with Docker Desktop
5. **Pull / start Modbus simulator**:
```powershell
docker run -d --name modbus-sim -p 502:502 -v ${PWD}/modbus-config.yaml:/server_config.yaml oitc/modbus-server
```
6. **Build + start ab_server** (in WSL2):
```bash
git clone https://github.com/libplctag/libplctag
cd libplctag/src/tests
make ab_server
./ab_server --plc=ControlLogix --port=44818 # default tags loaded from a config file
```
7. **Build + start Snap7 Server** (in WSL2):
- Download Snap7 from https://snap7.sourceforge.net/
- Build the example server; run on port 102 with the test DB layout from `test-data-sources.md` §4
8. **Set up TwinCAT XAR VM**:
- Create a Hyper-V VM (Gen 2, Windows 11)
- Install TwinCAT 3 XAE + XAR (download from Beckhoff, free for dev/test)
- Activate the 7-day trial; document the rotation schedule
- Configure ADS routes for the integration host to reach the VM
- Deploy the test PLC project from `test-data-sources.md` §5 ("a tiny test project — `MAIN` (PLC code) + `GVL`")
9. **Build + start OPC Foundation reference server**:
```bash
git clone https://github.com/OPCFoundation/UA-.NETStandard
cd UA-.NETStandard/Applications/ConsoleReferenceServer
dotnet run --port 62541
```
10. **Install SQL Server 2022 dev edition** (or run the Docker container as on developer machines)
11. **Build + run FOCAS TestStub** (from this repo, post-Phase-5):
```powershell
dotnet run --project src/ZB.MOM.WW.OtOpcUa.Driver.Focas.TestStub -- --port 8193
```
12. **Verify** by running `dotnet test --filter Category=Integration` from a developer machine pointed at the integration host
## Credential Management
### Dev environment defaults
The defaults in this doc are **for dev environments only**. They're documented here so a developer can stand up a working setup without hunting; they're not secret.
### Production overrides
For any production deployment:
- SQL Server: Integrated Security with gMSA (decision #46) — never SQL login with shared password
- LDAP: production GLAuth or AD instance with proper service principal
- TwinCAT: paid license (per-runtime), not the 7-day trial
- All other services: deployment-team's credential management process; documented in deployment-guide.md (separate doc, post-v2.0)
### Storage
For dev defaults:
- SQL Server SA password: stored in each developer's local `appsettings.Development.json` (gitignored)
- GLAuth bind DN/password: stored in `glauth-otopcua.cfg` (gitignored)
- Docker secrets / volumes: developer-local
For production:
- gMSA / cert-mapped principals — no passwords stored anywhere
- Per-NodeId credentials in `ClusterNodeCredential` table (per decision #83)
- Admin app uses LDAP (no SQL credential at all on the user-facing side)
## Test Data Seed
Each environment needs a baseline data set so cross-developer tests are reproducible. Lives in `tests/ZB.MOM.WW.OtOpcUa.IntegrationTests/SeedData/`:
- **GLAuth** users: `test-readonly@otopcua.local` (in `OtOpcUaReadOnly`), `test-operator@otopcua.local` (`OtOpcUaWriteOperate` + `OtOpcUaAlarmAck`), `test-fleetadmin@otopcua.local` (`OtOpcUaAdmins`)
- **Central config DB**: a seed cluster `TEST-CLUSTER-01` with 1 node + 1 namespace + 0 drivers (other tests add drivers)
- **Modbus sim**: YAML config preloading the addresses from `test-data-sources.md` §1 (HR 09 constants, ramp at HR 100, etc.)
- **TwinCAT XAR**: the test PLC project deployed; symbols match `test-data-sources.md` §5
- **OPC Foundation reference server**: starts with built-in test address space; tests don't modify it
Seeds are idempotent (re-runnable) and gitignored where they contain credentials.
## Setup Plan (executable)
### Step 1 — Inner-loop dev environment (each developer, ~1 day with documentation)
**Owner**: developer
**Prerequisite**: Bootstrap order steps 19 above
**Acceptance**:
- `dotnet test ZB.MOM.WW.OtOpcUa.slnx` passes
- A test that touches the central config DB succeeds (proves SQL Server reachable)
- A test that authenticates against GLAuth succeeds (proves LDAP reachable)
### Step 2 — Integration host (one-time, ~1 week)
**Owner**: DevOps lead
**Prerequisite**: dedicated Windows machine, hardware specs ≥ 8 cores / 32 GB RAM / 500 GB SSD
**Acceptance**:
- Each simulator (Modbus, AB, S7, TwinCAT, OPC UA reference) responds to a probe from a developer machine
- A nightly CI job runs `dotnet test --filter Category=Integration` against the integration host and passes
- Service-account permissions reviewed by security lead
### Step 3 — TwinCAT XAR VM trial rotation automation (one-time, half-day)
**Owner**: DevOps lead
**Prerequisite**: Step 2 complete
**Acceptance**:
- A scheduled task on the integration host either re-activates the 7-day trial automatically OR alerts the team 24h before expiry; cycle tested
### Step 4 — Per-developer GLAuth config sync (recurring, when test users change)
**Owner**: developer (each)
**Acceptance**:
- A script in the repo (`scripts/sync-glauth-dev-config.ps1`) updates the local GLAuth config from a template; documented in CLAUDE.md
- Test users defined in the template work on every developer machine
### Step 5 — Docker simulator config (per-developer, ~30 min)
**Owner**: developer (each)
**Acceptance**:
- The Modbus simulator container is reachable from `127.0.0.1:502` from the developer's test runner (only needed if the developer is debugging Modbus driver work; not required for Phase 0/1)
### Step 6 — Codex companion setup (per-developer, ~5 min)
**Owner**: developer (each)
**Acceptance**:
- `/codex:setup` skill confirms readiness; `/codex:adversarial-review` works against a small test diff
## Operational Risks
| Risk | Mitigation |
|------|------------|
| TwinCAT 7-day trial expires mid-CI run | Step 3 automation; alert before expiry; license budget approved as fallback for production-grade pre-release validation |
| Docker Desktop license terms change for org use | Track Docker pricing; budget approved or fall back to Podman if license becomes blocking |
| Integration host single point of failure | Document the setup so a second host can be provisioned in <2 days; test fixtures pin to a hostname so failover changes one DNS entry |
| GLAuth dev config drifts between developers | Sync script + template (Step 4) keep configs aligned; periodic review |
| Galaxy / MXAccess licensing for non-dev-machine | Galaxy stays on the dev machines that already have Aveva licenses; integration host does NOT run Galaxy (Galaxy.Host integration tests run on the dev box, not the shared host) |
| Long-lived dev env credentials in dev `appsettings.Development.json` | Gitignored; documented as dev-only; production never uses these |
## Decisions to Add to plan.md
| # | Decision | Rationale |
|---|----------|-----------|
| 133 | Two-tier dev environment: inner-loop (in-process simulators on developer machines) + integration (Docker / VM / native simulators on a single dedicated Windows host) | Per decision #99. Concrete inventory + setup plan in `dev-environment.md` |
| 134 | Docker Desktop with WSL2 backend (not Hyper-V backend) on integration host so TwinCAT XAR VM can run in Hyper-V alongside Docker | TwinCAT runtime cannot coexist with Hyper-V-mode Docker Desktop; WSL2 backend leaves Hyper-V free for the XAR VM. Documented operational constraint |
| 135 | TwinCAT XAR runs only in a dedicated VM on the integration host; developer machines do NOT run XAR locally | The 7-day trial reactivation needs centralized management; the VM is shared infrastructure |
| 136 | Galaxy / MXAccess testing happens on developer machines that have local Aveva installs, NOT on the shared integration host | Aveva licensing scoped to dev workstations; integration host doesn't carry the license. v1 IntegrationTests parity (Phase 2) runs on developer boxes. |
| 137 | Dev env credentials are documented openly in `dev-environment.md`; production credentials use Integrated Security / gMSA per decision #46 | Dev defaults are not secrets; they're convenience. Production never uses these values |