Files
mxaccessgw/archreview/60-testing-docs-gaps.md
T
Joseph Doherty 59856b8c63 docs(archreview): add architecture review + per-domain remediation designs and tracker
Adds the 2026-07-08 architecture review (00-overall + six domain reports)
and a remediation/ tree: one design+implementation doc per domain covering
every finding, plus 00-tracking.md as the master progress tracker.

- 153 findings with stable IDs (GWC/WRK/IPC/SEC/CLI/TST), each with
  design rationale, implementation steps, tests, docs, and verification.
- Tracker rolls findings up by severity and P0/P1/P2 roadmap tier, records
  cross-cutting clusters and per-finding status (all Not started).
- Planning docs only; no source changes.
2026-07-09 00:39:00 -04:00

88 lines
24 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.
# Testing, Documentation & Underdeveloped Areas — Architecture Review
## Scope & method
This review covers test architecture (`src/ZB.MOM.WW.MxGateway.Tests`, `src/ZB.MOM.WW.MxGateway.Worker.Tests`, `src/ZB.MOM.WW.MxGateway.IntegrationTests`, `scripts/`), documentation currency (`gateway.md`, `docs/Sessions.md`, `docs/GatewayConfiguration.md`, `docs/DesignDecisions.md`, `docs/GatewayTesting.md`, CLAUDE.md, repo-root working artifacts), and repo-wide underdeveloped areas (TODO sweep, half-shipped features, operational gaps). Method is static reading of code and docs on the macOS tree; no test suites were run. Generated code, `bin/`, and `obj/` are excluded. All paths are relative to `/Users/dohertj2/Desktop/MxAccessGateway`.
## Executive summary
- Test coverage of the gateway core is strong and unusually well-layered: 46 gateway test classes, 34 worker test classes, and an 8-scenario opt-in live MXAccess suite map cleanly onto session lifecycle, pipe framing, event fan-out/replay, auth, and dashboard. The `FakeWorkerHarness` reuses the production `WorkerFrameReader`/`WorkerFrameWriter`, so fake tests exercise real frame validation rather than a mock transport.
- There is **no CI configuration anywhere in the repository** — no `.github/`, `.gitea/`, or pipeline files. Every documented verification step (per-task filtered `dotnet test`, five client toolchains, opt-in live matrices) is manual operator discipline; nothing prevents a cross-component regression from landing on `main` untested.
- The session-resilience epic is half-shipped: 12 of 28 tasks are merged (`oldtasks.md`), leaving reconnect owner re-validation (a security gap), client-side `ReplayGap` handling in all five clients, per-session dashboard ACL, and orphan-worker reattach pending. The server emits a reconnect protocol no client yet understands.
- Fake-vs-real fidelity has one structural blind spot by design: `FakeWorkerHarness` cannot simulate STA behavior, COM faults, process crash, or MXAccess semantics — those live only in the opt-in `WorkerLiveMxAccessSmokeTests` and dev-rig probes, which never run automatically.
- Timing-based tests are mostly disciplined (`ManualTimeProvider`, cancellation-token-bounded polls), but a handful of real-clock sleeps followed by negative assertions remain (`WorkerClientTests.cs:433`, `SessionManagerTests.cs:402`) and are latent flakes under load. The known macOS `SessionWorkerClientFactoryFakeWorkerTests` timeout failures are pre-existing and environmental.
- Documentation currency is better than typical: `docs/GatewayConfiguration.md` defaults match `Configuration/*.cs` exactly, and `docs/Sessions.md`/`docs/DesignDecisions.md` reflect the shipped reconnect/fan-out behavior. The stale spots are concentrated in `gateway.md` (design-era `WorkerEnvelope` sketch, single-subscriber policy line, never-built `Session` bidi RPC) and one materially wrong sentence in CLAUDE.md about default retention.
- Repo-root hygiene is mixed: `stillpending.md` and `oldtasks.md` are tracked, annotated snapshots that still serve as the de-facto backlog (there is no issue tracker in evidence); the five untracked `*-docs-{issues,fixed,final}.md` artifacts (15k+ lines) are dead local working files; `code-reviews/` + `REVIEW-PROCESS.md` are a completed, self-consistent review system with zero open findings.
- Operationally the gateway is under-instrumented for a service: one health check (auth-store) with no Galaxy SQL, LDAP, alarm-monitor, or worker-launchability checks; hand-rolled NSSM deployment with config living in service env vars outside the repo; and no version stamping on the server/worker assemblies while client packages drift (Java 0.2.0 vs 0.1.2 everywhere else).
## Findings
### Coverage map
| Component | Unit | Fake-worker integration | Live integration | Gap notes |
|---|---|---|---|---|
| Session lifecycle (open/close/lease/detach-grace) | `SessionManagerTests`, `GatewaySessionTests` | `GatewayEndToEndFakeWorkerSmokeTests`, `SessionWorkerClientFactoryFakeWorkerTests` | `WorkerLiveMxAccessSmokeTests` | `SessionLeaseMonitorHostedService` timer loop itself untested (sweep logic covered via `CloseExpiredLeasesAsync` tests at `SessionManagerTests.cs:859-930`) |
| Worker launch / validation / orphan cleanup | `WorkerProcessLauncherTests`, `WorkerExecutableValidatorTests`, `OrphanWorkerTerminatorTests` | scripted fake launcher | live x86 launch | `OrphanWorkerCleanupHostedService` wrapper untested; reattach not implemented (epic Phase 5) |
| Pipe framing / IPC envelope | `WorkerFrameProtocolTests` (both projects), `WorkerClientTests`, `WorkerPipeClientTests`, `WorkerPipeSessionTests` | `FakeWorkerHarnessTests` incl. malformed/oversized frames | live | good — malformed payloads and oversized headers explicitly scripted |
| Worker crash / heartbeat / fault | `WorkerClientTests` with `ManualTimeProvider` | fault frames via harness | abnormal-exit kill test | real process crash covered only opt-in |
| Event streaming / fan-out / replay | `EventStreamServiceTests` (15 tests), `SessionEventDistributorTests` (22) | `GatewayEndToEndMultiSubscriberTests` | stream phase of live smoke | no end-to-end reconnect/replay fake-worker test (epic Task 15 pending); no client handles `ReplayGap` (Task 14) |
| Backpressure | distributor/stream tests | overflow via harness | e2e script drain-loop workaround | policy interplay (`FailFast` vs multi-subscriber degrade) unit-covered only |
| Auth (API keys, scopes, audit) | `ApiKeyVerifierTests`, `SqliteAuthStoreTests`, `GatewayGrpcAuthorizationInterceptorTests`, `ConstraintEnforcerTests`, audit tests | — | e2e script auth-rejection phase (opt-in) | good |
| Dashboard | ~17 test classes under `Gateway/Dashboard/` | — | `DashboardLdapLiveTests` (opt-in) | `DashboardLiveDataService`, `EventsHub` hub methods, `AlarmsHubPublisher`, `DashboardHubConnectionFactory` have no direct tests |
| Alarms (consumer, failover, monitor) | `FailoverAlarmConsumerTests`, `SubtagAlarmStateMachineTests`, `GatewayAlarmMonitorProviderModeTests`, `AlarmFailoverEndToEndTests` | fake alarm service | dev-rig probes (`[Fact(Skip=...)]`) | live failover undrivable on rig; `provider_switches` metric never live-exercised; subtag Clear unvalidated |
| Worker STA / COM / conversion | `StaMessagePumpTests`, `StaCommandDispatcherTests`, `MxAccessCommandExecutorTests`, `VariantConverterTests`, etc. | — | `MxAccessLiveComCreationTests` (opt-in) | multi-sample `OnBufferedDataChange` conversion never observed live (`stillpending.md` §3.2) |
| Galaxy browse | `GalaxyFilterInputSafetyTests`, `GalaxyRepositoryHostWiringTests` | — | `GalaxyRepositoryLiveTests` (opt-in) | core browse logic now lives in the external `ZB.MOM.WW.GalaxyRepository` package |
| Clients (×5) | dotnet 13 / go 8 / rust 3 / python 13 / java 7 test files | — | `scripts/run-client-e2e-tests.ps1` only | no per-client wire-level integration tests; all client↔gateway behavior unverified in default runs |
### Test Coverage
- **High — Reconnect/replay has no end-to-end test and no client-side consumer.** The server emits the `ReplayGap` sentinel and replays the ring (`docs/Sessions.md` "Reconnect and replay"), unit-covered in `src/ZB.MOM.WW.MxGateway.Tests/Gateway/Grpc/EventStreamServiceTests.cs` and `MxAccessGatewayServiceTests.cs`, but the epic's fake-worker reconnect integration test (Task 15) and client `ReplayGap` handling in all five clients (Task 14) are pending per `oldtasks.md:40-44`. Impact: the shipped, default-on reconnect protocol (DetachGraceSeconds=30, ReplayBufferCapacity=1024) is unproven end-to-end and unusable by every official client. Recommendation: land Tasks 1415 before advertising reconnect; until then document it as server-only.
- **High — Reconnect owner re-validation is not implemented.** Epic Task 13 ("Owner re-validation on reconnect", `oldtasks.md:40`) is pending, so nothing ties a resuming `StreamEvents` call to the API key that opened the session beyond the `event` scope and knowledge of the session id. Impact: with fan-out or detach-grace enabled, any event-scoped key that learns a session id can attach to another key's session and receive its replayed data. Recommendation: prioritize Task 13; it is a security control, not a resilience feature.
- **Medium — Real-worker control/COM behavior is only verified opt-in.** All eleven late-added command kinds are unit-tested against fakes and live-verified once on the dev rig (`stillpending.md` §1.1), but the default suite exercises `Ping`/`GetWorkerInfo`/`DrainEvents`/`ShutdownWorker` only through `FakeWorkerHarness.RespondToControlCommandAsync` (`src/ZB.MOM.WW.MxGateway.Tests/Gateway/Workers/Fakes/FakeWorkerHarness.cs:417-513`), which returns canned replies. Impact: a worker-side regression in these paths is invisible until someone sets `MXGATEWAY_RUN_LIVE_MXACCESS_TESTS=1`. Recommendation: schedule the live suite on the windev box on a cadence (see CI finding).
- **Medium — Dashboard live-data path untested.** `DashboardLiveDataService` (owns the shared lazily-opened gateway session backing `/browse` live values, per `gateway.md:128-131`) has no test class; `EventsHub`/`AlarmsHubPublisher` hub methods are likewise untested. Impact: the one dashboard component that holds a real worker session and can fault it has no regression net. Recommendation: add a fake-worker-backed test for session reuse, fault recovery, and disposal.
- **Low — Hosted-service wrappers untested.** `SessionLeaseMonitorHostedService` and `OrphanWorkerCleanupHostedService` delegate to well-tested cores but their timer/startup wiring has no tests. Impact: low; failure mode is obvious at startup.
### Test Quality
- **Medium — Real-clock sleeps with negative assertions are latent flakes.** `src/ZB.MOM.WW.MxGateway.Tests/Gateway/Workers/WorkerClientTests.cs:433` sleeps 150 ms of wall time ("give the heartbeat monitor a few real check-intervals") then asserts state did *not* change; `src/ZB.MOM.WW.MxGateway.Tests/Gateway/Sessions/SessionManagerTests.cs:402` races a 50 ms delayed state flip against a 500 ms bounded wait. 47 `Thread.Sleep`/`Task.Delay` occurrences exist across 22 test files; most are benign (cancellation-bounded polls, `Timeout.InfiniteTimeSpan` hang simulators, `ManualTimeProvider` used for the actual clock), but the fixed-real-time ones can pass spuriously or fail under load. Impact: intermittent failures erode trust in an already manual test regime. Recommendation: convert the negative-assertion sleeps to `ManualTimeProvider`-driven check-interval pumping or polling-until-stable.
- **Medium — Full-suite orphaned testhost processes remain an unfixed hygiene defect.** CLAUDE.md ("Source Update Workflow") documents that the full gateway suite "leaves orphaned testhost processes" and mandates filtered runs as a workaround. Impact: the workaround is procedural, so any future CI or an unaware contributor inherits zombie processes; it also suggests undisposed pipe servers or hosted services in some fixture. Recommendation: identify the leaking fixture (likely a `NamedPipeServerStream` or hosted-service test without disposal) rather than institutionalizing the workaround. The macOS `SessionWorkerClientFactoryFakeWorkerTests` timeout-message failures are pre-existing platform noise (wrong timeout message on macOS) and are correctly excluded from this finding.
- **Low — `FakeWorkerHarness` fidelity is good within its charter.** It shares `WorkerFrameReader`/`WorkerFrameWriter`/`WorkerEnvelope` with production (`FakeWorkerHarness.cs:36-37`), scripts malformed payloads and oversized headers (`:519-553`), heartbeats, faults, and shutdown acks, and mirrors real control-command reply shapes. What it structurally cannot represent — STA pumping, COM HRESULT semantics, process exit codes, event timing under load — is honestly delegated to the live suite by `docs/GatewayTesting.md`. No action needed beyond keeping its canned replies in lockstep with `WorkerPipeSession` (the §1.1 history shows this drifted once: green fakes masked an unimplemented real worker for months).
- **Low — The e2e script embeds a workaround for a real product sharp edge.** `scripts/run-client-e2e-tests.ps1` (1,715 lines) must interleave `StreamEvents` drains every 15 advised tags because advising without a consumer overflows the worker event channel and faults the session under `FailFast` (`docs/GatewayTesting.md` "Client E2E Scripts" phase 3). Impact: the test harness papering over this documents that real clients doing bulk-advise-then-stream will fault sessions. Recommendation: treat this as product feedback (e.g., subscribe-time channel policy), not just a script detail.
### CI & Operations
- **High — No CI exists.** There is no `.github/`, `.gitea/`, `azure-pipelines.yml`, `Jenkinsfile`, or any pipeline file in the repository. `docs/ImplementationPlanIndex.md` names a Gitea repo and a `packaging-and-ops` milestone, but nothing automated runs the .NET, Go, Rust, Python, or Java suites on push. Impact: the multi-language, multi-target (net10/net48-x86) build matrix is exactly the kind that silently breaks; the repo's own history (net48 CS0246 on unregenerated protos, Java generated-file churn) shows cross-component breakage is routine. Recommendation: a minimal Gitea Actions (or runner-on-windev) pipeline that builds `NonWindows.slnx` + runs the gateway suite on every push, with a Windows job for the x86 worker, would catch the majority class of regressions; add a scheduled opt-in live-MXAccess job on the dev rig.
- **Medium — Health checks cover only the auth store.** `src/ZB.MOM.WW.MxGateway.Server/GatewayApplication.cs:71-75` registers a single `AuthStoreHealthCheck`; there is no readiness check for Galaxy SQL reachability, LDAP bind, alarm-monitor session health, or worker-executable presence/launchability, even though each is a documented startup dependency. Impact: `/health` reports ready while the gateway cannot open a session or browse Galaxy. Recommendation: add tagged checks for worker exe validation (cheap, reuses `WorkerExecutableValidator`) and Galaxy/LDAP with caching.
- **Medium — Deployment and upgrade are undocumented in-repo and hand-rolled.** Deployments are NSSM-wrapped services with configuration held in NSSM environment variables (acknowledged in `docs/GatewayConfiguration.md` "Host Endpoints" and the project memory notes); `scripts/` contains packaging and e2e scripts but no deploy/upgrade script, and there is no runbook for the `MxAccessGw``OtOpcUa` service-dependency dance. Impact: deploys are tribal knowledge; a second operator cannot reproduce them from the repo. Recommendation: commit a `docs/Deployment.md` plus a publish/deploy script that captures the NSSM env-var config as code.
- **Medium — No version discipline on server artifacts; client versions drift.** The Server and Worker csproj files carry no `Version` property (assemblies stamp 1.0.0), Contracts is `0.1.2` (`src/ZB.MOM.WW.MxGateway.Contracts/ZB.MOM.WW.MxGateway.Contracts.csproj:10`), Python and Rust clients are `0.1.2` (`clients/python/pyproject.toml:9`, `clients/rust/Cargo.toml:3`), while Java is `0.2.0` (`clients/java/build.gradle:16`) — and the Java bump required a separately-maintained `CLIENT_VERSION` constant (commit `b6a0d90` fixed a mismatch). Impact: support cannot correlate a deployed gateway or a client wheel to a commit; duplicated version constants have already drifted once. Recommendation: single-source versions (Directory.Build.props for the .NET side; generate client version constants at build) and stamp the server assembly informational version with the git SHA.
- **Low — Log rotation is configured but minimal.** `src/ZB.MOM.WW.MxGateway.Server/appsettings.json:8-11` rolls the file sink daily (`logs/mxgateway-.log`, a path relative to the service working directory) with no explicit `retainedFileCountLimit` or size cap (Serilog defaults to 31 files, unbounded size per day). Impact: acceptable, but a high-rate event day can produce an unbounded single file. Recommendation: set `fileSizeLimitBytes` + `rollOnFileSizeLimit` and an absolute log path for the service deployment.
### Documentation Currency
- **Medium — CLAUDE.md misstates the default retention behavior.** CLAUDE.md ("Repository-Specific Conventions") says "Default config preserves the original single-subscriber, no-retention behavior," but the code defaults enable retention: `DetachGraceSeconds = 30` (`src/ZB.MOM.WW.MxGateway.Server/Configuration/SessionOptions.cs:47`) and `ReplayBufferCapacity = 1024` / `ReplayRetentionSeconds = 300` (`Configuration/EventOptions.cs:11-31`). Only `AllowMultipleEventSubscribers` defaults off. Impact: an agent or operator following CLAUDE.md will assume detached sessions die immediately and no events are buffered. Recommendation: reword to "single-subscriber by default; detach-grace and replay retention are on by default."
- **Medium — gateway.md carries design-era sketches that no longer match the wire contract.** The `WorkerEnvelope` snippet (`gateway.md:291-309`) shows `uint64 correlation_id = 4` and body tags `command = 20 … fault = 26` with no `WorkerShutdownAck`; the actual proto uses `string correlation_id = 4` and `gateway_hello = 10 … worker_fault = 20` including `worker_shutdown_ack = 17` (`src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_worker.proto:19-38`). Similarly `gateway.md:738` still states the gateway "allow[s] one active client event subscriber per session, reject[s] a second subscriber" as unconditional policy, contradicted by the config-gated fan-out described later in the same file (`:1177-1180`) and by `docs/Sessions.md`. The public-API sketch (`gateway.md:328-334`) includes `rpc Session(stream ClientMessage)` which was never implemented (the real service has no bidi RPC — `mxaccess_gateway.proto:18-37`). Impact: gateway.md is the mandated pre-change reading; internally inconsistent sections cost every reader a reconciliation pass. Recommendation: replace the envelope sketch with a pointer to the proto, mark the single-subscriber paragraph as the default mode, and label `Session` as unbuilt future work.
- **Low — GatewayConfiguration.md, Sessions.md, DesignDecisions.md, and GatewayTesting.md are current.** Spot-checks confirm every table default in `docs/GatewayConfiguration.md` matches `Configuration/{SessionOptions,EventOptions,DashboardOptions,AuthenticationOptions,WorkerOptions}.cs`; `docs/DesignDecisions.md:63-118` correctly records the superseded reconnect/fan-out constraints; `docs/GatewayTesting.md` "all eight tests" matches the 8 `[LiveMxAccessFact]` attributes in `WorkerLiveMxAccessSmokeTests.cs`. The only nit: the Configuration Shape JSON block (`docs/GatewayConfiguration.md:12-83`) omits `DisableLogin`/`AutoLoginUser`/`CookieName`, `Tls`, and the `Alarms:Fallback` block that the tables below it document.
- **Medium — Repo-root working artifacts need triage.** Classification by evidence:
- `MxAccessGateway-docs-{issues,fixed,final}.md` and `MxGatewayClient-docs-{issues,fixed}.md`**untracked** (gitignored via `*-docs-issues.md`, `.gitignore:152`; the others simply never committed), 15,416 lines in the largest. These are dead local outputs of a finished docs-review pass; delete or move out of the working tree so greps and agents stop tripping over them.
- `stillpending.md` (tracked, last touched 2026-06-25) — a 2026-06-15 audit snapshot with resolution annotations that currently serves as the project's only backlog register. Living but structurally a snapshot; either promote it to a maintained register (drop "Generated: … Commit:" framing) or migrate open items (§1.3, §1.4, §3.x, epic remainder) to the Gitea tracker that `docs/ImplementationPlanIndex.md` describes.
- `oldtasks.md` (tracked, 2026-06-16) — explicitly a "human-readable mirror" of `docs/plans/2026-06-15-session-resilience.md.tasks.json`; keep only until the epic resumes, then delete.
- `REVIEW-PROCESS.md` + `code-reviews/` — a coherent, generated-index review system, all 351 findings resolved, `Server`/`Tests` re-reviewed 2026-06-25. Living docs; keep.
- `A2-galaxyrepository-adoption-handoff.md` — completed-migration handoff referenced from CLAUDE.md; archive candidate under `docs/plans/` once its cross-repo follow-ups land.
### Underdeveloped Areas
- **High — Session-resilience epic is 16/28 tasks unfinished with security-relevant remainder.** Pending per `oldtasks.md:39-63`: owner re-validation (Task 13), client ReplayGap handling (14), reconnect integration test (15), per-session dashboard ACL (1619), orphan-worker reattach (2028, incl. the `EnableOrphanReattach` flag that does not exist yet). Impact: the repo advertises reconnect and fan-out as shipped while their trust boundary and client support are absent. Recommendation: finish Phase 3 (1315) as one unit; re-scope Phases 45 explicitly if they are no longer planned.
- **Medium — Dashboard EventsHub has no per-session ACL.** `src/ZB.MOM.WW.MxGateway.Server/Dashboard/Hubs/EventsHub.cs:39` (`TODO(per-session-acl)`): any authenticated dashboard Viewer may join any `session:{id}` group and observe that session's event stream. The remark documents why this is accepted today; it is the only production-source TODO in the repo. Impact: acceptable for single-tenant dashboards, wrong the moment `GroupToRole` admits low-trust viewers. Recommendation: covered by epic Phase 4; keep the TODO until then.
- **Medium — `Dashboard:ShowTagValues` is a dead flag.** It is bound (`Configuration/DashboardOptions.cs:62`), projected into effective configuration, and displayed on the settings page (`Dashboard/Components/Pages/SettingsPage.razor:65`), but gates no value-display behavior anywhere; `docs/GatewayConfiguration.md:174` calls it "Reserved". Impact: operators toggling it see no effect. Recommendation: implement or remove; a config option that renders on the settings page implies function.
- **Medium — Vendor-gated alarm parity residuals are silently lossy.** The 8-arg `AlarmAckByName` operator `domain`/`full_name` fields are accepted on the wire and discarded (`src/ZB.MOM.WW.MxGateway.Worker/MxAccess/WnWrapAlarmConsumer.cs:261-278`, vendor stub returns 55); `AlarmAckByGUID` is `E_NOTIMPL` on this AVEVA build; the `provider_switches` metric's reason tagging has never been exercised live (`stillpending.md` §1.31.4, §3.43.5). Impact: contract fields that do nothing, invisible to callers. Recommendation: surface the drop in the ack reply diagnostic message until AVEVA implements the stub.
- **Low — The bidirectional `Session` RPC from the original design was never built.** `gateway.md:328-345` presents it as the "best long-term shape"; the service stopped at unary + server-streaming. Impact: none functionally; it is only a docs-currency and roadmap-clarity issue (see Documentation Currency).
- **Low — Client wire behavior has no automated verification.** All five clients have unit tests (13/8/3/13/7 test files for dotnet/go/rust/python/java) but no in-process or containerized gateway integration tests; the only cross-language verification is the operator-run `scripts/run-client-e2e-tests.ps1` against a live rig. Impact: a gateway contract change can pass every default suite and break all five clients (mitigated somewhat by shared-proto codegen and `CrossLanguageSmokeMatrixTests` shape checks). Recommendation: an in-process fake gateway (the Java CLI already has `InProcessGatewayHarness` per `stillpending.md` §8) is the cheapest pattern to replicate per client.
## Top 5 recommendations
1. **Stand up minimal CI.** One pipeline that builds `NonWindows.slnx` and runs the gateway suite per push, a Windows/windev job for the x86 worker + worker tests, and a scheduled opt-in live-MXAccess run. This single change converts most findings above from "process risk" to "caught automatically."
2. **Finish session-resilience Phase 3 (Tasks 1315).** Owner re-validation is a security control for a default-on retention window; client `ReplayGap` handling and the reconnect integration test make the already-shipped server protocol real.
3. **Fix the two documentation defects that actively mislead:** the CLAUDE.md "no-retention default" sentence and gateway.md's stale `WorkerEnvelope`/single-subscriber/`Session` sections (replace sketches with pointers to the protos and `docs/Sessions.md`).
4. **Triage the repo root:** delete the untracked `*-docs-*.md` artifacts, retire `oldtasks.md` into the plan's tasks.json, and either maintain `stillpending.md` as a living register or migrate its open items into the Gitea tracker the plans already assume.
5. **Harden operations:** add Galaxy/LDAP/worker-executable health checks next to the existing auth-store check, commit a deployment runbook + script capturing the NSSM env-var configuration, single-source versions across server, contracts, and clients, and fix (rather than route around) the orphaned-testhost fixture leak.