Add Phase 0 + Phase 1 detailed implementation plans under docs/v2/implementation/ with a phase-gate model so the work can be verified for compliance to the v2 design as it lands. Three-gate structure per phase (entry / mid / exit) with explicit compliance-check categories: schema compliance (live DB introspected against config-db-schema.md DDL via xUnit), decision compliance (every decision number cited in the phase doc must have at least one code/test citation in the codebase, verified via git grep), visual compliance (Admin UI screenshots reviewed side-by-side against ScadaLink CentralUI's equivalent screens), behavioral compliance (per-phase end-to-end smoke test that always passes at exit, never "known broken fix later"), stability compliance (cross-cutting protections from driver-stability.md wired up and regression-tested for Tier C drivers), and documentation compliance (any deviation from v2 design docs reflected back as decision-log updates with explicit "supersedes" notes). Exit gate requires two-reviewer signoff and an exit-gate-{phase}.md record; silent deviation is the failure mode the gates exist to make impossible to ship. Phase 0 doc covers the mechanical LmxOpcUa → OtOpcUa rename with 9 tasks, 7 compliance checks, and a completion checklist that gates on baseline test count parity. Phase 1 doc covers the largest greenfield phase — 5 work streams (Core.Abstractions, Configuration project with EF Core schema + stored procs + LiteDB cache + generation-diff applier, Core with GenericDriverNodeManager rename + IAddressSpaceBuilder + driver isolation, Server with Microsoft.Extensions.Hosting replacing TopShelf + credential-bound bootstrap, Admin Blazor Server app mirroring ScadaLink CentralUI verbatim with LDAP cookie auth + draft/diff/publish workflow + UNS structure management + equipment CRUD + release-reservation and merge-equipment operator flows) — with task-level acceptance criteria, a 14-step end-to-end smoke test, and decision citation requirements for #1-125. New decisions #126-127 capture the gate model and per-phase doc structure. Cross-references added to plan.md Reference Documents section.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-04-17 11:25:09 -04:00
parent a59ad2e0c6
commit 592fa79e3c
4 changed files with 1062 additions and 0 deletions

View File

@@ -0,0 +1,269 @@
# Phase 0 — Rename to OtOpcUa + .NET 10 Cleanup
> **Status**: DRAFT — implementation plan for Phase 0 of the v2 build (`plan.md` §6).
>
> **Branch**: `v2/phase-0-rename`
> **Estimated duration**: 35 working days
> **Predecessor**: none (first phase)
> **Successor**: Phase 1 (`phase-1-configuration-and-admin-scaffold.md`)
## Phase Objective
Mechanically rename the existing v1 codebase from `LmxOpcUa` to `OtOpcUa` and verify all existing v1 tests still pass under the new names. **No new functionality**, **no .NET 10 retargeting of `Host` or `Historian.Aveva`** (those move in Phase 2 with the Galaxy split — they need to stay on .NET 4.8 because of MXAccess and Wonderware Historian SDK dependencies). All other projects are already on .NET 10 and stay there.
The phase exists as a clean checkpoint: future PRs reference `OtOpcUa` consistently, the rename is not entangled with semantic changes, and the diff is mechanical enough to review safely.
## Scope — What Changes
| Concern | Change |
|---------|--------|
| Project names | `ZB.MOM.WW.LmxOpcUa.*``ZB.MOM.WW.OtOpcUa.*` (all 11 projects) |
| Solution file | `ZB.MOM.WW.LmxOpcUa.slnx``ZB.MOM.WW.OtOpcUa.slnx` |
| Namespaces | `ZB.MOM.WW.LmxOpcUa` root → `ZB.MOM.WW.OtOpcUa` root (all source files) |
| Assembly names | `<AssemblyName>` and `<RootNamespace>` in every csproj |
| Folder names | `src/ZB.MOM.WW.LmxOpcUa.*``src/ZB.MOM.WW.OtOpcUa.*`; same in `tests/` |
| Default `appsettings.json` keys | `Lmx*``Ot*` only where the section name is product-bound (e.g. `LmxOpcUa.Server``OtOpcUa.Server`); leave `MxAccess.*` keys alone (those refer to the AVEVA product, not ours) |
| Service registration name | TopShelf service name `LmxOpcUa``OtOpcUa` (until Phase 1 swaps TopShelf for `Microsoft.Extensions.Hosting`) |
| Documentation | All `docs/*.md` references; `CLAUDE.md` |
| Repo name | **NOT** in scope for Phase 0 — repo rename happens in a separate ops step after exit gate clears |
## Scope — What Does NOT Change
| Item | Reason |
|------|--------|
| `.NET Framework 4.8` target on `Host` and `Historian.Aveva` | MXAccess COM is 32-bit only; Wonderware Historian SDK is .NET 4.8. Both move to `Galaxy.Host` (still .NET 4.8 x86) in Phase 2. |
| `.NET 10` target on Client.CLI / Client.Shared / Client.UI / all Tests | Already there (verified 2026-04-17 via `grep TargetFramework src/*/*.csproj`). |
| Project structure (no new projects) | New projects (Configuration, Core, Core.Abstractions, Server, Admin) are added in Phase 1, not Phase 0. |
| Galaxy / MXAccess implementation | Stays in `OtOpcUa.Host` for now; Phase 2 splits it into Proxy/Host/Shared. |
| `master` branch / production deployments | Untouched — v2 work all happens on the `v2` branch. |
| OPC UA `ApplicationUri` defaults | Currently include `LmxOpcUa` — leave as-is to avoid breaking existing client trust during v1/v2 coexistence. New `ApplicationUri` defaults land in Phase 1 alongside the cluster model. |
| MxAccess product references in docs / code | "MxAccess" is AVEVA's product name, not ours. Stays. |
## Entry Gate Checklist
Verify all before opening the `v2/phase-0-rename` branch:
- [ ] `v2` branch is at commit `a59ad2e` or later (decisions #1125 captured)
- [ ] `git status` is clean on `v2`
- [ ] `dotnet test ZB.MOM.WW.LmxOpcUa.slnx` passes locally with **zero failing tests**, baseline test count recorded
- [ ] `dotnet build ZB.MOM.WW.LmxOpcUa.slnx` succeeds with zero errors and ≤ baseline warning count
- [ ] All design docs reviewed by the implementation lead: `docs/v2/plan.md`, `docs/v2/config-db-schema.md`, `docs/v2/admin-ui.md`, `docs/v2/driver-specs.md`, `docs/v2/driver-stability.md`, `docs/v2/implementation/overview.md`
- [ ] Decision #9 (rename to OtOpcUa as step 1) re-read and confirmed
- [ ] No other developers have open work on `v2` that would conflict with bulk renames
**Evidence file**: `docs/v2/implementation/entry-gate-phase-0.md` recording date, baseline test count, signoff name.
## Task Breakdown
### Task 0.1 — Inventory references
Generate a complete map of every place `LmxOpcUa` appears:
```bash
grep -rln "LmxOpcUa" --include="*.cs" --include="*.csproj" --include="*.slnx" --include="*.json" --include="*.md" --include="*.razor" .
```
Save the result to `docs/v2/implementation/phase-0-rename-inventory.md` (gitignored after phase completes).
**Acceptance**:
- Inventory file exists, lists every reference grouped by file type
- Reviewer agrees inventory is complete (cross-check against `git grep -i lmx` for case-sensitivity bugs)
### Task 0.2 — Rename project folders
Per project (11 projects total — 5 src + 6 tests):
```bash
git mv src/ZB.MOM.WW.LmxOpcUa.Client.CLI src/ZB.MOM.WW.OtOpcUa.Client.CLI
git mv src/ZB.MOM.WW.OtOpcUa.Client.CLI/ZB.MOM.WW.LmxOpcUa.Client.CLI.csproj \
src/ZB.MOM.WW.OtOpcUa.Client.CLI/ZB.MOM.WW.OtOpcUa.Client.CLI.csproj
```
Repeat for: `Client.Shared`, `Client.UI`, `Historian.Aveva`, `Host`, and all 6 test projects.
Use `git mv` (not `mv` + `git rm`/`git add`) to preserve history.
**Acceptance**:
- `ls src/` shows only `ZB.MOM.WW.OtOpcUa.*` folders
- `ls tests/` shows only `ZB.MOM.WW.OtOpcUa.*` folders
- `git log --follow` on a renamed file shows continuous history pre-rename
### Task 0.3 — Rename solution file
```bash
git mv ZB.MOM.WW.LmxOpcUa.slnx ZB.MOM.WW.OtOpcUa.slnx
```
Edit the `.slnx` to update every project path reference inside it.
**Acceptance**:
- `ZB.MOM.WW.OtOpcUa.slnx` exists and references the renamed project paths
- `dotnet sln list` (or `dotnet build` against the slnx) succeeds
### Task 0.4 — Update csproj contents
For every csproj:
- Update `<AssemblyName>` if explicitly set
- Update `<RootNamespace>` if explicitly set
- Update `<ProjectReference Include=...>` paths for inter-project refs
- Update `<PackageId>` if any project ships as a NuGet (none currently expected, but verify)
**Acceptance**:
- `grep -rl "LmxOpcUa" src/*/*.csproj tests/*/*.csproj` returns empty
- `dotnet restore` succeeds with no missing project references
### Task 0.5 — Bulk-rename namespaces in source files
Run the rename across all `.cs` and `.razor` files:
```bash
grep -rl "ZB.MOM.WW.LmxOpcUa" --include="*.cs" --include="*.razor" . \
| xargs sed -i 's/ZB\.MOM\.WW\.LmxOpcUa/ZB.MOM.WW.OtOpcUa/g'
```
**Acceptance**:
- `grep -rln "ZB.MOM.WW.LmxOpcUa" --include="*.cs" --include="*.razor" .` returns empty
- `dotnet build ZB.MOM.WW.OtOpcUa.slnx` succeeds
### Task 0.6 — Update appsettings.json + service hosting
In `src/ZB.MOM.WW.OtOpcUa.Host/appsettings.json` and equivalents:
- Rename product-named sections: `LmxOpcUa.Server``OtOpcUa.Server` (if present)
- Leave `MxAccess`, `Galaxy`, `Historian` keys untouched (those are external product names)
- Update TopShelf `ServiceName` constant from `LmxOpcUa``OtOpcUa`
**Acceptance**:
- Service install (`dotnet run --project src/.../Host install`) registers as `OtOpcUa`
- Service uninstall + reinstall cycle succeeds on a Windows test box
### Task 0.7 — Update documentation references
- `CLAUDE.md`: replace `LmxOpcUa` references with `OtOpcUa` in product-naming contexts; leave `MxAccess` / `MXAccess` references alone
- `docs/*.md` (existing v1 docs): same pattern
- `docs/v2/*.md`: already uses `OtOpcUa` — verify with grep
**Acceptance**:
- `grep -rln "LmxOpcUa" docs/ CLAUDE.md` returns only references that explicitly need to retain the old name (e.g. historical sections, change log)
- Each retained reference has a comment explaining why
### Task 0.8 — Run full test suite + smoke test
```bash
dotnet build ZB.MOM.WW.OtOpcUa.slnx
dotnet test ZB.MOM.WW.OtOpcUa.slnx
```
Plus manual smoke test of Client.CLI against a running v1 OPC UA server:
```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 2
```
**Acceptance**:
- Test count matches the baseline recorded at entry gate; **zero failing tests**
- Smoke test produces equivalent output to baseline (capture both, diff)
### Task 0.9 — Update build commands in CLAUDE.md
The Build Commands section currently references `ZB.MOM.WW.LmxOpcUa.slnx`. Update to `ZB.MOM.WW.OtOpcUa.slnx`. Also update test paths.
**Acceptance**:
- `cat CLAUDE.md | grep -i lmxopcua` returns only retained-by-design references
- A new developer cloning the repo can follow CLAUDE.md to build + test successfully
## Compliance Checks (run at exit gate)
A `phase-0-compliance.ps1` (or `.sh`) script runs all these and exits non-zero on any failure:
1. **No stale `LmxOpcUa` references**:
```
grep -rln "LmxOpcUa" --include="*.cs" --include="*.csproj" --include="*.slnx" \
--include="*.json" --include="*.razor" . | wc -l
```
Expected: 0 (or only allowlisted retained references)
2. **All projects build**:
```
dotnet build ZB.MOM.WW.OtOpcUa.slnx --warnaserror
```
Expected: success, warning count ≤ baseline
3. **All tests pass**:
```
dotnet test ZB.MOM.WW.OtOpcUa.slnx
```
Expected: total count = baseline, failures = 0
4. **Solution structure matches plan**:
- `ls src/` shows exactly: `ZB.MOM.WW.OtOpcUa.{Client.CLI, Client.Shared, Client.UI, Historian.Aveva, Host}` (5 entries)
- `ls tests/` shows the 6 test projects similarly renamed
- No new projects yet (those land in Phase 1)
5. **.NET targets unchanged**:
- Client projects (CLI/Shared/UI): `net10.0`
- Host + Historian.Aveva: `net48` (split + retarget happens Phase 2)
- All test projects: same targets as their SUT projects
6. **Decision compliance**: this phase implements decision #9 ("Rename to OtOpcUa as step 1"). Verify by:
```
grep -rln "decision #9\|Decision #9" src/ tests/
```
Expected: at least one citation in CLAUDE.md or a phase-rename README explaining the mechanical scope.
7. **Service registration works**:
- Install service → `sc query OtOpcUa` returns the service
- Uninstall service → `sc query OtOpcUa` returns "service does not exist"
## Behavioral Smoke Test (exit-gate gate)
The v1 IntegrationTests suite is the authoritative behavioral spec for Phase 0. The renamed code must pass it identically.
```bash
dotnet test tests/ZB.MOM.WW.OtOpcUa.IntegrationTests --logger "console;verbosity=detailed"
```
Expected: pass count = baseline. Fail count = 0. Skipped count = baseline.
## Completion Checklist
The exit gate signs off only when **every** item below is checked:
- [ ] All 11 projects renamed (5 src + 6 tests)
- [ ] Solution file renamed
- [ ] All `<AssemblyName>` / `<RootNamespace>` / `<ProjectReference>` updated
- [ ] All namespaces in source files updated
- [ ] `appsettings.json` product-named sections updated; external product names untouched
- [ ] TopShelf service name updated; install/uninstall cycle verified on a Windows host
- [ ] `docs/*.md` and `CLAUDE.md` references updated; retained references explained
- [ ] Build succeeds with zero errors and warning count ≤ baseline
- [ ] Test suite passes with zero failures and count = baseline
- [ ] Smoke test against running OPC UA server matches baseline output
- [ ] `phase-0-compliance.ps1` script runs and exits 0
- [ ] Adversarial review of the phase diff (`/codex:adversarial-review --base v2`) — findings closed or deferred with rationale
- [ ] PR opened against `v2`, includes: link to this doc, link to exit-gate record, compliance script output, adversarial review output
- [ ] Reviewer signoff (one reviewer beyond the implementation lead)
- [ ] `exit-gate-phase-0.md` recorded with all of the above
After the PR merges, repo rename (`lmxopcua` → `otopcua` on Gitea) happens as a separate ops step — out of scope for Phase 0.
## Risks and Mitigations
| Risk | Likelihood | Impact | Mitigation |
|------|:----------:|:------:|------------|
| Bulk `sed` rename breaks string literals (e.g. `"LmxOpcUa"` used as a runtime identifier) | Medium | Medium | Inventory step (0.1) flags string literals separately; rename them deliberately, not via bulk sed |
| MxAccess / Galaxy / Wonderware references accidentally renamed | Low | High (breaks COM interop) | Inventory step (0.1) calls out external product names explicitly; bulk rename targets only `ZB.MOM.WW.LmxOpcUa` (with namespace prefix), not bare `LmxOpcUa` |
| Test count drops silently because a test project doesn't get re-discovered | Medium | High | Baseline test count captured at entry gate; exit gate compares exactly |
| `.slnx` references break and projects disappear from solution view | Low | Medium | `dotnet sln list` after Task 0.3 verifies all projects load |
| TopShelf service install fails on a hardened Windows box (UAC, signing) | Low | Low | Manual install/uninstall cycle is part of Task 0.6 acceptance |
| Long-lived branches diverge while phase 0 is in flight | Medium | Low | Phase 0 expected duration ≤ 5 days; coordinate that no other v2 work merges during the phase |
## Out of Scope (do not do in Phase 0)
- Adding any new project (Configuration, Admin, Core, Server, Driver.* — all Phase 1+)
- Splitting Host into Galaxy.Proxy/Host/Shared (Phase 2)
- Migrating Host/Historian.Aveva to .NET 10 (Phase 2 — when Galaxy is split, the .NET 4.8 x86 piece becomes Galaxy.Host and the rest can move)
- Replacing TopShelf with `Microsoft.Extensions.Hosting` (Phase 1, decision #30)
- Implementing the cluster / namespace / equipment data model (Phase 1)
- Changing any OPC UA wire behavior
- Renaming the Gitea repo