# 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 `` 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 -- --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): `` — 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).**