Files
mxaccessgw/archreview/2026-08-19/remediation/00-tracking.md
T
Joseph Doherty 74ba889dea
ci / nightly-windev (push) Has been skipped
ci / windows-x86 (push) Successful in 1m21s
ci / java (push) Successful in 2m29s
ci / portable (push) Successful in 9m46s
docs: record cycle-3 client release
2026-08-25 12:47:54 -04:00

260 lines
27 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.
# MxAccessGateway — Remediation Plan & Tracking (2026-08-19 review)
Source review: [`../00-overall.md`](../00-overall.md) (HEAD `8b3e3b3`, committed as `f4919ba`). Generated 2026-08-20.
Tracks the 34 cycle-3 IDs (GWC-31+, WRK-32+, IPC-33+, SEC-37+, CLI-46+, TST-31+) plus the tracker-housekeeping actions the overall asked for. Cycle-1/2 trackers remain the record for their own IDs.
**Status legend:** `Not started` · `In progress` · `Done` · `Blocked (external)` · `Deferred (P2)` · `N/A`.
## Execution model
This plan is executed as **seven parallel batches**, each by one subagent working in an isolated git worktree on its own `fix/c3-<batch>` branch, merged back into `main` by the coordinator in the order listed (lowest conflict surface first). Every batch must: build the affected component(s) on macOS (`NonWindows.slnx` — the x86 worker cannot build here), run only the filtered tests it touches, update docs in the same commit, and cite finding IDs in commit messages (`fix(IPC-34): …`). Worker-side changes (batches E, F) are verified afterwards on windev via `windev-worker-ci.ps1 -Mode test`.
**Model assignment rationale:** `opus` for batches that change concurrency/authorization logic or require reasoning across several interacting files with regression tests; `sonnet` for contained, well-specified edits with a clear recipe (version bumps, doc reconciliation, a two-line worker fix); `haiku` for none — every batch either touches code invariants or needs judgement about stale prose, where the cost of a wrong mechanical sweep exceeds the saving.
| Batch | Branch | Findings | Model | Why this model | Status |
|---|---|---|---|---|---|
| A | `fix/c3-client-versions` | CLI-46 | sonnet | Mechanical five-client bump + README note + one script guard; recipe is fully specified | Done |
| B | `fix/c3-glauth-doc` | TST-31 | sonnet | Reconcile a table against one TOML source of truth, re-derive one default, change an assert to a log | Done |
| C | `fix/c3-tracker-hygiene` | GWC-31 + housekeeping (close GWC-16/IPC-08/IPC-31, refresh TST-06/TST-17, register WRK-31) | sonnet | Cross-file comment/ID sweep needs care about what each ID *means*, but no logic changes | Done (`3619152`) |
| D | `fix/c3-dashboard-acl` | SEC-40, SEC-41, GWC-38 | opus | Authorization semantics, fail-closed decisions, claims re-resolution; each needs a regression test | **Done** (`b177636` GWC-38, `2ef7b93` SEC-41+SEC-40, `8257a03` comment fixup; branch not merged/pushed) |
| E | `fix/c3-ipc-boundaries` | IPC-33, IPC-39, IPC-34, IPC-35 | opus | Changes the WorkerClient command lifecycle, the options validator, and the worker drain budget; race-sensitive | **Done** (`577a0db` IPC-33, `a71663a` IPC-39, `b27035b` IPC-34, `ab41362` IPC-35; branch `fix/c3-ipc-boundaries`, not merged/pushed) — worker half verified on windev (`f2ab9cc`, Worker.Tests 524/524) |
| F | `fix/c3-worker-residuals` | WRK-32, WRK-33 (+WRK-34 opportunistic) | sonnet | Small, precisely located worker edits + doc prose; cannot be built on macOS so the agent's job is a careful, minimal diff | Done (windev x86 build + Worker.Tests 524/524 green on `f2ab9cc`) |
| G | `fix/c3-session-lifecycle` | GWC-32, GWC-34, GWC-37 | opus | Distributor/dispose lifecycle races and an alarm-monitor re-resolve path; needs lock-discipline reasoning | Done (`058d8d5`, `282a0e7`, `1932217`) |
**Out of scope for this execution (recorded, not dropped):**
| ID | Status | Why |
|---|---|---|
| TST-32 | Blocked (external) | Gitea runner registration-token reset is a web-UI action on the Gitea admin page; no repo change. Operator to-do. |
| SEC-37 | Blocked (external) | Family-wide: TLS on the shared GLAuth lives in `scadaproj/infra/glauth/` (separate repo + docker host). Plan separately; after it lands, move the deployed service to `Production`. |
| CLI-47/48/49/50, IPC-36/37, TST-35 | Not started | Activated by the 2026-08-25 P2 continuation below. |
| GWC-33/35, SEC-38/39 | Done | Closed by continuation batch I; regression counts and commits are recorded below. |
| GWC-36, IPC-38, TST-33/34/36 | Done | Closed by continuation batch H; verification and commit are recorded below. |
| WRK-34 | Done | Completed opportunistically in batch F (`3679e59`): watchdog faults use `CommandMethod=Watchdog` and carry the stuck correlation ID in the diagnostic message. Tracker correction recorded by the continuation audit. |
## Batch specifications
### A — CLI-46 client release-train bump (sonnet)
- Bump .NET/Go/Python/Rust clients `0.2.0 → 0.2.1`, Java `0.2.1 → 0.2.2` in every version-bearing file (`pyproject.toml` + `version.py`, `Cargo.toml` workspace + any `version =` consumers, `Directory.Build.props`/csproj `<Version>`, Java `build.gradle`/`gradle.properties`, Go module is tag-driven — update any `Version` const if one exists). Follow `docs/ClientPackaging.md` for the authoritative file list.
- `clients/python/README.md` + `docs/ClientPackaging.md`: note that published pypi 0.2.0 has a broken `connect()` (event-loop binding, fixed by `a8f86b5`) and is superseded by 0.2.1.
- Add a PR-time check to `scripts/check-codegen.ps1` (or a new `scripts/check-client-versions.ps1` wired where the other checks run): fail if any `clients/<lang>` source differs from its last publish tag while the version constant still equals that tag's version. Document in `docs/ClientPackaging.md`.
- Verify: `dotnet build clients/dotnet/...slnx`; `cargo check --workspace`; `python -m pytest` (clients/python); `JAVA_HOME=/opt/homebrew/opt/openjdk@17 gradle test` then **revert** spurious generated-proto churn; `go build ./... && go test ./...`.
### B — TST-31 glauth.md + live-test credential (sonnet)
- Reconcile `glauth.md` against `~/Desktop/scadaproj/infra/glauth/config.toml`: ReadOnly gid, the pre-provisioned users table (drop `readonly/readonly123`, fix `admin` password), the worked example at `:124`. Keep the OPC-UA taxonomy note.
- Re-derive `MXGATEWAY_LIVE_MXACCESS_WRITE_SECURED_PASSWORD` default in `docs/GatewayTesting.md` and the `?? "admin123"` literal in `WorkerLiveMxAccessSmokeTests.cs` from the config; make the test **log** (not assert) whether `AuthenticateUser` succeeded.
- Verify: `dotnet build src/ZB.MOM.WW.MxGateway.NonWindows.slnx` (IntegrationTests compiles on mac? if excluded, note it).
### C — GWC-31 + tracker housekeeping (sonnet)
- Sweep code comments citing unregistered `GWC-30`/`GWC-31` (perf pass) in `WorkerClient.cs`, `GatewayMetrics.cs`, `WorkerClientTests.cs`, worker `WorkerFrameReader/Writer.cs` to `PERF-nn` keyed to task numbers in `docs/plans/2026-08-15-perf-review-remediation.md`; add a PERF-ID table to that plan. Do not touch the genuine GWC-30 (frame-reader prefix) citations if any exist.
- `archreview/REVIEW-PROCESS.md` (or wherever the ID rule lives): state that GWC/WRK/IPC/SEC/CLI/TST prefixes are reserved for archreview registers.
- Cycle-2 tracker (`archreview/2026-07-12/remediation/00-tracking.md` + domain docs): GWC-16 → Won't fix (rationale from cycle-3 10-gateway-core.md), IPC-08 → Done (`7c1ea12`, residual IPC-34), IPC-31 → Done (superseded by GWC-28), refresh TST-06/TST-17 rows, register WRK-31 (interim ID burned by `e913dab`) so it is never reused.
- Verify: `dotnet build src/ZB.MOM.WW.MxGateway.NonWindows.slnx`; `dotnet test --filter FullyQualifiedName~WorkerClientTests`.
### D — SEC-40, SEC-41, GWC-38 (opus)
- SEC-41: `DashboardSessionAcl` re-resolves tags from `LdapGroupClaimType` claims via `DashboardGroupTagMapping.MapGroupsToTags` against current options (as `HubTokenService` does) instead of trusting login-stamped tag claims; keep stamped claims only as fallback when no group claims exist (document). Test: principal with stale tag claim + group whose grant was removed → denied.
- SEC-40: decide **tighten**: `AlarmsHub` gets a dedicated policy requiring a real role claim (anonymous-loopback/Disabled bypass excluded) — and document in `docs/GatewayDashboardDesign.md` ACL section + `docs/GatewayConfiguration.md` GroupToTag row that alarm metadata remains galaxy-wide for authenticated Viewers (central monitor, no per-session provenance). Test for the policy.
- GWC-38: `GatewayBrowseScopeProvider` throws `RpcException(PermissionDenied)` (and logs) when `Current` is null **and** API-key auth is enabled; unchanged when auth mode is Disabled. Test both branches.
- Verify: build Server; `dotnet test --filter "FullyQualifiedName~DashboardSessionAcl|FullyQualifiedName~AlarmsHub|FullyQualifiedName~BrowseScope"` (adjust to real class names).
**Outcome (done, branch `fix/c3-dashboard-acl`, commits `b177636`, `2ef7b93`, `8257a03`; not merged, not pushed).** Decisions worth carrying forward:
- **SEC-41 fallback:** the ACL re-resolves from `mxgateway:ldap_group` claims when the principal has any, and falls back to the stamped `zb:dashboardtag` claims only when it has none. That is exactly the hub-token principal — `HubTokenService.Validate` rehydrates roles and tags but **not** groups, so re-resolution already happened at mint. The fallback cannot re-open the cookie gap, because `DashboardAuthenticator` stamps both claim kinds from one group list.
- **SEC-40 requirement type:** new `AuthenticatedDashboardRoleRequirement` + `AuthenticatedDashboardRoleHandler` and a `MxGateway.Dashboard.Alarms` policy. `DashboardAuthorizationHandler` is `AuthorizationHandler<DashboardAuthorizationRequirement>`, so it is never invoked for the new type — the same structural device the Secrets UI's `secrets:manage` already relies on. Both hub schemes are retained on the policy.
- **SEC-40 scope answer:** the `/alarms` Blazor page carries the SAME policy (it reads the central monitor in process — gating only the hub left the disclosure one navigation away), and the side-rail Alarms link is wrapped in an `AuthorizeView` on that policy. `AlarmsHubPublisher` is unchanged and stays unfiltered by design: audience is decided at hub admission and the feed carries no per-session provenance.
- **GWC-38:** `PermissionDenied` + warning only when `Authentication:Mode=ApiKey`; `Mode=Disabled` still returns unscoped. `GalaxyRepositoryHostWiringTests` phase 1 was changed to push an *unconstrained identity* rather than none — that is production's real unscoped case.
- New/changed tests: `AlarmFeedAuthorizationTests` (new, 7), `DashboardSessionAclTests` (+5), `GatewayBrowseScopeProviderTests` (+2), `SecretsNavRenderTests` (+2, and it must register the new policy — an unregistered policy name *throws* inside `AuthorizeView` rather than denying). Filtered run over the touched classes: 92 passed / 0 failed; `NonWindows.slnx` builds zero-warning.
### E — IPC-33, IPC-39, IPC-34, IPC-35 (opus)
- IPC-33 option (a): `GatewayOptionsValidator` adds the upper coupling `Worker:MaxMessageBytes ≤ Protocol:MaxGrpcMessageBytes + 2×reserve`; worker `ResolveDrainReplyByteBudget` becomes `MaxMessageBytes − 2×DrainReplyFrameHeadroomBytes` (floor `MaxMessageBytes/2`); update `docs/GatewayConfiguration.md`, `docs/WorkerFrameProtocol.md`. Test: `GatewayOptionsTests` case for the new bound.
- IPC-39: pre-check compares against `MaxMessageBytes − MaxSequenceEncodingBytes (11)`; fix the comment direction.
- IPC-34: caller-cancellation arms call `TrySendCancelForAbandonedCommand` (renamed) gated on `removed`, skipped when `_stopCts` is the source; new `WorkerClientErrorCode.CallerCancelled` replacing the `GatewayShutdown` mislabel, mapped to `Cancelled` in the gRPC layer; update `docs/Grpc.md` mapping line, `gateway.md:913-919`, `docs/GatewayProcessDesign.md:596-602`. Test: twin of the timeout test driven by the caller token.
- IPC-35: `ReadHandshakeEnvelopeAsync` special-cases `WorkerFault` → `WorkerClientException(WorkerFaulted, CreateWorkerFaultMessage(...))`. Test: fake writes `WorkerFault{MxaccessCreationFailed}` instead of `WorkerReady`, category text surfaces.
- Verify: build Server; `dotnet test --filter "FullyQualifiedName~WorkerClientTests|FullyQualifiedName~GatewayOptions"`. Worker half → windev afterwards.
### F — WRK-32, WRK-33 (sonnet)
- WRK-33: `ObserveFaultWhenever(ObserveCommandTaskAsync(commandTask));` at `WorkerPipeSession.cs:1235` (or catch-all-and-log in the observer).
- WRK-32: one `pumpStep()` before capturing the `CurrentVersion` baseline in `MxAccessCommandExecutor.cs`; rewrite the executor doc comment (`:996-1003`) to state the recency-based contract; add the same to `docs/MxAccessWorkerInstanceDesign.md` write-completion section and the reply-field comment in `mxaccess_gateway.proto` (**comment-only proto edit → regenerate all generated artifacts in the same commit**, per the repo rule; if regen tooling is unavailable on macOS, skip the proto comment and say so).
- WRK-34 if trivial: `CreateFault(StaHung, "Watchdog", …)` with the correlation id moved into the message.
- Cannot build on macOS: keep diff minimal, net48-safe (no init-only/positional records). Verified on windev afterwards.
### G — GWC-32, GWC-34, GWC-37 (opus)
- GWC-32: `DisposeAsync` transitions `_state` to `Closed` under `_syncRoot` before completing channels (or `if (_disposed || IsTerminalState()) return;` in `StageWorkerEvent`); update the XML doc to name all three completion paths. Test: dispose a Ready client mid-dispatch → no `ProtocolViolation` fault/metric.
- GWC-34: `_eventDistributorDisposed` flag set under `_syncRoot` when nulling; `EnsureDistributorCreated` throws `SessionManagerException(SessionNotReady)` when set. Test: attach after dispose → throws, no silent hang.
- GWC-37: on `ApplyProviderModeChangeAsync(toMode == Subtag)` re-resolve the watch list and re-subscribe; additionally surface "zero targets resolved while mode can need them" on the provider status (`Degraded` + reason). Update `docs/` alarm-subtag section. Test with the fake worker: GR discovery fails at start, succeeds at failover → watch list non-empty after failover.
- Verify: build Server; `dotnet test --filter "FullyQualifiedName~WorkerClientTests|FullyQualifiedName~GatewaySession|FullyQualifiedName~AlarmMonitor|FullyQualifiedName~AlarmWatchList"`.
- **Outcome (Done):** `058d8d5` GWC-32, `282a0e7` GWC-34, `1932217` GWC-37 on `fix/c3-session-lifecycle`. Filtered run 176/176 green; `NonWindows.slnx` clean.
- GWC-37 **does not re-send** the watch-list on the failover edge. The worker's `AlarmCommandHandler.Subscribe` rejects a second `SubscribeAlarms` while subscribed, and the only expressible replacement (`UnsubscribeAlarms` + `SubscribeAlarms`) rebuilds `FailoverAlarmConsumer` — restarting it on the primary that just failed and discarding the warm standby — so it would flap the feed rather than repair it. The re-resolve happens and any drift is reported on the same status frame as the failover; a monitor restart is the supported way to apply a new list. Adding a watch-list-update worker command is a possible future item, not done here.
- GWC-37 changes `IAlarmWatchListResolver.ResolveAsync` to return `AlarmWatchListResolution`, and gives `Degraded && Mode==ALARMMGR` a real meaning (empty/undiscoverable standby) with a new dashboard label `Alarm Manager (no subtag standby)`.
- GWC-32's dispose path now also records `WorkerStopped("dispose")` via the existing once-latch, closing a `workers_running` gauge leak on the dispose-a-Ready-client path. GWC-12's double-dispose interlock got a natural home (the `_disposed` claim moved under `_syncRoot`) but is **not** claimed as fixed.
## Merge order
C is merged **last** (its comment sweep touches the same files E and G edit). Order: A, B, F, D, G, E, C. After merge: full `NonWindows` build + one full gateway test run; then windev worker CI for E+F.
## Integration gate (2026-08-20)
All seven branches merged into `main` (A `ee68cc3`, B `f59c34a`, C `f2ab9cc`, D `9f0c3fd`, E `cfbc995`, F `aa07497`, G `c6650c1`). Merged tree: `NonWindows.slnx` 0 warnings; full gateway suite **1183/1183** passed; windev `windev-worker-ci.ps1 -Sha f2ab9cc -Mode test`: x86 Worker build succeeded, `Worker.Tests` **524 passed / 11 skipped / 0 failed**. Follow-ups surfaced by the batches: a worker command to refresh a live alarm subscription's watch-list (would let GWC-37's re-resolve be applied, not just reported); WRK-32's proto reply-field comment deferred until the next proto regen wave; the five clients are bumped but not yet published.
## P2 continuation (planned 2026-08-25)
Current-state audit: refreshed `origin/main` and re-checked every deferred finding and integration-gate follow-up. Sixteen of the seventeen P2 findings remain open; WRK-34 was already completed by `3679e59`. All three follow-ups remain open. Exact target client coordinates are still absent from Gitea (HTTP 404), and `clients/go/v0.2.1` does not exist.
Approved design: [`../../../docs/plans/2026-08-25-cycle-3-p2-followups-design.md`](../../../docs/plans/2026-08-25-cycle-3-p2-followups-design.md) (`30548da`).
Execution plan: [`../../../docs/plans/2026-08-25-cycle-3-p2-followups.md`](../../../docs/plans/2026-08-25-cycle-3-p2-followups.md).
The continuation runs serially in the isolated `.worktrees/c3-p2-followups` worktree on `codex/c3-p2-followups`, with a tracker update at every batch boundary. Codex equivalents are selected from the implementation risk: `gpt-5.6-terra`/medium for contained documentation, CI, and client work; `gpt-5.6-sol`/high for concurrency, authorization, protocol, and live alarm lifecycle changes.
| Batch | Findings / follow-ups | Codex model | Status | Exit gate |
|---|---|---|---|---|
| H — tracker and currency | WRK-34 record, GWC-36, IPC-38, TST-33/34/36 | `gpt-5.6-terra` medium | Done | Currency greps clean; README check in portable CI; NonWindows build clean |
| I — gateway correctness/security | GWC-33/35, SEC-38/39 | `gpt-5.6-sol` high | Done | Focused regression tests green; NonWindows build clean |
| J — secondary contracts | IPC-36/37 | `gpt-5.6-sol` high | Done | Shared launch constants and descriptor parity tests green |
| K — client alarm parity | CLI-47/48/49/50 | `gpt-5.6-terra` high | Done | All five client suites green |
| L — live watch-list/proto wave | watch-list refresh follow-up + WRK-32 proto comment | `gpt-5.6-sol` high | Done | Codegen clean; portable alarm/contract tests green; windev Worker.Tests green |
| M — Windows gateway CI | TST-35 | `gpt-5.6-terra` medium | Done | windev `live` mode proves Worker, gateway, full-slnx, and live smoke tiers |
| N — integration and release | publish five clients + Contracts | `gpt-5.6-sol` high | Done | Full local/windev gates; merged `main`; registry coordinates and Go tag verified |
### Continuation tasklist
#### H — tracker and currency
- [x] Correct WRK-34 from Deferred to Done with commit `3679e59`.
- [x] GWC-36: correct `EventSubscriberReplayAttachment.OldestAvailableSequence` semantics.
- [x] IPC-38: add public `DrainEvents` behavior to `docs/Grpc.md` and correct the cycle-2 omission rationale.
- [x] TST-33: reconcile the five stale/self-contradictory `stillpending.md` claims and mark its live-status boundary.
- [x] TST-34: run `code-reviews/regen-readme.py --check` in portable CI and qualify the interpreter note.
- [x] TST-36: replace prose ``event`` scope references with canonical ``events:read``.
- [x] Record batch-H commit and verification evidence here.
Batch-H verification (2026-08-25): `python3 code-reviews/regen-readme.py --check`
reported the generated README current and consistent; the TST-33 and live-document
TST-36 stale-pattern greps returned no matches; `docs/Grpc.md` contains the public
`DrainEvents` contract; and `dotnet build src/ZB.MOM.WW.MxGateway.NonWindows.slnx`
succeeded with **0 warnings / 0 errors** in 1.59 s. Batch commit: `92aae28`.
#### I — gateway correctness and security
- [x] GWC-33: unregister terminal clients from the event-queue-depth gauge without shortening fault retention.
- [x] GWC-35: repair acknowledged-to-active snapshot deltas with a Raise transition and instance identity.
- [x] SEC-38: feed/consult the aggregate for valid key IDs that overflow the peer partition cap.
- [x] SEC-39: prove a null effective key ID cannot reset the shared fallback partition.
- [x] Run focused tests and clean NonWindows build; record counts and commits here.
Batch-I verification (2026-08-25): the combined `WorkerClientTests`,
`GatewayAlarmMonitorAttachOrderTests`, and `ApiKeyFailureLimiterTests` filter passed
**55/55** tests in 12 s; `dotnet build src/ZB.MOM.WW.MxGateway.NonWindows.slnx`
succeeded with **0 warnings / 0 errors** in 3.42 s. Finding commits: GWC-33
`b0156c6`, GWC-35 `7807201`, and SEC-38/SEC-39 `1875426`.
#### J — secondary contracts
- [x] IPC-36: centralize all six worker-launch environment names in Contracts and consume them on both sides.
- [x] IPC-36: document variable/config/default mapping and test launcher completeness.
- [x] IPC-37: add normalized retained-vs-served Galaxy descriptor parity test.
- [x] Run focused tests and clean NonWindows build; record counts and commits here.
Batch-J verification (2026-08-25): the combined `WorkerProcessLauncherTests` and
`GalaxyContractParityTests` filter passed **10/10** tests in 1 s; the descriptor
mutation proof identified the changed protobuf message; and `dotnet build
src/ZB.MOM.WW.MxGateway.NonWindows.slnx` succeeded with **0 warnings / 0
errors** in 2.20 s. IPC-36/IPC-37 commit: `d6c5a11`.
#### K — client alarm-feed parity
- [x] CLI-47: make .NET text status flags explicit and document deliberate JSON differences.
- [x] CLI-48: test provider/snapshot status arms in .NET, Go, and Java; add Go stream-alarm scaffold.
- [x] CLI-49: export Go aliases for `AlarmProviderStatus` and `AlarmSnapshotStatus`; fix five-arm documentation.
- [x] CLI-50: stream Python alarm rows incrementally with an unbounded default and bounded opt-in.
- [x] Run .NET, Go, Python, Rust, and Java client suites; record counts and commits here.
Batch-K verification (2026-08-25): `dotnet test
clients/dotnet/ZB.MOM.WW.MxGateway.Client.slnx` passed **134/134** portable
tests (1 live test skipped); `go test ./...` passed both tested packages;
`python -m pytest` passed **169/169** portable tests (1 opt-in TLS test
skipped); `cargo test --workspace` passed **108/108** tests; and
`JAVA_HOME=/opt/homebrew/opt/openjdk@17 gradle test` passed **132/132** tests.
CLI-47/CLI-48/CLI-49/CLI-50 commit: `5cdb3da`.
#### L — live watch-list refresh and protobuf wave
- [x] Add explicit fallback-enabled subscription semantics for an initially empty discovered list.
- [x] Add and round-trip `UpdateAlarmWatchListCommand` and its `MxCommandKind`.
- [x] Route the command through the worker executor and alarm handler on the STA.
- [x] Build/subscribe/attach a replacement standby before swap; preserve the old standby on failure.
- [x] Preserve current primary/subtag mode during a successful replacement.
- [x] Re-resolve on the existing gateway reconciliation cadence and send only material changes/recovery.
- [x] Refresh immediately at failover edge, update acknowledged fingerprint only after worker OK, and reconcile after success.
- [x] Expand WRK-32's public reply-field comment to the sequential stale-callback/recency case.
- [x] Replace IPC-36's internal env-var name in the proto comment with the public configuration key.
- [x] Regenerate Contracts and all client generated trees once; run codegen and all affected suites.
- [x] Verify x86 Worker build and Worker.Tests on windev; record SHA/counts here.
Batch-L verification (2026-08-25): `pwsh scripts/check-codegen.ps1` passed all
four descriptor/C#/Rust/Go/Python freshness checks, and Java
`checkGeneratedClean` passed. The exact portable alarm/contract/descriptor
filter passed **77/77**; `NonWindows.slnx` built with **0 warnings / 0 errors**;
the .NET, Go, Python, Rust, and Java client suites were green (respectively
**134 passed / 1 live skip**, both Go packages, **169 passed / 1 opt-in TLS
skip**, **108 passed**, and **132 passed**). Windev `test` mode at
`986c6c0ce4f02c62b4f59ec44780de5af6d291b8` built the x86/net48 Worker with
**0 warnings / 0 errors** and passed Worker.Tests **529 passed / 11 opt-in
skips / 0 failed**. Feature/proto commit: `484028e`; Batch-J x86 compile
follow-up: `986c6c0`.
#### M — Windows gateway CI
- [x] TST-35: add the serialized net10 gateway suite to windev `live` mode.
- [x] Update `docs/GatewayTesting.md` and `scripts/ci/README.md` with order/failure semantics.
- [x] Execute `live` mode on windev and record Worker, gateway, full-slnx, and live-smoke evidence here.
Batch-M verification (2026-08-25): windev `live` mode at exact commit
`cc143b52cf04be0b2f08350922a987b2e250bc1d` completed the serialized sequence in
**4m45s** with remote exit code 0. The x86/net48 Worker build succeeded with **0
warnings / 0 errors** in 22.59 s; Worker.Tests passed **529**, skipped **11**
opt-in probes, and failed **0**; the full Windows `slnx` build succeeded with **0
warnings / 0 errors** in 43.24 s; the net10 gateway suite passed **1193/1193**
in 53 s; and the live-MXAccess smoke passed **8/8** in 1m46s. TST-35 commit:
`cc143b5`.
#### N — integration and release
- [x] Run `git diff --check`, codegen, client-version, README, NonWindows build, and full gateway suite gates.
- [x] Run all five client suites on the final candidate.
- [x] Run final windev `live` gate at the exact candidate SHA.
- [x] Merge/push the verified continuation to `main`; wait for green CI.
- [x] Re-query all package coordinates and Go tag immediately before publish; abort on collision.
- [x] Publish .NET Client + Contracts 0.2.1, Python 0.2.1, Rust 0.2.1, Java 0.2.2.
- [x] Create/push `clients/go/v0.2.1` at the exact release commit.
- [x] Verify every registry coordinate and Go module/tag, update `docs/ClientPackaging.md`, and record immutable release evidence here.
Batch-N portable verification (2026-08-25): `git diff --check`, codegen freshness,
client-version freshness, and the generated README check all passed without changing
the worktree. `NonWindows.slnx` built with **0 warnings / 0 errors**; the gateway
suite passed **1193/1193**, and the portable integration suite passed **5** with
**19** live-only skips. The final .NET, Go, Python, Rust, and Java client gates were
green (respectively **134 passed / 1 live skip**, all Go packages built/tested with
clean `gofmt`, **169 passed / 1 opt-in TLS skip** from a clean virtual environment,
**108 passed** plus clean `cargo fmt`/Clippy, and **132 passed / 0 failed** with the
generated Java tree unchanged).
Batch-N integration/release verification (2026-08-25): `main` was integrated and
released at exact commit `5776021970cbbe59813ba57579abe00d9fc61a46`; Gitea CI
run **783** concluded success. Windev `live` mode at that SHA built the x86/net48
Worker with **0 warnings / 0 errors**, passed Worker.Tests **529 passed / 11
opt-in skips / 0 failed**, built the full Windows solution with **0 warnings / 0
errors**, passed the Windows gateway suite **1193/1193**, and passed the live
MXAccess smoke **8/8**. One preceding full-suite attempt hit a transient fake-worker
disconnect in the reconnect/replay test; the test then passed **50/50** isolated
macOS runs and **20/20** isolated windev runs before the clean full-gate pass.
Immediately before publication, all five package coordinates returned HTTP 404
and `clients/go/v0.2.1` was absent locally/remotely. The published coordinates are
NuGet `ZB.MOM.WW.MxGateway.Client` **0.2.1** and
`ZB.MOM.WW.MxGateway.Contracts` **0.2.1**, PyPI
`zb-mom-ww-mxaccess-gateway-client` **0.2.1**, Cargo
`zb-mom-ww-mxgateway-client` **0.2.1**, and Maven
`com.zb.mom.ww.mxgateway:zb-mom-ww-mxgateway-client` **0.2.2**. Each exact Gitea
package API coordinate returned HTTP 200 after publication. Annotated Go tag
`clients/go/v0.2.1` resolves locally, remotely, and through `go list -m` to
`5776021970cbbe59813ba57579abe00d9fc61a46`.