# 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
