Files
lmxopcua/archreview/plans/R2-12-repo-hygiene-plan.md
T
Joseph Doherty 1891f5d6a7 docs(archreview): 2026-07-12 re-review at f6eaa267 + 12 round-2 remediation plans
Re-ran all seven domain reviews at master f6eaa267 (reports rewritten in
place, each with a prior-finding status table): all 4 round-1 Criticals
verified closed; new top findings are the S7 connect-timeout OCE regression
(05/STAB-14), the ResilienceConfig operator-authorable brick (01/S-6), and
a batch of resilience-seam Mediums. 00-OVERALL.md carries the updated
maturity matrix + 12-item action list.

Adds R2-01..R2-12 design/implementation plans (one per action item, house
format + bite-sized TDD task breakdowns + co-located .tasks.json; 193 tasks,
~18-24 dev-days). STATUS.md updated: round-1 topology marked historical
(all merged+pushed), re-review findings table + plan pointers added.
2026-07-12 23:52:23 -04:00

548 lines
35 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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).**