# Design + Implementation Plan — R2-12 Repo Hygiene Batch

> **Status (2026-07-12):** PLANNED — nothing executed. This plan documents commands only; the
> destructive steps (git rm of DLLs / planning files / workflow, rm -rf of husk dirs) **must not run
> until their zero-reference proof step passes AND the maintainer signs off** (flagged per task).

- **Source report:** `archreview/07-client-tooling-engineering.md` (2026-07-12 re-review; working-tree version)
- **Overall context:** `archreview/00-OVERALL.md` — prioritized action item **#12** ("Hygiene: delete `lib/` AVEVA DLLs + Wonderware husks, untrack planning files, document 5 CLI commands, E2E workflow or delete it, dedupe CI integration matrix" — Source 07, Effort Small, Impact Medium — license risk + doc trust)
- **Round-1 plan:** `archreview/plans/07-client-tooling-engineering-plan.md` (Tier B/C designs carried forward here)
- **Verified against tree at:** master `f6eaa267` (clean tracked tree; archreview/*.md working-tree updates only)
- **Scope:** `lib/`, retired `Driver.Historian.Wonderware*` disk husks, 5 root planning files, `docs/Client.CLI.md`, `.github/workflows/v2-e2e.yml` + `v2-ci.yml`, `StyleGuide.md` + `.editorconfig`, the 3 xunit 2.9.2 holdouts. Items: **07/U-5, 07/U-6, 07/U-4, 07/U-3, 07/S-3, 07/P-5 (new), 07/C-5 (StyleGuide + editorconfig; the R2-12 brief labels this C-6), 07/C-4**. 07/U-9 is already done (see below).

## Verification summary

Every item was re-verified directly against the tree at `f6eaa267` on 2026-07-12. **All items
CONFIRMED still present except U-9 (already fixed in the working tree).** Exact results:

- **U-5 CONFIRMED** — `git ls-files lib/` returns exactly 7 tracked binaries:
  `lib/ArchestrA.CloudHistorian.Contract.dll`, `lib/ArchestrA.MXAccess.dll`, `lib/Historian.CBE.dll`,
  `lib/Historian.DPAPI.dll`, `lib/aahClient.dll`, `lib/aahClientCommon.dll`, `lib/aahClientManaged.dll`.
  A per-filename grep of every `*.csproj`/`*.props`/`*.targets`/`*.slnx` in the repo returns **zero
  hits for all 7 names**, and there is **not a single `<HintPath>` element anywhere in the repo**
  (`grep -rn "HintPath" --include='*.csproj' --include='*.props' --include='*.targets' .` → empty).
  Zero references, proven.
- **U-6 CONFIRMED** — 5 retired directories exist on disk:
  `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware{,.Client,.Client.Contracts}` and
  `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware{.Tests,.Client.Tests}`. Each contains
  **only `bin/` + `obj/`** (no source, no csproj). `git ls-files` over all 5 paths returns **0 files**
  (they are untracked, gitignored bin/obj husks). `grep -i wonderware ZB.MOM.WW.OtOpcUa.slnx` → no
  match (exit 1); zero `Historian.Wonderware` hits in any csproj. Tracked Wonderware residue is only
  the intentional retired stub `docs/drivers/Historian.Wonderware.md` (keep — CLAUDE.md points at it)
  and 3 `code-reviews/Driver.Historian.Wonderware*/findings.md` (keep — closed-review history).
- **U-4 CONFIRMED** — `git ls-files` shows all 5 tracked at root: `pending.md` (71,575 B, "As of
  2026-06-16"), `current.md` (8,040 B, "Updated 2026-06-13"), `looseends.md` (2,817 B, 2026-05-18),
  `stillpending.md` (22,491 B, reconciled 2026-06-18), `HISTORIAN-GATEWAY-INTEGRATION-ISSUES.md`
  (11,528 B, dated 2026-06-27). `pending.md` line 3 still carries the hard rule "never stage
  `pending.md` / `current.md`". `.gitignore` has **no** entry for any of them (nor for `lib/`).
  One tracked cross-reference exists: `docs/Historian.md:397-399` cites `stillpending.md` by name.
- **U-3 CONFIRMED (count corrected)** — the CLI has **13** commands, not the report's 14:
  `[Command("…")]` attributes in `src/Client/ZB.MOM.WW.OtOpcUa.Client.CLI/Commands/` are exactly
  `ack, alarms, browse, confirm, connect, disable, enable, historyread, read, redundancy, shelve,
  subscribe, write` (13 files, 13 attributes). `docs/Client.CLI.md` has `###` sections for 8:
  connect, read, write, browse, subscribe, historyread, alarms, redundancy. **Missing: `ack`,
  `confirm`, `shelve`, `enable`, `disable`** — the full operator alarm workflow. Options verified
  from source (see item 4).
- **S-3 CONFIRMED** — `grep -rn 'Category.*E2E' tests/ --include='*.cs'` → zero matches (exit 1).
  `v2-e2e.yml` header (lines 6-10) itself admits "this workflow is a green no-op". It also boots the
  docker-dev fleet on a hosted runner — a fleet that per CLAUDE.md binds the **shared GLAuth at
  `10.100.0.35:3893`** (LAN-only) and builds locally-defined images, so it is not
  hosted-runner-bootable even if E2E tests existed.
- **P-5 CONFIRMED** — `v2-ci.yml:77-106`: the `integration` matrix re-restores, re-builds, and
  re-runs `Host.IntegrationTests` + `OpcUaServer.IntegrationTests` under the **identical filter**
  (`Category!=E2E&Category!=LiveIntegration`) the whole-solution `unit-tests` leg (lines 58-62)
  already applies to both projects, fixtureless both times, with a report-only (`MIN_EXECUTED=0`)
  gate whose tally the unit leg's trx already contains. Fully redundant.
- **C-5 CONFIRMED (path corrected)** — the file is at repo root **`StyleGuide.md`** (not
  `docs/StyleGuide.md` as the report cites); line 3 reads "…for all ScadaBridge documentation".
  No `.editorconfig` exists anywhere (`git ls-files '*.editorconfig'` → empty).
- **C-4 CONFIRMED (with a load-bearing wrinkle)** — `Directory.Packages.props:108` pins
  `xunit` 2.9.2; exactly 3 csprojs reference it (`AdminUI.Tests`, `ControlPlane.Tests`,
  `Runtime.Tests`; 44 projects are on `xunit.v3` 1.1.0). **All three holdout csprojs carry an
  explicit comment: `Akka.TestKit.Xunit2` ties them to xunit v2 ("TestKit isn't ported to v3
  yet").** Akka is pinned at 1.5.62. So this is an upstream-blocked migration, not a mechanical
  version alignment — see item 8 for the two-branch plan.
- **U-9 ALREADY DONE — no task.** `archreview/plans/STATUS.md` in the working tree (modified,
  uncommitted, per `git status`) now reads "Branch topology (HISTORICAL — as of 2026-07-08; since
  merged + pushed)" (line 13), carries the "2026-07-12 UPDATE — … merged to master and pushed"
  banner (lines 6-9), and strikes through "Nothing is pushed" (line 116). The stale claims are
  corrected; committing the working-tree archreview updates ships it. **Mark done; do not re-plan.**

---

## Priority ordering

All items are Small; ordering is by risk-class, destructive-first-with-proof:

1. **U-5** — tracked proprietary binaries (license risk on every clone) — the one item with legal weight.
2. **U-4** — tracked planning files violating their own rule (trust/doc drift).
3. **S-3 + P-5** — CI truth fixes (one workflow file deleted, one job deleted).
4. **U-3** — operator-facing doc gap.
5. **C-5 + editorconfig** — trivial polish.
6. **C-4** — conditional on upstream; smallest honest fix if blocked.
7. **U-6** — untracked disk-only cleanup (no commit at all).

---

## 1. 07/U-5 — 7 orphaned proprietary AVEVA DLLs tracked in `lib/`

**Restatement:** 7 committed AVEVA/Wonderware vendor binaries sit in `lib/`, referenced by no
project — a redistribution/licence risk shipped in every clone. Leftovers from the retired
in-process MXAccess / Wonderware-sidecar era; the COM/bitness story now lives entirely in the
sibling `mxaccessgw` repo.

**Verification:** Confirmed (see summary). Zero-reference proof already executed read-only:
per-filename grep over all msbuild files + slnx = 0 hits; zero `HintPath` elements repo-wide.

**Proposed action — DELETE (`git rm -r lib/`) + gitignore guard.** There is no keep case: nothing
compiles against them, the gateway repo owns the MXAccess surface, and the binaries remain
retrievable from git history or the AVEVA SDK if ever needed. Add `lib/` to `.gitignore` to block
re-introduction. **Explicitly out of scope:** purging them from *history* (BFG/filter-repo) is a
separate, heavier decision that rewrites every clone; the immediate licence-hygiene win is removing
them from the tip. Note this in the commit message.

> **⚠️ MAINTAINER SIGN-OFF REQUIRED before the `git rm` runs.** The executor must re-run the proof
> (step 1) and show its empty output; delete only after the proof passes and the maintainer approves.

**Steps (exact commands):**

1. **Proof (must pass first — expect zero output from both):**
   ```bash
   for dll in aahClient.dll aahClientCommon.dll aahClientManaged.dll \
              ArchestrA.CloudHistorian.Contract.dll ArchestrA.MXAccess.dll \
              Historian.CBE.dll Historian.DPAPI.dll; do
     grep -rln "$dll" --include='*.csproj' --include='*.props' --include='*.targets' --include='*.slnx' .
   done
   grep -rn "HintPath" --include='*.csproj' --include='*.props' --include='*.targets' .
   ```
   Expected: no output (verified 2026-07-12: no output).
2. `git rm -r lib/` — expect `rm 'lib/…'` × 7.
3. Append to `.gitignore` under a new `# Vendor binaries — never commit (license risk; see R2-12/U-5)` header: `lib/`
4. `dotnet build ZB.MOM.WW.OtOpcUa.slnx` — must succeed (proves nothing linked them).
5. Commit (see Task breakdown, commit 1).
6. Verify: `git ls-files lib/` → empty; `git status` clean.

**Effort:** Trivial. **Risk:** Low — zero-reference proven; blast radius nil at the tip. The only
residual is history retention (documented as out of scope).

---

## 2. 07/U-6 — retired `Historian.Wonderware*` husk directories on disk

**Restatement:** 5 retired `Driver.Historian.Wonderware*` directories linger under `src/Drivers/` +
`tests/Drivers/` after the HistorianGateway cutover retired the Wonderware backend.

**Verification:** Confirmed — but they are **untracked, gitignored `bin/`+`obj/` husks only**
(0 tracked files across all 5; absent from the slnx; zero csproj references). This is a disk-only
cleanup: **no git command, no commit** results from it.

**Proposed action — plain `rm -rf` of the 5 directories.** Keep the two classes of *tracked*
Wonderware residue: `docs/drivers/Historian.Wonderware.md` (deliberate retired stub, referenced by
CLAUDE.md) and the 3 `code-reviews/Driver.Historian.Wonderware*/findings.md` (closed-review
history). The scope brief's "verify absent from slnx + zero project references, then delete" is
satisfied: the projects were properly `git rm`'d in the retirement PR; only ignored build output
survived on this machine.

> **⚠️ Sign-off note:** destructive to local disk only (build artifacts). Proof step must still show
> "0 tracked files" before `rm -rf` runs.

**Steps (exact commands):**

1. **Proof (expect `0` and no slnx match):**
   ```bash
   git ls-files src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware* \
                tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware* | wc -l   # → 0
   grep -i wonderware ZB.MOM.WW.OtOpcUa.slnx                                           # → exit 1
   ```
2. ```bash
   rm -rf src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware \
          src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client \
          src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Contracts \
          tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Tests \
          tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Tests
   ```
3. Verify: `find src tests -maxdepth 3 -type d -iname '*Wonderware*'` → empty;
   `git status` **unchanged** by the deletion (they were untracked).

**Effort:** Trivial. **Risk:** None (ignored build output; regenerable would-be, except the
projects no longer exist to regenerate it — which is the point).

---

## 3. 07/U-4 — tracked root planning files contradict their own "never stage" rule

**Restatement:** 5 point-in-time planning/working-notes files are committed at root; `pending.md:3`
itself declares the hard rule "never stage `pending.md` / `current.md`". All are stale snapshots
that now contradict `docs/` + CLAUDE.md (e.g. `pending.md` is "as of 2026-06-16", ~25 commits and
one whole backend cutover ago).

**Verification:** Confirmed — all 5 tracked; no `.gitignore` entries; the one inbound tracked
reference is `docs/Historian.md:397,399` → `stillpending.md`.

**Proposed action — per-file (contents were read; recommendations follow from content):**

| File | Content finding | Recommendation | Rationale |
|---|---|---|---|
| `pending.md` | Active session-notes ledger (71 KB); carries the "never stage" hard rule itself | **Untrack, keep locally:** `git rm --cached pending.md` + gitignore | Makes the file's own rule true without destroying live working notes |
| `current.md` | Galaxy standard-driver status "Updated 2026-06-13" — that effort fully shipped (Phases A/B/C on master; recorded in CLAUDE.md) | **Untrack, keep locally:** `git rm --cached current.md` + gitignore | Named by the same hard rule; content stale but it's a working-notes file by declared convention |
| `looseends.md` | 2026-05-18 snapshot; sole open item "#20 dev-rig rollout smoke" targets the **retired Wonderware sidecar rig** — moot since the gateway cutover | **Delete outright:** `git rm looseends.md` | Fully superseded; its one open item is against infrastructure that no longer exists |
| `stillpending.md` | The reconciled 2026-06-18 backlog audit (~95% shipped); **referenced by tracked `docs/Historian.md:397-399`** and by the memory index | **Archive-move:** `git mv stillpending.md docs/plans/2026-06-18-stillpending-backlog-audit.md` + update the `docs/Historian.md` reference | Referenced history — preserve under the established dated-plan convention rather than delete |
| `HISTORIAN-GATEWAY-INTEGRATION-ISSUES.md` | 2026-06-27 live-smoke findings that drove the provisioning-dormancy fix (file/line refs pinned to `245316d8`) | **Archive-move:** `git mv HISTORIAN-GATEWAY-INTEGRATION-ISSUES.md docs/plans/2026-06-27-historian-gateway-integration-issues.md` | Referenced investigative history (memory index cites it); dated-plan archive fits |

> **⚠️ MAINTAINER SIGN-OFF REQUIRED for the `looseends.md` deletion** (only truly destructive step —
> `git rm --cached` keeps local copies, `git mv` keeps content). The executor must show the
> maintainer this table before running step 2.

**Steps (exact commands):**

1. ```bash
   git rm --cached pending.md current.md          # untrack, keep on disk
   ```
2. ```bash
   git rm looseends.md                            # delete (sign-off required)
   git mv stillpending.md docs/plans/2026-06-18-stillpending-backlog-audit.md
   git mv HISTORIAN-GATEWAY-INTEGRATION-ISSUES.md docs/plans/2026-06-27-historian-gateway-integration-issues.md
   ```
3. Append to `.gitignore` under `# Session working notes — never stage (pending.md's own hard rule)`:
   ```
   /pending.md
   /current.md
   ```
   (root-anchored so a future `docs/plans/*pending*` archive is not swallowed).
4. Edit `docs/Historian.md:397-399`: repoint the two `stillpending.md` mentions at
   `docs/plans/2026-06-18-stillpending-backlog-audit.md`.
5. Verify:
   ```bash
   git ls-files | grep -E '^(pending|current|looseends|stillpending|HISTORIAN)'   # → empty
   git status --short | grep -E 'pending|current'                                # → nothing staged; files still on disk
   git grep -n 'stillpending\.md' -- docs/ CLAUDE.md                             # → only the archived path
   ```

**Effort:** Small. **Risk:** Low — pure VCS hygiene; `--cached` + `mv` preserve all content; the one
deletion is a 2.8 KB fully-superseded snapshot recoverable from history.

---

## 4. 07/U-3 — `docs/Client.CLI.md` documents 8 of 13 commands

**Restatement:** the five operator alarm commands (`ack`, `confirm`, `shelve`, `enable`, `disable`)
ship in the CLI but have zero sections in `docs/Client.CLI.md`, while CLAUDE.md points operators at
that doc. (Correction to the report: the actual command set is **13**, not 14 — verified by
enumerating `[Command("…")]` attributes; 8 documented + 5 missing = 13.)

**Verification:** Confirmed. Enumerated command set vs `###` headings (see summary). Options
extracted from the command sources:

| Command | Options (from source) |
|---|---|
| `ack` (`AcknowledgeCommand.cs`) | `--node/-n` condition NodeId (required) · `--event-id/-e` hex EventId (required) · `--comment/-c` |
| `confirm` (`ConfirmCommand.cs`) | `--node/-n` (required) · `--event-id/-e` (required) · `--comment/-c` — two-stage ack |
| `shelve` (`ShelveCommand.cs`) | `--node/-n` (required) · `--kind/-k` `OneShot \| Timed \| Unshelve` (required) · `--duration/-d` seconds (required for `Timed`; converted to ms for the OPC UA call) |
| `enable` (`EnableCommand.cs`) | `--node/-n` (required) |
| `disable` (`DisableCommand.cs`) | `--node/-n` (required) |

All five inherit `CommandBase` connection options (`-u`, auth, security, failover) like the
documented commands.

**Proposed action — exactly these docs sections to add** to `docs/Client.CLI.md`, inserted after the
existing `### alarms` section (they are its operator-action counterparts) and before `### redundancy`:

1. A grouping intro `### Alarm operator commands (ack / confirm / shelve / enable / disable)` **or**
   five flat `###` sections mirroring the existing per-command shape — prefer **five flat `###`
   sections** (`### ack`, `### confirm`, `### shelve`, `### enable`, `### disable`) to match the
   established one-heading-per-command structure, each with: synopsis line, options table, one
   example invocation, expected output. Note in each that the server gates these on the **AlarmAck**
   LDAP role (per CLAUDE.md §Scripted Alarm Ack/Shelve) and that `--event-id` comes from a prior
   `alarms` subscription notification.
2. Cross-check terminology against `docs/ScriptedAlarms.md` §"Inbound operator ack/shelve".
3. Scan the doc's Overview/intro for any stale command-count claim ("8 commands"/"14 commands") and
   correct to 13.

**Steps:** edit `docs/Client.CLI.md`; verify each command's documented options against
`dotnet run --project src/Client/ZB.MOM.WW.OtOpcUa.Client.CLI -- <cmd> --help` output (or the
source attributes above, if not running the CLI).

**Effort:** Small. **Risk:** None (docs only).

---

## 5. 07/S-3 — `v2-e2e.yml` gates zero tests (permanent green no-op)

**Restatement:** the nightly workflow boots the docker-dev fleet then runs
`dotnet test --filter "Category=E2E"` — a filter that matches zero tests in the tree — and reports
green daily, training people to ignore the badge.

**Verification:** Confirmed — zero `Category=E2E` traits repo-wide (`grep -rn 'Category.*E2E'
tests/ --include='*.cs'` → exit 1); the workflow header self-documents the no-op.

**Proposed action — DELETE the workflow (recommended), not seed a test.** Rationale:

- **The fleet cannot boot on a hosted runner anyway.** `docker-dev/docker-compose.yml` binds the
  shared GLAuth at `10.100.0.35:3893` (LAN-only, per CLAUDE.md) and builds locally-defined
  `otopcua-*` images — so even a seeded E2E test would fail-or-flake at the "Build docker-dev
  fleet" / "Wait for cluster" steps on GitHub/Gitea hosted runners. Seeding one real E2E test does
  **not** make this workflow honest; it makes it red-for-infrastructure-reasons, which is worse
  than absent.
- **The false-green cost is live today**; deletion removes it immediately at zero effort.
- **Nothing of value is lost.** The whole-solution CI leg (07/S-1, shipped) genuinely gates all 47
  test projects; the `scripts/e2e/test-*.ps1` harnesses (the real E2E assets) are untouched. When
  a real `E2ETests` project lands (the round-1 plan's option (b), still the right follow-on), it
  should arrive **with its own workflow** including a zero-match guard (fail if `Category=E2E`
  matches nothing) and a runner that can actually reach a fleet — a fresh design, not this husk.
- Alternative (round-1 option (a): keep the file, comment out `schedule:`, add a zero-match guard)
  is acceptable if the maintainer wants the file as a placeholder — but a placeholder that cannot
  run on its own runner documents an intention better served by one line in a tracked follow-up.

> **⚠️ Maintainer sign-off:** deleting a workflow file — confirm the nightly's absence won't be
> mistaken for a CI regression (it will simply stop appearing in Actions).

**Steps:**
```bash
git rm .github/workflows/v2-e2e.yml
```
Record the follow-up ("land tests/Server/…E2ETests + a new workflow with a zero-match guard when a
runner-reachable fleet exists") in the commit message and in `archreview/plans/STATUS.md`'s open
list. Also remove the now-dangling pointer in `v2-ci.yml`'s header comment (line 7-8: "Skips E2E …
that runs nightly via v2-e2e.yml") — fold into the P-5 edit below.

**Effort:** Trivial. **Risk:** Low — removes a job that has never executed a test.

---

## 6. 07/P-5 (new) — CI integration matrix fully redundant with the unit leg

**Restatement:** the `integration` matrix job (`v2-ci.yml:77-106`) re-restores, re-builds, and
re-runs `Host.IntegrationTests` + `OpcUaServer.IntegrationTests` under the identical filter the
whole-solution `unit-tests` leg already applies to the same two projects, fixtureless both times.
Its only unique output is a per-project skip tally the unit leg's trx already contains. (Note the
projects are not pure-skip — the in-process 2-node Akka harness tests, e.g. `HardKillFailoverTests`,
genuinely execute — but they execute in **both** jobs.)

**Verification:** Confirmed at `v2-ci.yml:58-62` (unit leg, whole slnx, same filter) vs `:77-106`
(matrix, same filter, `MIN_EXECUTED=0` report-only gate).

**Proposed action — DELETE the `integration` job (recommended).** The report offers two options:
delete, or make it earn its keep by adding the `mcr.microsoft.com/iotedge/opc-plc` `services:`
block + strict gate (S-2 option #2). Recommend **delete** for this batch because:

- The matrixed projects don't consume `OPCUA_SIM_ENDPOINT` — the opc-plc fixture serves the
  OpcUaClient *driver* integration suite, which runs in the unit leg, not in this matrix. Making
  this specific matrix "earn its keep" therefore means redesigning *which* projects it runs — a
  real S-2-completion work item, not a dedupe.
- Deletion halves the remaining CI restore/build waste for free (per 07/P-1) and removes a
  duplicate execution of the slowest harness tests on every push.
- The S-2 option-#2 follow-up (a fixture-backed leg with a strict gate, covering something the unit
  leg cannot) stays tracked in the workflow's own comment lineage — preserve it in the commit
  message and STATUS.md rather than in dead YAML.

**Steps:**
1. Edit `.github/workflows/v2-ci.yml`: delete the entire `integration:` job (lines 77-106) and the
   header-comment line describing it (line 5), and update the header's E2E pointer (line 7-8) per
   S-3 above. Keep `build` and `unit-tests` untouched.
2. Verify YAML: `python3 -c "import yaml,sys; yaml.safe_load(open('.github/workflows/v2-ci.yml'))"`
   (or `yq`), then confirm on the first post-push run that `build` → `unit-tests` still gate.

**Effort:** Trivial. **Risk:** Low — no coverage is lost (both projects still run in the unit leg
under the strict `MIN_EXECUTED=1` gate); only duplicate execution is removed.

---

## 7. 07/C-5 — `StyleGuide.md` says "ScadaBridge"; no repo `.editorconfig`

*(The R2-12 brief labels this C-6; in the source report the StyleGuide + editorconfig finding is
**C-5** — C-6 is csproj-boilerplate dedup, out of scope here.)*

**Restatement:** the documentation style guide still carries the sister repo's product name; there
is no mechanical code-style anchor at all.

**Verification:** Confirmed — the file is at repo root **`StyleGuide.md`** (the report's
`docs/StyleGuide.md` path is wrong; noted for the next report pass), line 3: "This guide defines
writing conventions and formatting rules for all ScadaBridge documentation." No `.editorconfig`
tracked anywhere.

**Proposed action — the minimal fix, per the brief:**
1. `StyleGuide.md`: replace "ScadaBridge" → "OtOpcUa" on line 3, then
   `grep -n "ScadaBridge" StyleGuide.md` and fix any further hits (verified: line 3 is in the head;
   sweep the whole file at execution time).
2. Add a **minimal root `.editorconfig`** codifying the already-consistent conventions at
   non-error severity (documentation-of-convention, not a reformat mandate — no `dotnet format`
   CI step in this batch):
   ```ini
   root = true

   [*]
   charset = utf-8
   insert_final_newline = true
   indent_style = space

   [*.{cs,csproj,props,targets}]
   indent_size = 4

   [*.cs]
   csharp_style_namespace_declarations = file_scoped:suggestion
   dotnet_naming_rule.private_fields_underscore.severity = suggestion
   dotnet_naming_rule.private_fields_underscore.symbols = private_fields
   dotnet_naming_rule.private_fields_underscore.style = underscore_camel
   dotnet_naming_symbols.private_fields.applicable_kinds = field
   dotnet_naming_symbols.private_fields.applicable_accessibilities = private
   dotnet_naming_style.underscore_camel.capitalization = camel_case
   dotnet_naming_style.underscore_camel.required_prefix = _

   [*.{yml,yaml,json}]
   indent_size = 2
   ```
3. Verify: `dotnet build ZB.MOM.WW.OtOpcUa.slnx` — the build must be **warning-delta-free** (all
   severities `suggestion`; if any new warning appears in a `TreatWarningsAsErrors` project, drop
   that rule to `suggestion` — nothing in this batch may fail a TWE build).

**Effort:** Trivial. **Risk:** Low — the one hazard is `.editorconfig` rules surfacing as warnings
in TWE'd projects; mitigated by suggestion-only severities + the build check.

---

## 8. 07/C-4 — 3 xunit 2.9.2 holdout projects

**Restatement:** `AdminUI.Tests`, `ControlPlane.Tests`, `Runtime.Tests` still reference `xunit`
2.9.2 while 44 projects are on `xunit.v3` — a mixed v2/v3 population as a moving part in the new
whole-solution CI leg.

**Verification:** Confirmed — but with a decisive wrinkle the report undersells: **all three csprojs
carry an explicit comment that `Akka.TestKit.Xunit2` pins them to xunit v2** ("TestKit isn't ported
to v3 yet"). Akka is pinned at 1.5.62. The holdouts are not drift; they are a documented upstream
block, discoverable only at the csproj level today.

**Proposed action — two-branch, decided by a feed probe (exact commands below):**

- **Branch A (unblocked):** if an xunit-v3-compatible Akka TestKit now exists at/near Akka 1.5.62
  (candidate IDs: `Akka.TestKit.Xunit3`, `Akka.TestKit.Xunit.v3`), migrate all 3 projects:
  swap `Akka.TestKit.Xunit2`→the v3 package + `xunit`→`xunit.v3` in each csproj, fix any
  `Akka.TestKit.Xunit2` namespace usings, run each suite, then **delete the `xunit` 2.9.2 pin**
  from `Directory.Packages.props:108`. Effort M if taken — spill to a follow-up PR if test churn
  exceeds trivial.
- **Branch B (still blocked — expected):** do the honest CPM alignment instead: (1) bump the pin to
  the latest 2.9.x patch (`2.9.3`) if the feed has it; (2) **add the blocker rationale as a comment
  in `Directory.Packages.props`** next to line 108, house-style (mirroring the CVE-suppress
  carve-out): `<!-- xunit v2 pin: AdminUI/ControlPlane/Runtime.Tests are held on v2 by
  Akka.TestKit.Xunit2 (no v3 TestKit as of Akka 1.5.62). Remove when Akka ships an xunit.v3
  TestKit and the 3 holdouts migrate. -->` — so the removal condition lives at the pin, not just in
  3 scattered csprojs.

**Steps:**
1. Probe:
   ```bash
   for id in Akka.TestKit.Xunit3 Akka.TestKit.Xunit.v3; do
     curl -s "https://api.nuget.org/v3-flatcontainer/$(echo $id | tr 'A-Z' 'a-z')/index.json" | head -c 400; echo;
   done
   curl -s "https://api.nuget.org/v3-flatcontainer/xunit/index.json" | python3 -c "import json,sys; print([v for v in json.load(sys.stdin)['versions'] if v.startswith('2.9')])"
   ```
2. Take Branch A or B accordingly.
3. Verify (either branch): `dotnet test tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests
   tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests`
   all green; whole-solution build clean.

**Effort:** Small (Branch B) / Medium (Branch A — cap it: if migration churn is non-trivial, ship
Branch B now and file the migration as a follow-up). **Risk:** Low (B) / Medium (A — TestKit swap
touches Akka actor tests; run all three suites).

---

## 9. 07/U-9 — STATUS.md stale "nothing pushed" claim — **ALREADY DONE**

Fixed in the working tree on 2026-07-12 (verified: topology header marked HISTORICAL, merge/push
banner added, resume-facts struck through and superseded). No task in this plan; it ships when the
working-tree archreview updates are committed. **Do not re-plan.**

---

## Task breakdown

Execution note: **T1/T3a/T5a/T9a are the zero-reference/probe proof gates — each destructive task is
blocked on its proof AND on maintainer sign-off.** Suggested commit grouping: 4 commits (removals /
planning files / CI workflows / docs+style) + 1 conditional (xunit).

### T1 — U-5 proof: zero references to the 7 `lib/` DLLs
**Classification:** trivial · **Estimated implement time:** 5 min · **Parallelizable with:** T3a, T5a, T6-pre, T9a · **Files:** none (read-only)
Run the two greps from item 1 step 1. Expected output: **empty** (verified empty 2026-07-12).
Then `dotnet build ZB.MOM.WW.OtOpcUa.slnx` → success. Present the empty output to the maintainer
for sign-off on T2.

### T2 — U-5: `git rm -r lib/` + gitignore guard  *(blocked by T1 + sign-off)*
**Classification:** trivial · **Estimated implement time:** 5 min · **Parallelizable with:** none (commit 1) · **Files:** `lib/*` (delete ×7), `.gitignore`
`git rm -r lib/` (expect 7 `rm` lines) · append `lib/` to `.gitignore` · rebuild →
**Commit 1:** `chore(hygiene): remove 7 orphaned proprietary AVEVA DLLs from lib/ (07/U-5)` — note
zero-reference proof + history-purge-out-of-scope in the body. Verify `git ls-files lib/` → empty.

### T3a — U-6 proof: husk dirs are untracked and slnx-absent
**Classification:** trivial · **Estimated implement time:** 2 min · **Parallelizable with:** T1, T5a, T9a · **Files:** none (read-only)
Run item 2 step 1. Expected: `0` tracked files; slnx grep exit 1 (verified 2026-07-12).

### T3b — U-6: delete the 5 Wonderware husk directories  *(blocked by T3a)*
**Classification:** trivial · **Estimated implement time:** 2 min · **Parallelizable with:** any (no git change) · **Files:** the 5 untracked dirs (disk only)
`rm -rf` per item 2 step 2. Verify `find src tests -maxdepth 3 -type d -iname '*Wonderware*'` →
empty and `git status` unchanged. **No commit.**

### T5a — U-4 proof + per-file confirmation
**Classification:** trivial · **Estimated implement time:** 5 min · **Parallelizable with:** T1, T3a, T9a · **Files:** none (read-only)
`git ls-files | grep -E '^(pending|current|looseends|stillpending|HISTORIAN)'` → the 5 files
(verified). Re-read `looseends.md` to confirm nothing new was added since 2026-05-18; show the
maintainer the item-3 recommendation table for sign-off (especially the `looseends.md` delete).

### T5b — U-4: untrack / archive / delete + gitignore + doc repoint  *(blocked by T5a + sign-off)*
**Classification:** small · **Estimated implement time:** 15 min · **Parallelizable with:** T6/T7 (different files) · **Files:** the 5 root files, `.gitignore`, `docs/Historian.md`
Run item 3 steps 1-4 exactly. **Commit 2:** `chore(hygiene): untrack/archive root planning files;
honor pending.md's never-stage rule (07/U-4)`. Verify per item 3 step 5.

### T6 — S-3: delete `v2-e2e.yml`  *(sign-off required)*
**Classification:** trivial · **Estimated implement time:** 5 min · **Parallelizable with:** T5b, T8, T9b · **Files:** `.github/workflows/v2-e2e.yml` (delete)
Pre-proof: `grep -rn 'Category.*E2E' tests/ --include='*.cs'` → exit 1 (verified). Then
`git rm .github/workflows/v2-e2e.yml`. Folded into **Commit 3** with T7. Record the
E2ETests-with-zero-match-guard follow-up in the commit body + STATUS.md open list.

### T7 — P-5: remove the redundant `integration` matrix job from `v2-ci.yml`  *(pairs with T6)*
**Classification:** small · **Estimated implement time:** 10 min · **Parallelizable with:** T5b, T8, T9b · **Files:** `.github/workflows/v2-ci.yml`
Delete the `integration:` job (`v2-ci.yml:77-106`) + header lines referencing it and the deleted
nightly (lines 5, 7-8). YAML-validate. **Commit 3:** `ci(hygiene): delete no-op nightly E2E workflow
+ redundant integration matrix (07/S-3, 07/P-5)` — body notes the S-2 option-#2 fixture-leg
follow-up. Verify on first post-push run: `build` → `unit-tests` green, no `integration` job.

### T8 — U-3: add the 5 missing CLI command sections
**Classification:** small · **Estimated implement time:** 30 min · **Parallelizable with:** T6, T7, T9b, T10 · **Files:** `docs/Client.CLI.md`
Add `### ack`, `### confirm`, `### shelve`, `### enable`, `### disable` after `### alarms`, per the
item-4 options table (synopsis + options + example + AlarmAck-role note + event-id-from-alarms
note); fix any stale command-count claim to 13. Cross-check `docs/ScriptedAlarms.md` terminology.
Part of **Commit 4**.

### T9a — C-5 proof: ScadaBridge sweep + editorconfig absence
**Classification:** trivial · **Estimated implement time:** 2 min · **Parallelizable with:** T1, T3a, T5a · **Files:** none (read-only)
`grep -n "ScadaBridge" StyleGuide.md` (expect line 3; note any others) ·
`git ls-files '*.editorconfig'` → empty (verified).

### T9b — C-5: StyleGuide fix + minimal `.editorconfig`  *(blocked by T9a)*
**Classification:** trivial · **Estimated implement time:** 15 min · **Parallelizable with:** T8, T10 · **Files:** `StyleGuide.md`, new `.editorconfig`
Apply item 7 steps 1-2; rebuild and confirm zero new warnings in TWE'd projects. With T8 as
**Commit 4:** `docs(hygiene): document 5 alarm operator CLI commands; fix StyleGuide product name;
add minimal .editorconfig (07/U-3, 07/C-5)`.

### T10 — C-4: xunit holdouts — probe, then migrate or document the pin
**Classification:** small (Branch B) / medium (Branch A) · **Estimated implement time:** 20 min (B) / 2 h (A) · **Parallelizable with:** T8, T9b · **Files:** `Directory.Packages.props`; Branch A also the 3 holdout csprojs + their test sources
Run the item-8 probe. Branch B (expected): bump pin to latest 2.9.x + add the blocker comment at
`Directory.Packages.props:108`; run the 3 suites. Branch A: migrate per item 8 (cap effort; spill
to a follow-up if churn is non-trivial). **Commit 5:** `test(hygiene): document/align the xunit v2
holdout pin (07/C-4)` (or the migration equivalent). Verify: 3 suites green + whole-solution build.

### T11 — Final verification pass  *(blocked by all above)*
**Classification:** small · **Estimated implement time:** 15 min · **Parallelizable with:** none
```bash
dotnet build ZB.MOM.WW.OtOpcUa.slnx
dotnet test ZB.MOM.WW.OtOpcUa.slnx --filter "Category!=E2E&Category!=LiveIntegration"
git ls-files lib/                                    # empty
git ls-files | grep -E '^(pending|current|looseends|stillpending|HISTORIAN)'   # empty
find src tests -maxdepth 3 -type d -iname '*Wonderware*'                        # empty
ls .github/workflows/                                # v2-ci.yml only (of the two)
git log --oneline -6                                 # the 4-5 hygiene commits
```
Expected: build + tests green, all hygiene assertions empty/clean. Update
`archreview/plans/STATUS.md` marking R2-12 items done (U-9 was already marked).

**Overall effort: Small (~half a day; +up to 2 h only if C-4 Branch A opens).**

---

## Execution deviations (R2-12)

Recorded during execution on branch `r2/12-repo-hygiene` (off master `1676c8f4`).

- **T3b (U-6, Wonderware husk dirs) — nothing to delete in this worktree; end state already correct.**
  The 5 `Driver.Historian.Wonderware*` husk directories the plan targets are untracked
  `bin/`+`obj/` build artifacts that lived in the *main checkout*. A fresh git worktree does not
  carry untracked files, so `find . -type d -iname '*Wonderware*'` here returns only the tracked
  `code-reviews/Driver.Historian.Wonderware{,.Client,.Client.Contracts}/findings.md` history (KEEP,
  per plan). Proof: `git ls-files src/…/Wonderware* tests/…/Wonderware*` → 0; slnx grep → exit 1;
  no husk dirs on disk. No `rm -rf` was needed and no commit results. (The husks, if present in the
  main checkout, are harmless ignored build output and will be reaped by a normal `git clean`/rebuild
  there; they were never in VCS.)
