7.0 KiB
Phase 2 Final Exit Gate (2026-04-18)
Supersedes
phase-2-partial-exit-evidence.mdandexit-gate-phase-2.md. Captures the as-built state at the close of Phase 2 work delivered across two PRs.
Status: All five Phase 2 streams addressed. Stream D split across PR 2 (archive) + PR 3 (delete) per safety protocol.
Stream-by-stream status
| Stream | Plan §reference | Status | PR |
|---|---|---|---|
| A — Driver.Galaxy.Shared | §A.1–A.3 | ✅ Complete | PR 1 (merged or pending) |
| B — Driver.Galaxy.Host | §B.1–B.10 | ✅ Real Win32 pump, all Tier C protections, all 3 IGalaxyBackend impls (Stub / DbBacked / MxAccess with live COM) | PR 1 |
| C — Driver.Galaxy.Proxy | §C.1–C.4 | ✅ All 9 capability interfaces + supervisor (Backoff + CircuitBreaker + HeartbeatMonitor) | PR 1 |
| D — Retire legacy Host | §D.1–D.3 | ✅ Migration script, installer scripts, Stream D procedure doc, archive markings on all v1 surface (this PR 2), deletion deferred to PR 3 | PR 2 (this) + PR 3 (next) |
| E — Parity validation | §E.1–E.4 | ✅ E2E test scaffold + 4 stability-finding regression tests + HostSubprocessParityTests cross-FX integration |
PR 2 (this) |
What changed in PR 2 (this branch phase-2-stream-d)
tests/ZB.MOM.WW.OtOpcUa.Tests/renamed totests/ZB.MOM.WW.OtOpcUa.Tests.v1Archive/,<AssemblyName>kept asZB.MOM.WW.OtOpcUa.Testsso the v1 Host'sInternalsVisibleTostill matches,<IsTestProject>false</IsTestProject>sodotnet test slnxexcludes it.- Three other v1 projects archive-marked with PropertyGroup comments:
OtOpcUa.Host,Historian.Aveva,IntegrationTests.IntegrationTestsalso gets<IsTestProject>false</IsTestProject>. - New
tests/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.E2E/project (.NET 10):ParityFixturespawnsOtOpcUa.Driver.Galaxy.Host.exe(net48 x86) as subprocess viaProcess.Start, connects via real named pipe, exposes a connectedGalaxyProxyDriver. Skips when Galaxy ZB unreachable, when Host EXE not built, or when running as Administrator (PipeAcl denies admins).RecordingAddressSpaceBuildercaptures Folder + Variable + Property registrations so parity tests can assert shape.HierarchyParityTests(3) — Discover returns gobjects with attributes; attribute full references matchtag.attributeshape; HistoryExtension flag flows through.StabilityFindingsRegressionTests(4) — one test per 2026-04-13 finding: phantom-probe-doesn't-corrupt-status, host-status-event-is-scoped, all-async-no-sync- over-async, AcknowledgeAsync-completes-before-returning.
docs/v2/V1_ARCHIVE_STATUS.md— inventory + deletion plan for PR 3.docs/v2/implementation/exit-gate-phase-2-final.md(this doc) — supersedes the two partial-exit docs.
Test counts
Solution-level dotnet test ZB.MOM.WW.OtOpcUa.slnx: 470 pass / 7 skip / 1 baseline failure.
| Project | Pass | Skip |
|---|---|---|
| Core.Abstractions.Tests | 24 | 0 |
| Configuration.Tests | 42 | 0 |
| Core.Tests | 4 | 0 |
| Server.Tests | 2 | 0 |
| Admin.Tests | 21 | 0 |
| Driver.Galaxy.Shared.Tests | 6 | 0 |
| Driver.Galaxy.Host.Tests | 30 | 0 |
| Driver.Galaxy.Proxy.Tests | 10 | 0 |
| Driver.Galaxy.E2E (NEW) | 0 | 7 (all skip with documented reason — admin shell) |
| Client.Shared.Tests | 131 | 0 |
| Client.UI.Tests | 98 | 0 |
| Client.CLI.Tests | 51 / 1 fail | 0 |
| Historian.Aveva.Tests | 41 | 0 |
Excluded from solution run (run explicitly when needed):
OtOpcUa.Tests.v1Archive— 494 pass (v1 unit tests, kept as parity reference)OtOpcUa.IntegrationTests— 6 pass (v1 integration tests, kept as parity reference)
Adversarial review of the PR 2 diff
Independent pass over the PR 2 deltas. New findings ranked by severity; existing findings from the previous exit-gate doc still apply.
New findings
Medium 1 — IsTestProject=false on OtOpcUa.IntegrationTests removes the safety net.
The 6 v1 integration tests no longer run on solution test. Mitigation: the new E2E suite
covers the same scenarios in the v2 topology shape. Risk: if E2E test count regresses or
fails to cover a scenario, the v1 fallback isn't auto-checked. Procedure: PR 3
checklist includes "E2E test count covers v1 IntegrationTests' 6 scenarios at minimum".
Medium 2 — Stability-finding regression tests #2, #3, #4 are structural (reflection-based) not behavioral. Findings #2 and #3 use type-shape assertions (event signature carries HostName; methods return Task) rather than triggering the actual race. Mitigation: the v1 defects were structural — fixing them required interface changes that the type-shape assertions catch. Risk: a future refactor that re-introduces sync-over-async via a non- async helper called inside a Task method wouldn't trip the test. Filed as v2.1: add a runtime async-call-stack analyzer (Roslyn or post-build).
Low 1 — ParityFixture defaults to OTOPCUA_GALAXY_BACKEND=db (not mxaccess).
Discover works against ZB without needing live MXAccess. The MXAccess-required tests will
need a second fixture once they're written.
Low 2 — Process.Start(EnvironmentVariables) doesn't always inherit clean state. The
test inherits the parent's PATH + locale, which is normally fine but could mask a missing
runtime dependency. Mitigation: in CI, pin a clean environment block.
Existing findings (carried forward from exit-gate-phase-2.md)
All 8 still apply unchanged. Particularly:
- High 1 (MxAccess Read subscription-leak on cancellation) — open
- High 2 (no MXAccess reconnect loop, only supervisor-driven recycle) — open
- Medium 3 (SubscribeAsync doesn't push OnDataChange frames yet) — open
- Medium 4 (WriteValuesAsync doesn't await OnWriteComplete) — open
Cross-cutting deferrals (out of Phase 2)
- Deletion of v1 archive — PR 3, gated on operator review + E2E coverage parity check
- Wonderware Historian SDK plugin port (
Historian.Aveva→Driver.Galaxy.Host/Backend/Historian/) — Task B.1.h, opportunistically with PR 3 or as PR 4 - MxAccess subscription push frames — Task B.1.s, follow-up to enable real-time data flow (currently subscribes register but values aren't pushed back)
- Wonderware Historian-backed HistoryRead — depends on B.1.h
- Alarm subsystem wire-up —
MxAccessGalaxyBackend.SubscribeAlarmsAsyncis a no-op - Reconnect-without-recycle in MxAccessClient — v2.1 refinement
- Real downstream-consumer cutover (ScadaBridge / Ignition / SystemPlatform IO) — outside this repo
Recommended order
- PR 1 (
phase-1-configuration→v2) — merge first; self-contained, parity preserved - PR 2 (
phase-2-stream-d→v2, this PR) — merge after PR 1; introduces E2E suite + archive markings; v1 surface still builds and is run-able explicitly - PR 3 (next session) — delete v1 archive; depends on operator approval after PR 2 reviewer signoff
- PR 4 (Phase 2 follow-up) — Historian port + MxAccess subscription push frames + the open high/medium findings