Phase 2 Stream D Option B — archive v1 + new Driver.Galaxy.E2E parity suite #2
Closed
dohertj2
wants to merge 0 commits from
phase-2-stream-d into v2
pull from: phase-2-stream-d
merge into: dohertj2:v2
dohertj2:master
dohertj2:v2
dohertj2:phase-6-1-stream-e3-inflight-counter
dohertj2:phase-6-4-stream-b-staging-tables
dohertj2:phase-6-1-resilience-status-publisher
dohertj2:phase-6-1-stream-a-multihost-dispatch
dohertj2:phase-6-1-stream-a-resilience-config
dohertj2:phase-6-4-stream-d-identification
dohertj2:phase-6-1-stream-b4-hosted-service
dohertj2:v2-release-readiness-blocker3-closed
dohertj2:phase-6-3-stream-c-state-publisher
dohertj2:phase-6-3-stream-a-topology-loader
dohertj2:v2-release-readiness-blocker2-closed
dohertj2:phase-6-1-stream-d-wiring-followup
dohertj2:v2-release-readiness-blocker1-closed
dohertj2:phase-6-2-stream-c-dispatch-wiring-followup
dohertj2:v2-release-readiness-capstone
dohertj2:phase-6-4-exit-gate
dohertj2:phase-6-4-stream-ab-data-layer
dohertj2:phase-6-3-exit-gate
dohertj2:phase-6-3-stream-b-service-level
dohertj2:phase-6-2-exit-gate
dohertj2:phase-6-2-stream-d-validated-authoring
dohertj2:phase-6-2-stream-c-dispatch-wiring
dohertj2:phase-6-2-stream-b-permission-trie
dohertj2:phase-6-2-stream-a-ldap-role-mapping
dohertj2:phase-6-1-exit-gate
dohertj2:phase-6-1-stream-e-admin-hosts
dohertj2:phase-6-1-stream-d-litedb-sealed-cache
dohertj2:phase-6-1-stream-c-health-logging
dohertj2:phase-6-1-stream-b-stability
dohertj2:phase-6-1-stream-a-resilience
dohertj2:phase-6-reconcile
dohertj2:phase-6-plans-drafts
dohertj2:phase-3-pr76-opcua-client-history
dohertj2:phase-3-pr75-opcua-client-alarms
dohertj2:phase-3-pr74-opcua-client-session-reconnect
dohertj2:phase-3-pr73-opcua-client-browse-enrichment
dohertj2:phase-3-pr72-opcua-client-failover
dohertj2:phase-3-pr71-opcua-client-cert-auth
dohertj2:phase-3-pr70-opcua-client-security-policy
dohertj2:phase-3-pr69-opcua-client-subscribe-probe
dohertj2:phase-3-pr68-opcua-client-discovery
dohertj2:phase-3-pr67-opcua-client-read-write
dohertj2:phase-3-pr66-opcua-client-scaffold
dohertj2:phase-3-pr65-s7-discovery-subscribe-probe
dohertj2:phase-3-pr64-s7-read-write
dohertj2:phase-3-pr63-s7-address-parser
dohertj2:phase-3-pr62-s7-driver-scaffold
dohertj2:phase-2-pr61-scrub-v1-archive-residue
dohertj2:phase-3-pr60-mitsubishi-quirk-tests
dohertj2:phase-3-pr59-melsec-address-helper
dohertj2:phase-3-pr58-mitsubishi-sim-profile
dohertj2:phase-3-pr57-s7-quirk-tests
dohertj2:phase-3-pr56-s7-sim-profile
dohertj2:phase-3-pr55-mitsubishi-research-doc
dohertj2:phase-3-pr54-s7-research-doc
dohertj2:phase-3-pr53-dl205-reconnect
dohertj2:phase-3-pr52-dl205-exception-codes
dohertj2:phase-3-pr51-dl205-xinput
dohertj2:phase-3-pr50-dl205-coil-mapping
dohertj2:phase-3-pr49-dl205-fc-caps
dohertj2:phase-3-pr48-dl205-cdab-float
dohertj2:phase-3-pr47-dl205-vmemory
dohertj2:phase-3-pr46-dl205-bcd
dohertj2:phase-3-pr45-dl205-string-byte-order
dohertj2:phase-3-pr44-pymodbus-validation-fixes
dohertj2:phase-3-pr43-pymodbus-swap
dohertj2:phase-3-pr42-modbuspal-profiles
dohertj2:phase-3-pr41-dl205-quirks-doc
dohertj2:phase-3-pr40-livestack-write-subscribe
dohertj2:phase-3-pr39-elevated-shell-skip
dohertj2:phase-3-pr38-historyread-servicehandler
dohertj2:phase-3-pr37-live-stack-smoke
dohertj2:phase-3-pr36-aveva-prerequisites
dohertj2:phase-3-pr35-history-readtime-readevents
dohertj2:phase-3-pr34-host-status-publisher-page
dohertj2:phase-3-pr33-driverhoststatus-entity
dohertj2:phase-3-pr32-multi-driver-integration
dohertj2:phase-3-pr31-live-ldap-ad-compat
dohertj2:phase-3-pr30-modbus-integration-scaffold
dohertj2:phase-3-pr29-account-page
dohertj2:phase-3-pr28-cert-trust
dohertj2:phase-3-pr27-fleet-dashboard
dohertj2:phase-3-pr26-server-write-authz
dohertj2:phase-3-pr25-modbus-test-plan
dohertj2:phase-3-pr24-modbus-types
dohertj2:phase-3-pr23-modbus-probe
dohertj2:phase-3-pr22-modbus-subscribe
dohertj2:phase-3-pr21-modbus-driver
dohertj2:phase-3-pr20-lmx-followups
dohertj2:phase-3-pr19-ldap-security
dohertj2:phase-3-pr18-delete-v1
dohertj2:phase-3-pr17-server-startup
dohertj2:phase-3-pr16-opcua-server
dohertj2:phase-3-pr15-alarm-contract
dohertj2:phase-2-pr14-alarm-subsystem
dohertj2:phase-2-pr13-runtime-probe
dohertj2:phase-2-pr12-quality-mapper
dohertj2:phase-2-pr11-history-events
dohertj2:phase-2-pr10-history-attime
dohertj2:phase-2-pr9-alarms
dohertj2:phase-2-pr8-alarms-hoststatus
dohertj2:phase-2-pr7-history-processed
dohertj2:phase-2-pr6-monitor-findings
dohertj2:phase-2-pr5-historian
dohertj2:phase-2-pr4-findings
dohertj2:phase-1-configuration
dohertj2:phase-0-rename
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "phase-2-stream-d"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
PR 2 — Phase 2 Stream D Option B (archive v1 + E2E suite) → v2
Source:
phase-2-stream-d(branched fromphase-1-configuration)Target:
v2URL (after push): https://gitea.dohertylan.com/dohertj2/lmxopcua/pulls/new/phase-2-stream-d
Summary
Phase 2 Stream D Option B per
docs/v2/implementation/stream-d-removal-procedure.md:tests/ZB.MOM.WW.OtOpcUa.Tests/→tests/ZB.MOM.WW.OtOpcUa.Tests.v1Archive/(
<AssemblyName>kept asZB.MOM.WW.OtOpcUa.Testsso v1 Host'sInternalsVisibleTostill matches;
<IsTestProject>false</IsTestProject>so solution test runs skip it).tests/ZB.MOM.WW.OtOpcUa.IntegrationTests/—<IsTestProject>false</IsTestProject>src/ZB.MOM.WW.OtOpcUa.Host/+src/ZB.MOM.WW.OtOpcUa.Historian.Aveva/— archivePropertyGroup comments. Both still build (Historian plugin + 41 historian tests still
pass) so Phase 2 PR 3 can delete them in a focused, reviewable destructive change.
tests/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.E2E/test project (.NET 10):ParityFixturespawnsOtOpcUa.Driver.Galaxy.Host.exe(net48 x86) as a subprocess viaProcess.Start, connects via real named pipe, exposes a connectedGalaxyProxyDriver.Skips when Galaxy ZB unreachable / Host EXE not built / Administrator shell.
HierarchyParityTests(3) andStabilityFindingsRegressionTests(4) — one test per2026-04-13 stability finding (phantom probe, cross-host quality clear, sync-over-async,
fire-and-forget alarm shutdown race).
docs/v2/V1_ARCHIVE_STATUS.md— inventory + deletion plan for PR 3.docs/v2/implementation/exit-gate-phase-2-final.md— supersedes the two partial-exitdocs with the as-built state, adversarial review of PR 2 deltas (4 new findings), and the
recommended PR sequence (1 → 2 → 3 → 4).
What's NOT in this PR
HistoryRead.data-change push from Host → Proxy.
Tests
dotnet test ZB.MOM.WW.OtOpcUa.slnx: 470 pass / 7 skip / 1 pre-existing baseline.The 7 skips are the new E2E tests, all skipping with the documented reason
"PipeAcl denies Administrators on dev shells" — the production install runs as a non-admin
service account and these tests will execute there.
Run the archived v1 suites explicitly:
Test plan for reviewers
dotnet build ZB.MOM.WW.OtOpcUa.slnxsucceeds with no warnings beyond the knownNuGetAuditSuppress + NU1702 cross-FX
dotnet test ZB.MOM.WW.OtOpcUa.slnxshows the 470/7-skip/1-baseline resultdotnet build src/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host),then run E2E tests on a non-admin shell — they should actually execute and pass
against live Galaxy ZB
docs/v2/V1_ARCHIVE_STATUS.mdand confirm the deletion plan is acceptableFollow-up tracking
V1_ARCHIVE_STATUS.md.4 projects removed, .slnx updated, full solution test confirms parity.
close the high/medium open findings from
exit-gate-phase-2-final.md.otopcua-mssql-data:/var/opt/mssqlon the SQL Server container so DB files survive container restart anddocker rm; sqlcmd verification command using the newmssql-tools18path that the 2022 image ships with; EF Core CLI install for use starting in Phase 1 Stream B; bumped step count from 8 → 10. Also adds a Troubleshooting subsection covering the seven most common Windows install snags (WSL distro not auto-installed needs-d Ubuntu; Docker PATH not refreshed needs new shell or sign-in; docker-users group membership needs sign-out/in; WSL 2 kernel update needs manual install on legacy systems; SA password complexity rules; Linux vs Windows containers mode mismatch; Hyper-V coexistence with Docker requires WSL 2 backend not Hyper-V backend per decision #134). Step 1 acceptance criteria gain "docker ps shows otopcua-mssql Up" and explicit note that steps 4a/4b need admin elevation (no silent admin-free path exists on Windows). bf6741ba7fotopcua-mssqlat localhost:1433 with sa/OtOpcUaDev_2026! credentials and Docker named volumeotopcua-mssql-datamounted at /var/opt/mssql, dev Galaxy, GLAuth at C:\publish\glauth\ on ports 3893/3894, plus rows for not-yet-standing services like OPC Foundation reference server / FOCAS stub / Modbus simulator / ab_server / Snap7 / TwinCAT XAR VM with target ports to stand up later); Connection strings for appsettings.Development.json (copy-paste-ready, flagged never-commit); Container management quick reference (start/stop/logs/shell/query/nuclear-reset); Credential rotation note. fc0ce36308requiredmodifier — net48 lacks RequiredMemberAttribute and we'd need a polyfill shim like the existing IsExternalInit one; default-string init is simpler). System.Data.SqlClient 4.9.0 added (the same package the v1 Host uses; net48-compatible). Backend/DbBackedGalaxyBackend wraps the repository: DiscoverAsync builds a real DiscoverHierarchyResponse (groups attributes by gobject, resolves parent-by-tagname, maps category_id → human-readable template-category name mirroring v1 AlarmObjectFilter); ReadValuesAsync/WriteValuesAsync/HistoryReadAsync still surface "MXAccess code lift pending (Phase 2 Task B.1)" because runtime data values genuinely need the COM client; OpenSession/CloseSession/Subscribe/Unsubscribe/AlarmSubscribe/AlarmAck/Recycle return success without backend work (subscription ID is a synthetic counter for now). Live smoke tests (GalaxyRepositoryLiveSmokeTests) skip when localhost ZB is unreachable; when present they verify (1) TestConnection returns true, (2) GetHierarchy returns at least one deployed gobject with a non-empty TagName, (3) GetAttributes returns rows with FullTagReference matching the "tag.attribute" shape, (4) GetLastDeployTime returns a value, (5) DbBackedBackend.DiscoverAsync returns at least one gobject with attributes and a populated TemplateCategory. All 5 pass against the local Galaxy. Full solution 957 pass / 1 pre-existing Phase 0 baseline; the 494 v1 IntegrationTests + 6 v1 IntegrationTests-net48 tests still pass — legacy OtOpcUa.Host untouched. Remaining for the Phase 2 exit gate is the MXAccess COM client port itself (the v1 MxAccessClient partials + IMxProxy abstraction + StaPump-based Connect/Subscribe/Read/Write semantics) — Discover is now solved in DB-backed form, so the lift can focus exclusively on the runtime data-plane. 549cd36662git rm -r src/ZB.MOM.WW.OtOpcUa.Hostis destructive enough to need explicit operator authorization on a real PR review. scripts/migration/Migrate-AppSettings-To-DriverConfig.ps1 takes a v1 appsettings.json and emits the v2 DriverInstance.DriverConfig JSON blob (MxAccess/Database/Historian sections) ready to upsert into the central Configuration DB; null-leaf stripping; -DryRun mode; smoke-tested against the dev appsettings.json and produces the expected three-section ordered-dictionary output. scripts/install/Install-Services.ps1 registers the two v2 services with sc.exe — OtOpcUaGalaxyHost first (net48 x86 EXE with OTOPCUA_GALAXY_PIPE/OTOPCUA_ALLOWED_SID/OTOPCUA_GALAXY_SECRET/OTOPCUA_GALAXY_BACKEND/OTOPCUA_GALAXY_ZB_CONN/OTOPCUA_GALAXY_CLIENT_NAME env vars set via HKLM:\SYSTEM\CurrentControlSet\Services\OtOpcUaGalaxyHost\Environment registry), then OtOpcUa with depend=OtOpcUaGalaxyHost; resolves down-level account names to SID for the IPC ACL; generates a fresh 32-byte base64 shared secret per install if not supplied (kept out of registry — operators record offline for service rebinding scenarios); echoes start commands. scripts/install/Uninstall-Services.ps1 stops + removes both services. tests/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Proxy.Tests/HostSubprocessParityTests.cs is the production-shape parity test — Proxy (.NET 10) spawns the actual OtOpcUa.Driver.Galaxy.Host.exe (net48 x86) as a subprocess via Process.Start with backend=db env vars, connects via real named pipe, calls Discover, asserts at least one Galaxy gobject comes back. Skipped when running as Administrator (PipeAcl denies admins, same guard as other IPC integration tests), when the Host EXE hasn't been built, or when the ZB SQL endpoint is unreachable. This is the cross-FX integration that the parity suite genuinely needs — the previous IPC tests all ran in-process; this one validates the production deployment topology where Proxy and Host are separate processes communicating only over the named pipe. docs/v2/implementation/stream-d-removal-procedure.md is the next-session playbook: Option A (rewrite 494 v1 tests via a ProxyMxAccessClientAdapter that implements v1's IMxAccessClient by forwarding to GalaxyProxyDriver — Vtq↔DataValueSnapshot, Quality↔StatusCode, OnTagValueChanged↔OnDataChange mapping; 3-5 days, full coverage), Option B (rename OtOpcUa.Tests → OtOpcUa.Tests.v1Archive with [Trait("Category", "v1Archive")] for opt-in CI runs; new OtOpcUa.Driver.Galaxy.E2E test project with 10-20 representative tests via the HostSubprocessParityTests pattern; 1-2 days, accreted coverage); deletion checklist with eight pre-conditions, ten ordered steps, and a rollback path (git revert restores the legacy Host alongside the v2 stack — both topologies remain installable until the downstream consumer cutover). Full solution 964 pass / 1 pre-existing Phase 0 baseline; the 494 v1 IntegrationTests + 6 v1 IntegrationTests-net48 still pass because legacy OtOpcUa.Host stays untouched until an interactive session executes the procedure doc. 7403b92b72dotnet test ZB.MOM.WW.OtOpcUa.slnxnow skips them via IsTestProject=false on the test projects + archive-status PropertyGroup comments on the src projects. The destructive deletion is reserved for Phase 2 PR 3 with explicit operator review per CLAUDE.md "only use destructive operations when truly the best approach". tests/ZB.MOM.WW.OtOpcUa.Tests/ renamed via git mv to tests/ZB.MOM.WW.OtOpcUa.Tests.v1Archive/; csproj <AssemblyName> kept as the original ZB.MOM.WW.OtOpcUa.Tests so v1 OtOpcUa.Host's [InternalsVisibleTo("ZB.MOM.WW.OtOpcUa.Tests")] still matches and the project rebuilds clean. tests/ZB.MOM.WW.OtOpcUa.IntegrationTests gets <IsTestProject>false</IsTestProject>. src/ZB.MOM.WW.OtOpcUa.Host + src/ZB.MOM.WW.OtOpcUa.Historian.Aveva get PropertyGroup archive-status comments documenting they're functionally superseded but kept in-build because cascading dependencies (Historian.Aveva → Host; IntegrationTests → Host) make a single-PR deletion high blast-radius. New tests/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.E2E/ project (.NET 10) with ParityFixture that spawns OtOpcUa.Driver.Galaxy.Host.exe (net48 x86) as a Process.Start subprocess with OTOPCUA_GALAXY_BACKEND=db env vars, awaits 2s for the PipeServer to bind, then exposes a connected GalaxyProxyDriver; skips on non-Windows / Administrator shells (PipeAcl denies admins per decision #76) / ZB unreachable / Host EXE not built — each skip carries a SkipReason string the test method reads via Assert.Skip(SkipReason). RecordingAddressSpaceBuilder captures every Folder/Variable/AddProperty registration so parity tests can assert on the same shape v1 LmxNodeManager produced. HierarchyParityTests (3) — Discover returns gobjects with attributes; attribute full references match the tag.attribute Galaxy reference grammar; HistoryExtension flag flows through correctly. StabilityFindingsRegressionTests (4) — one test per 2026-04-13 stability finding from commitsc76ab8fand7310925: phantom probe subscription doesn't corrupt unrelated host status; HostStatusChangedEventArgs structurally carries a specific HostName + OldState + NewState (event signature mathematically prevents the v1 cross-host quality-clear bug); all GalaxyProxyDriver capability methods return Task or Task<T> (sync-over-async would deadlock OPC UA stack thread); AcknowledgeAsync completes before returning (no fire-and-forget background work that could race shutdown). Solution test count: 470 pass / 7 skip (E2E on admin shell) / 1 pre-existing Phase 0 baseline. Run archived suites explicitly:dotnet test tests/ZB.MOM.WW.OtOpcUa.Tests.v1Archive(494 pass) +dotnet test tests/ZB.MOM.WW.OtOpcUa.IntegrationTests(6 pass). docs/v2/V1_ARCHIVE_STATUS.md inventories every archived surface with run-it-explicitly instructions + a 10-step deletion plan for PR 3 + rollback procedure (git revert restores all four projects). docs/v2/implementation/exit-gate-phase-2-final.md supersedes the two partial-exit docs with the per-stream status table (A/B/C/D/E all addressed, D split across PR 2/3 per safety protocol), the test count breakdown, fresh adversarial review of PR 2 deltas (4 new findings: medium IsTestProject=false safety net loss, medium structural-vs-behavioral stability tests, low backend=db default, low Process.Start env inheritance), the 8 carried-forward findings from exit-gate-phase-2.md, the recommended PR order (1 → 2 → 3 → 4). docs/v2/implementation/pr-2-body.md is the Gitea web-UI paste-in for opening PR 2 once pushed. a3d16a28f1Closing � commits from this PR are already ancestors of v2 via PR #3 (pr4-findings was a linear superset). The code is merged; this PR is now redundant.
Pull request closed