Record Phase 0 entry baseline: 820 passing, 2 pre-existing failures (Client.CLI.Tests.SubscribeCommandTests.Execute_PrintsSubscriptionMessage and Tests.MxAccess.MxAccessClientMonitorTests.Monitor_ProbeDataChange_PreventsStaleReconnect), 0 build errors, 167 build warnings. The two failures exist on v2 as of commit 1189dc8 and are unrelated to the rename. Phase 0 exit gate adapts the requirement to "failure count = baseline (2); pass count ≥ baseline (820)".
Branch-naming convention updated in implementation/overview.md and phase-0 doc: cannot use `v2/phase-N-slug` form because git treats `/` as path separator and `v2` already exists as a branch, blocking creation of any `v2/...` branch. Convention is now `phase-N-slug` (no v2/ prefix).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
270 lines
13 KiB
Markdown
270 lines
13 KiB
Markdown
# Phase 0 — Rename to OtOpcUa + .NET 10 Cleanup
|
||
|
||
> **Status**: DRAFT — implementation plan for Phase 0 of the v2 build (`plan.md` §6).
|
||
>
|
||
> **Branch**: `phase-0-rename`
|
||
> **Estimated duration**: 3–5 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 `phase-0-rename` branch:
|
||
|
||
- [ ] `v2` branch is at commit `a59ad2e` or later (decisions #1–125 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
|