28 tasks across 5 workstreams (A worker control cmds, B worker COM cmds, C audit CorrelationId, D client CLI parity, E docs). Zero proto changes; worker net48/x86 + Java on windev, rest local.
24 KiB
Still-Pending Completion Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers-extended-cc:executing-plans (or subagent-driven-development) to implement this plan task-by-task.
Goal: Close the actionable items in stillpending.md — 11 unimplemented worker command kinds (§1.1), audit CorrelationId threading (§1.2), client CLI/helper parity (§4), and doc hygiene (§7).
Architecture: Two-process gateway/worker design is unchanged. All 11 worker commands already have proto request+reply messages, gateway validation, scope mapping, and generic pass-through routing — so the work is worker executor arms + 6 new COM-wrapper methods + a gateway constraint-path CorrelationId thread + client CLI additions. Zero .proto changes, therefore no codegen and no net48 regen risk.
Tech Stack: .NET 10 (gateway, x64), .NET Framework 4.8 (worker, x86, MXAccess COM on STA), Go/Python/Rust/Java/.NET clients. Worker net48/x86 + Java client build/test on Windows host windev (10.100.0.48, passwordless ssh, PowerShell); everything else builds locally on macOS.
Design source: docs/plans/2026-06-15-stillpending-completion-design.md.
Branch: feat/stillpending-completion (already created).
Cross-platform build reference (read before any worker/Java task)
- Worker (net48/x86) + Worker.Tests + Java client do NOT build on macOS. Build/test them on
windev:- Copy the working tree (or use the existing build worktree pattern) to
windev,git fetch && git reset --hard origin/<branch>in the build worktree (NEVER trust a stale localmain— see memoryproject_deploy_mechanics). - Build:
dotnet build src/ZB.MOM.WW.MxGateway.Worker/MxGateway.Worker.csproj -p:Platform=x86 - Test:
dotnet test src/ZB.MOM.WW.MxGateway.Worker.Tests/MxGateway.Worker.Tests.csproj -p:Platform=x86 - Live MXAccess: set
$env:MXGATEWAY_RUN_LIVE_MXACCESS_TESTS = "1"then run the IntegrationTests filter. - Nested ssh→PowerShell mangles quotes; scp a
.ps1and runpowershell -NoProfile -ExecutionPolicy Bypass -File. Wrap git incmd /c "git ... 2>&1".
- Copy the working tree (or use the existing build worktree pattern) to
- net48 worker C#: no init-only props / positional records (no
IsExternalInit); use{ get; set; }or ctors (memoryproject_net48_worker_csharp). - Gateway, .NET client, Go, Rust, Python build+test locally on macOS.
Workstream A — Worker control/lifecycle commands (5)
These add arms to MxAccessCommandExecutor.Execute (src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessCommandExecutor.cs:90-129). They need runtime state the executor does not currently hold. Task A0 establishes how the executor reaches that state; do it first.
Task A0: Decide & wire control-command collaborators into the executor
Classification: standard Estimated implement time: ~5 min Parallelizable with: none (A1–A5 depend on it)
Files:
- Modify:
src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessCommandExecutor.cs(constructor + fields) - Read first:
src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessEventQueue.cs(Drain(uint):168,Count:58),WorkerRuntimeHeartbeatSnapshot.cs,src/ZB.MOM.WW.MxGateway.Worker/Sta/StaRuntime.cs(IsRunning:80,Shutdown()),MxAccessInteropInfo.cs(progid/clsid), the executor's existing construction site (grepnew MxAccessCommandExecutor()
What to do: The 5 control commands need: the event queue (DrainEvents), a session-state source (GetSessionState), worker identity — pid/version/progid/clsid (GetWorkerInfo), and a shutdown signal (ShutdownWorker). Determine the cleanest seam:
- Preferred: inject the collaborators the executor lacks (event queue reference, a
Func<SessionState>or the session object,MxAccessInteropInfo, and a shutdown delegate/Action) via the constructor, matching how its existing COM collaborator is passed. - If the executor's construction site shows control commands are better intercepted one layer up (where
StaRuntime/session context already lives), surface that to the controller before proceeding — do NOT silently relocate the dispatch.
Acceptance: executor compiles on windev with new collaborators available to A1–A5; no behavior change yet (arms still fall through). Commit.
Note: A1–A5 are sequential edits to the same
Executeswitch + helper region of one file, so they are NOT parallelizable with each other. Bundle their review.
Task A1: Ping
Classification: small Estimated implement time: ~3 min Parallelizable with: none (same file as A0/A2-A5)
Files:
- Modify:
src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessCommandExecutor.cs(addMxCommandKind.Pingarm +ExecutePing) - Test:
src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/MxAccessCommandExecutorTests.cs(or the existing executor test file — grep to confirm name)
Step 1 — failing test: assert Execute with a Ping command (PingCommand { Message = "hi" }) returns ProtocolStatusCode.Ok, Hresult == 0, and echoes the message (via reply diagnostic or base reply — Ping has no dedicated reply message, so assert OK status). Build/test on windev.
Step 2 — run, expect FAIL (currently INVALID_REQUEST).
Step 3 — implement: add MxCommandKind.Ping => ExecutePing(command), to the switch (:99-126 region). ExecutePing returns CreateOkReply(command) (helper at :784).
Step 4 — run, expect PASS on windev.
Step 5 — commit: feat(worker): implement Ping command
Task A2: DrainEvents
Classification: small Estimated implement time: ~4 min Parallelizable with: none
Files:
- Modify:
MxAccessCommandExecutor.cs(DrainEventsarm +ExecuteDrainEvents) - Test: executor test file
Steps (TDD): test that DrainEvents { MaxEvents = N } drains up to N from the injected MxAccessEventQueue and returns DrainEventsReply { events = [...] } (reply field 102). MaxEvents == 0 drains all. Map each WorkerEvent → MxEvent using the existing event-mapping path (grep how the live event loop converts WorkerEvent→MxEvent; reuse, do not duplicate). Build/test windev. Commit feat(worker): implement DrainEvents command.
Task A3: GetSessionState
Classification: small Estimated implement time: ~3 min Parallelizable with: none
Files: MxAccessCommandExecutor.cs + executor test.
Steps: test that GetSessionState returns SessionStateReply { State = <current> } (reply field 100) mapping the worker's lifecycle to the proto SessionState enum (READY when the STA is running). Build/test windev. Commit feat(worker): implement GetSessionState command.
Task A4: GetWorkerInfo
Classification: small Estimated implement time: ~3 min Parallelizable with: none
Files: MxAccessCommandExecutor.cs + executor test.
Steps: test that GetWorkerInfo returns WorkerInfoReply { WorkerProcessId, WorkerVersion, MxaccessProgid, MxaccessClsid } (reply field 101) sourced from Process.GetCurrentProcess().Id, the worker assembly version, and MxAccessInteropInfo (progid LMXProxy.LMXProxyServer.1, clsid {C30B52F5-2CB5-4760-AF0A-3A344A7EB5DC}). Build/test windev. Commit feat(worker): implement GetWorkerInfo command.
Task A5: ShutdownWorker
Classification: standard Estimated implement time: ~5 min Parallelizable with: none
Files: MxAccessCommandExecutor.cs + executor test.
Steps: test that ShutdownWorker { GracePeriod } returns a base OK reply and triggers the injected shutdown signal after the reply is produced (must not deadlock the STA — signal shutdown, return reply, let the pump drain). Verify the grace period is honored (or documented as best-effort). Build/test windev. Commit feat(worker): implement ShutdownWorker command.
Task A6: Make FakeWorkerHarness respond to control commands
Classification: standard Estimated implement time: ~5 min Parallelizable with: none (depends on A1–A5 reply shapes)
Files:
- Modify:
src/ZB.MOM.WW.MxGateway.Tests/Gateway/Workers/Fakes/FakeWorkerHarness.cs - Test: a gateway-side test that invokes
Ping/GetWorkerInfo/DrainEventsthrough the harness and asserts the reply (builds locally on macOS).
Why: the audit (§1.1) flagged that control kinds were "exercised only through FakeWorkerHarness" but the harness is a passive relay that does not auto-respond — so gateway tests could not actually cover them. Add canned responses so the gateway↔worker round-trip for these commands is verified in the default (no-MXAccess) suite. Commit test(gateway): fake worker responds to control commands.
Workstream B — Worker MXAccess COM commands (6)
Suspend, Activate, AuthenticateUser, ArchestrAUserToId, AddBufferedItem, SetBufferedUpdateInterval. Task B0 (windev interop inspection) MUST run first — the native interface exposing each method is unknown until inspected.
Task B0: Resolve native COM signatures on windev
Classification: standard Estimated implement time: ~5 min (investigation) Parallelizable with: A-workstream tasks (different files/host activity)
Files:
- Read on windev: the generated interop for
ArchestrA.MXAccess.dll(theILMXProxyServer/ILMXProxyServer3/ILMXProxyServer4RCW definitions),C:\Users\dohertj2\Desktop\mxaccess\docs\MXAccess-Public-API.md(method list/signatures). - Output: a short note appended to this plan (or a comment block) recording, for each of the 6 methods, which interface version exposes it and its exact signature.
What to do: Confirm the exact native signatures for Suspend(int serverHandle, int itemHandle), Activate(int serverHandle, int itemHandle), AuthenticateUser(int serverHandle, string verifyUser, string verifyUserPassword) → user id, ArchestrAUserToId(int serverHandle, string userIdGuid) → user id, AddBufferedItem(int serverHandle, string itemDefinition, string itemContext) → item handle, SetBufferedUpdateInterval(int serverHandle, int intervalMs). If any method is not present on the installed interop (mirroring the §3.4/§3.5 vendor-stub pattern for alarms), STOP and surface it — implement only the available ones and record the rest as vendor-gated residuals. Commit the note.
Task B1: Add 6 wrapper methods to IMxAccessServer + MxAccessComServer
Classification: high-risk Estimated implement time: ~5 min Parallelizable with: none (blocked by B0)
Files:
- Modify:
src/ZB.MOM.WW.MxGateway.Worker/MxAccess/IMxAccessServer.cs(add 6 method declarations) - Modify:
src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessComServer.cs(dispatch to the interface version resolved in B0, mirroring existing methods likeWrite2:173,AddItem2:84) - Modify: any fake/test
IMxAccessServerimplementation (grep: IMxAccessServer) to add the 6 methods (return canned values). - Test:
src/ZB.MOM.WW.MxGateway.Worker.Tests/MxAccess/...forMxAccessComServerif one exists.
Steps (TDD): add the 6 declarations; implement dispatch following the existing version-selection pattern; update fakes so the solution compiles. Build on windev -p:Platform=x86. Commit feat(worker): add MXAccess COM wrappers for suspend/activate/auth/buffered.
B2–B7 are sequential edits to the same
Executeswitch; not parallelizable with each other. Bundle review.
Task B2: Suspend arm
Classification: small · ~3 min · Parallelizable with: none
Files: MxAccessCommandExecutor.cs + executor test.
TDD: Suspend { ServerHandle, ItemHandle } calls the wrapper and returns SuspendReply { Status = MxStatusProxy } (reply field 24). Use a fake IMxAccessServer asserting the call. Build/test windev. Commit feat(worker): implement Suspend command.
Task B3: Activate arm
Classification: small · ~3 min · Parallelizable with: none
Files: MxAccessCommandExecutor.cs + executor test.
TDD: Activate { ServerHandle, ItemHandle } → ActivateReply { Status } (field 25). Build/test windev. Commit feat(worker): implement Activate command.
Task B4: AuthenticateUser arm
Classification: standard · ~4 min · Parallelizable with: none
Files: MxAccessCommandExecutor.cs + executor test.
TDD: AuthenticateUser { ServerHandle, VerifyUser, VerifyUserPassword } → AuthenticateUserReply { UserId } (field 26). Credentials must never be logged (standing rule) — assert no log statement includes the password. AuthenticateUser is allowed to fail (surface the native HResult, do not throw). Build/test windev. Commit feat(worker): implement AuthenticateUser command.
Task B5: ArchestrAUserToId arm
Classification: small · ~3 min · Parallelizable with: none
Files: MxAccessCommandExecutor.cs + executor test.
TDD: ArchestrAUserToId { ServerHandle, UserIdGuid } → ArchestrAUserToIdReply { UserId } (field 27). Build/test windev. Commit feat(worker): implement ArchestrAUserToId command.
Task B6: AddBufferedItem arm
Classification: standard · ~4 min · Parallelizable with: none
Files: MxAccessCommandExecutor.cs + executor test.
TDD: AddBufferedItem { ServerHandle, ItemDefinition, ItemContext } → AddBufferedItemReply { ItemHandle } (field 23). Build/test windev. Commit feat(worker): implement AddBufferedItem command.
Task B7: SetBufferedUpdateInterval arm
Classification: small · ~3 min · Parallelizable with: none
Files: MxAccessCommandExecutor.cs + executor test.
TDD: SetBufferedUpdateInterval { ServerHandle, UpdateIntervalMilliseconds } → base OK reply (no dedicated reply message). Build/test windev. Commit feat(worker): implement SetBufferedUpdateInterval command.
Task B8: Live COM smoke + buffered capture on windev
Classification: high-risk Estimated implement time: ~5 min (authoring; live run is manual) Parallelizable with: none (blocked by B1–B7)
Files:
- Modify:
src/ZB.MOM.WW.MxGateway.IntegrationTests/WorkerLiveMxAccessSmokeTests.cs(the existing AuthenticateUser send at ~line 919/931 should now get an OK/typed reply instead of INVALID_REQUEST; add Suspend/Activate/AddBufferedItem+SetBufferedUpdateInterval sends). - Possibly:
src/ZB.MOM.WW.MxGateway.Worker.Tests/Probes/for a buffered-capture probe.
Steps: Under MXGATEWAY_RUN_LIVE_MXACCESS_TESTS=1 on windev: verify the 4 unambiguous COM commands round-trip; then AddBufferedItem + SetBufferedUpdateInterval on a real tag and capture a multi-sample OnBufferedDataChange batch to validate the §3.2 VariantConverter path. If the buffered conversion proves correct, record it; if it surfaces a conversion bug, STOP and report (do not silently ship). If a live buffered sample cannot be elicited on the rig, record buffered round-trip as the documented residual (close the command gap, leave §3.2 open). Commit test(integration): live COM command + buffered capture smoke.
Workstream C — §1.2 gateway audit CorrelationId
Task C1: Thread ClientCorrelationId into constraint-denial audit records
Classification: high-risk Estimated implement time: ~5 min Parallelizable with: all A/B/D tasks (gateway-only files, builds locally)
Files:
- Modify:
src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/IConstraintEnforcer.cs(addstring? correlationIdparam toRecordDenialAsync, signature at:49-54) - Modify:
src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/ConstraintEnforcer.cs(:124-158): accept the param,Guid.TryParseit into theGuid? CorrelationIdaudit field (was hardcodednullat:147); removeTODO(Task 2.3)at:134-136. - Modify:
src/ZB.MOM.WW.MxGateway.Server/Grpc/MxAccessGatewayService.cs: threadrequest.ClientCorrelationIdfromInvoke(:96) →ApplyConstraintsAsync(:279) → the 6 filter helpers (EnforceReadTagAsync:427,EnforceWriteHandleAsync:448,FilterTagBulkAsync:474,FilterReadBulkAsync:529,FilterWriteBulkAsync:584,FilterHandleBulkAsync:656) →RecordDenialAsync. - Test:
src/ZB.MOM.WW.MxGateway.Tests/...constraint-enforcer / gateway-service test.
Step 1 — failing test: a denied operation with ClientCorrelationId = "<a real GUID>" persists an audit record whose CorrelationId equals that GUID; a non-GUID correlation id persists null (documented behavior). Run locally: dotnet test src/ZB.MOM.WW.MxGateway.Tests/MxGateway.Tests.csproj --filter <name>.
Step 2 — FAIL (currently always null).
Step 3 — implement the threading + Guid.TryParse.
Step 4 — PASS locally + full gateway suite green.
Step 5 — commit: feat(gateway): thread ClientCorrelationId into constraint-denial audit (§1.2)
Workstream D — Client CLI/helper parity (5 clients)
All D tasks touch disjoint client trees and are parallelizable across languages. Each builds/tests on its own toolchain (Java on windev; the rest local).
Task D1: Go single-shot Write2 helper
Classification: small · ~3 min · Parallelizable with: D2–D9 Files:
- Modify:
clients/go/mxgateway/session.go(addWrite2/Write2RawafterWrite:559, modeled onWrite+ theWrite2Bulk:427 payload shape) - Test:
clients/go/mxgateway/session_test.go(or nearest) TDD:Write2(ctx, serverHandle, itemHandle, value, timestampValue *MxValue, userID int32) errorissuesMX_COMMAND_KIND_WRITE2withWrite2Command{ServerHandle,ItemHandle,Value,TimestampValue,UserId}. Verify:gofmt,go build ./...,go test ./...fromclients/go. Commitfeat(go): add single-shot Write2 session helper (§4.1).
Task D2: Python galaxy-* CLI commands (4)
Classification: standard · ~5 min · Parallelizable with: D1,D3–D9 Files:
- Modify:
clients/python/src/zb_mom_ww_mxgateway_cli/commands.py(addgalaxy-test-connection,galaxy-last-deploy,galaxy-discover,galaxy-watchClick commands wrappinggalaxy.pytest_connection/get_last_deploy_time/discover_hierarchy/watch_deploy_events; mirror the existingpingcommand structure at:221) - Modify:
clients/python/README.md:217(correct the understated galaxy CLI claim) - Test:
clients/python/tests/CLI test TDD thenpython -m pytestfromclients/python. Commitfeat(python): add galaxy-* CLI commands (§4.2).
Task D3: ping CLI in Go
Classification: small · ~3 min · Parallelizable with: others
Files: clients/go/cmd/mxgw-go/main.go (add ping case to the switch ~:77-130/:1199, modeled on an existing simple command) + test.
TDD; gofmt, go build ./..., go test ./.... Commit feat(go): add ping CLI subcommand (§4.3).
Task D4: ping CLI in Java
Classification: small · ~3 min · Parallelizable with: others — build on windev
Files: clients/java/zb-mom-ww-mxgateway-cli/src/main/java/com/zb/mom/ww/mxgateway/cli/MxGatewayCli.java (register a ping subcommand ~:126-149) + test.
TDD; gradle test on windev. Commit feat(java): add ping CLI subcommand (§4.3).
Task D5: browse CLI — Go
Classification: standard · ~4 min · Parallelizable with: others
Files: clients/go/cmd/mxgw-go/main.go (new browse command wrapping GalaxyClient.Browse:398 / LazyBrowseNode.Expand:337) + test. go build/test. Commit feat(go): add browse CLI (§4.6).
Task D6: browse CLI — Python
Classification: standard · ~4 min · Parallelizable with: others
Files: clients/python/src/zb_mom_ww_mxgateway_cli/commands.py (new browse command wrapping galaxy.py browse:163) + test. pytest. Commit feat(python): add browse CLI (§4.6).
Task D7: browse CLI — Rust
Classification: standard · ~4 min · Parallelizable with: others
Files: clients/rust/crates/mxgw-cli/src/main.rs (new Browse command variant wrapping the galaxy browse helper in galaxy.rs) + test. cargo fmt, cargo test --workspace, cargo clippy --all-targets -- -D warnings. Commit feat(rust): add browse CLI (§4.6).
Task D8: browse CLI — Java + dotnet
Classification: standard · ~5 min · Parallelizable with: others — Java builds on windev
Files: clients/java/.../MxGatewayCli.java (browse subcommand wrapping GalaxyRepositoryClient.browse) + clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/MxGatewayClientCli.cs (browse command wrapping LazyBrowseNode.ExpandAsync:63) + tests. gradle test (windev), dotnet test (local). Commit feat(dotnet,java): add browse CLI (§4.6) — 5/5 parity.
Task D9: Java galaxy-name aliases + verify dotnet version
Classification: small · ~4 min · Parallelizable with: others — Java builds on windev Files:
- Modify:
clients/java/.../MxGatewayCli.java:145-146— add canonicalgalaxy-test-connection/galaxy-last-deployas the primary names; keepgalaxy-test/galaxy-deploy-timeas deprecated aliases (picocli@Command(name=..., aliases={...})or equivalent). - Verify:
clients/dotnet/.../MxGatewayClientCli.cs— the explorer found aversionpath at:85that conflicts with audit §4.4. Read it: if aversionsubcommand genuinely works, no change (note it in the §7 update); if it's only a--versionflag andIsKnownGatewayCommandlacksversion, add the subcommand. Do not add what already exists. - Test: Java CLI test asserting both names resolve.
gradle test(windev),dotnet build/test(local). Commitfeat(java): galaxy command aliases; chore(dotnet): verify version subcommand (§4.4,§4.5).
Workstream E — Docs/hygiene + residual recording
Task E1: Doc hygiene + dead-code removal
Classification: small · ~5 min · Parallelizable with: all (mostly doc-only; one code deletion) Files:
docs/plans/2026-06-14-deferred-followups.md:4— change "Plan only — NOT yet executed" to reflect D1 done (4af24b9).docs/AlarmClientDiscovery.md:765-774— rewrite stale STA "production fix needed" prose (alarms now run through worker STA /GatewayAlarmMonitor).src/ZB.MOM.WW.MxGateway.Server/Dashboard/Hubs/EventsHub.cs:9-17— remove/update stale "publisher side is a follow-up" comment (broadcaster shipped).CLAUDE.md— fix project-name driftsrc/MxGateway.*→src/ZB.MOM.WW.MxGateway.*throughout.src/ZB.MOM.WW.MxGateway.Server/Grpc/GalaxyRepositoryGrpcService.cs:350-360— remove dead IDE0051-suppressedMapSqlException. Verify:dotnet build src/ZB.MOM.WW.MxGateway.Serverlocally (the only code change is the deletion). Commitdocs+chore: fix stale prose, project names, remove dead MapSqlException (§7).
Task E2: Record §1.3 and §1.4 residuals + refresh stillpending.md
Classification: trivial · ~3 min · Parallelizable with: all (doc-only) Files:
docs/plans/2026-06-14-deferred-followups.md— record §1.3 (provider_switches counter live-exercise unproven; rig can't drive a real failover) as an explicit documented residual.- Add a short note (in the worker alarm code's existing comment near
WnWrapAlarmConsumer.cs:261or the design doc) that §1.4's 8-arg ack drops domain/full-name because the AVEVAAlarmAckByNamev2 is a vendor stub (-55) — already partly noted; make it explicit and cross-referenced. stillpending.md— mark §1.1, §1.2, §4.1/§4.2/§4.3/§4.6 (and §4.4/§4.5 per outcome) as Resolved with commit refs; keep the documented residuals. Commitdocs: record §1.3/§1.4 residuals and refresh stillpending.md (§7).
Final integration review
After all workstreams: run the full local suite (dotnet test gateway + .NET client, go test, pytest, cargo test+clippy) and the windev suite (worker net48/x86 + Java + live MXAccess smoke). Then use superpowers-extended-cc:finishing-a-development-branch.
Dependency summary
- A0 → A1..A5 → A6
- B0 → B1 → B2..B7 → B8
- C1 independent (gateway-only, local)
- D1..D9 independent of A/B/C and of each other (disjoint client trees)
- E1, E2 last (reflect what closed); E1 mostly independent