Files
lmxopcua/archreview/plans/R2-12-repo-hygiene-plan.md
T
Joseph Doherty 0aebffe800 chore(r2-12): U-6 husk dirs already absent in worktree — no deletion needed (07/U-6)
The 5 Driver.Historian.Wonderware* husks are untracked bin/obj build artifacts
(0 tracked files; absent from slnx). A fresh worktree carries no untracked files,
so there is nothing to rm. Only tracked Wonderware residue is the intentional
retired doc stub + closed-review findings.md (kept). Deviation recorded in plan.
2026-07-13 09:50:21 -04:00

36 KiB
Raw Blame History

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 CONFIRMEDgit 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 CONFIRMEDgit 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 CONFIRMEDgrep -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 CONFIRMEDv2-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):
    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):
    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. 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,399stillpending.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. git rm --cached pending.md current.md          # untrack, keep on disk
    
  2. 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:
    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:

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 buildunit-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):
    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 + xunitxunit.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:
    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:buildunit-testsgreen, nointegration` 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

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.)