Files
mxaccessgw/archreview/2026-07-12/remediation/60-testing-docs-gaps.md
T
Joseph Doherty 09f2285957 docs(TST-25,TST-26): mark Done — credentialed windows-x86 green on d769244 (run #37)
Operator bring-up complete and the SSH-driven Windows/x86 CI tier passed
green end-to-end under Gitea Actions run #37: the Linux runner SSHed windev,
checked out the SHA in the isolated C:\build\mxaccessgw-ci clone under lock,
built the x86 Worker and ran Worker.Tests (exit 0); nightly-windev correctly
skipped on the push event. Flip TST-25/TST-26 status rows to Done and record
the bring-up + green-run evidence in the change log.
2026-07-13 10:27:47 -04:00

155 lines
26 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 — Remediation Design & Implementation (2026-07-12 cycle)
Source review: [60-testing-docs-gaps.md](../60-testing-docs-gaps.md) · Baseline: `main` @ `4f5371f` · Generated: 2026-07-13
This cycle's new findings (TST-25..29) are all fallout or residue of the prior remediation: the Windows CI tier was removed after act_runner proved broken (TST-25), the docs describing that tier were not updated in the same commit (TST-26), a flag SEC-25 made live is still documented as dead (TST-27), a handshake field is asserted only on the side CI cannot run (TST-28), and the root-artifact triage from TST-14 needs its retirement decision refreshed (TST-29). Every citation below was re-verified against the working tree on 2026-07-13.
Prior-cycle open findings (TST-05..24 where still open) are tracked in the prior design doc, `archreview/remediation/60-testing-docs-gaps.md`; this doc covers only the new findings. Note TST-25 is the unlock for two of those: a working Windows tier is where the scheduled live-MXAccess smoke (old TST-05) and CI-run client wire tests (old TST-24) land.
## Finding register
| ID | Sev | Tier | Eff | Dep | Status | Title |
|----|-----|------|-----|-----|--------|-------|
| TST-25 | High | P1 | M | — (unlocks TST-05, TST-24) | Done | Windows/x86 test tier has zero automation — restore via SSH-driven windev CI job |
| TST-26 | Medium | P1 (folded into TST-25) | S | TST-25 | Done | docs/GatewayTesting.md, check-codegen.ps1, and ci.yml comments describe removed CI jobs |
| TST-27 | Medium | P1 (doc batch) | S | — | Not started | `ShowTagValues` config row still says "Reserved" after SEC-25 made the flag live |
| TST-28 | Low | P2 | S | relates IPC-02 | Not started | Gateway-side `max_frame_bytes` handshake field untested in the CI-run suite |
| TST-29 | Low | P2 | S | — | Not started | Retire `oldtasks.md` after folding the Phase-5 governance record into DesignDecisions.md; delete root docs-review artifacts |
---
## TST-25 — Windows/x86 test tier has zero automation `High` · `P1` · Effort M
**Finding.** The `windows` and `live-mxaccess` CI jobs were removed in `abb0930` because Gitea act_runner v0.6.1 host-mode is broken on Windows and a `runs-on`-gated job with no matching runner wedges every run in "queued" (removal note: `.gitea/workflows/ci.yml:123-131`). Consequence: the x86/net48 Worker build, all of `src/ZB.MOM.WW.MxGateway.Worker.Tests` — including the tests for remediation-added machinery that exists nowhere else (`WorkerFrameWritePriority` ordering at `Worker.Tests/Ipc/WorkerFrameProtocolTests.cs:353-395`, worker-side `max_frame_bytes` adoption, WnWrap/subtag alarm consumers, `StaRuntime` changes) — and the 8-fact live suite (`src/ZB.MOM.WW.MxGateway.IntegrationTests/WorkerLiveMxAccessSmokeTests.cs`) run only when someone remembers the manual windev worktree process. The portable job's `check-codegen.ps1` Generated-diff check substitutes for the net48 CS0246 guard; nothing substitutes for actually compiling and running the worker tests.
**Impact.** A whole tier of *unit* tests — not just live smoke — is unguarded. A worker-side regression (frame priority inversion, frame-max adoption break, alarm-consumer conversion bug, STA loop change) ships green through CI and is caught, if at all, by hand on windev or in production. This regressed the prior cycle's TST-03/TST-05 posture and blocks old TST-24.
**Design.** Three candidates, weighed against the operational facts (Gitea Actions runs on a co-located `gitea-runner` container on docker host `10.100.0.35`, job containers on network `traefik` resolving `gitea:3000`; windev is Windows 10 at `10.100.0.48`, ssh alias `windev`, passwordless ssh from the Mac, MXAccess installed, and a proven isolated-worktree flow already exists in `C:\build` driven by remote PowerShell via base64 `-EncodedCommand`):
- **(a) Retry a Windows act_runner in host mode with the right labels — rejected.** The failure is not a label problem: act_runner v0.6.1's hostexecutor writes each step's script to a path it then cannot resolve (independent of shell) and cannot stage JS actions, so no label configuration fixes it; Windows containers are impractical for a net48/x86/MXAccess build. And the structural hazard remains: any `runs-on` gate with no live runner sticks queued forever, so a flaky/offline Windows runner would block *every* run. Revisit only if a fixed act_runner release ships — it would then replace the SSH job with a native one.
- **(b) SSH-driven job — recommended.** A Linux CI job (same `ubuntu-latest` runner class as `portable`, so it always schedules) ssh's to windev, fast-forwards an isolated CI clone under `C:\build` to the exact SHA under test, runs the x86 build + `Worker.Tests` there, and propagates the remote exit code through ssh → step → job. This reuses the already-proven windev worktree + `-EncodedCommand` recipe, requires no new runner software on Windows, and cannot wedge the queue (the Linux runner always exists; an unreachable windev fails the job red — loud, not stuck).
- **(c) Trigger split — adopted as part of (b).** Worker tests have nontrivial wall time on windev, and the live suite needs provider state and must never gate a push. Split: **per-push/PR** job runs `dotnet build Worker.csproj -p:Platform=x86` + `dotnet test Worker.Tests -p:Platform=x86` (unit suite — expected minutes; if measured wall time exceeds ~10 min, demote the test step to nightly and keep the per-push x86 build, which alone catches the net48/CS0246/x86 compile class); the **nightly** scheduled job (the existing `cron: '0 6 * * *'`, currently pointless) re-runs the x86 build + full Worker.Tests on `main`, then runs the live-MXAccess smoke with `MXGATEWAY_RUN_LIVE_MXACCESS_TESTS=1`, and optionally a full `src/ZB.MOM.WW.MxGateway.slnx` build (catches Windows-only analyzer diagnostics like xUnit1030 that never surface on macOS/Linux).
Design details for (b):
- **Isolation.** A dedicated CI clone `C:\build\mxaccessgw-ci` — never the Desktop checkout (dirty feature branch, per the established windev rule). The job's inline bootstrap (base64 `-EncodedCommand`) does `git fetch origin && git checkout --force <sha>` in that clone, then invokes the *checked-out* `scripts/ci/windev-worker-ci.ps1 -Sha <sha> -Mode <build|test|live>` so the CI logic itself is versioned with the code under test; only the tiny bootstrap lives in ci.yml.
- **Serialization.** One clone, potentially overlapping runs: `windev-worker-ci.ps1` acquires a lock (`New-Item C:\build\mxaccessgw-ci.lock -ItemType Directory`, retry loop with ~20 min timeout, always released in `finally`) so concurrent pushes queue instead of stomping the worktree. Add a workflow-level `concurrency` group too if the deployed Gitea version honors it — the lock is the guarantee, `concurrency` the nicety.
- **Secrets.** Generate a dedicated ed25519 keypair for CI (do **not** reuse the operator's personal windev key). Private key → Gitea Actions repo secret `WINDEV_SSH_KEY`; pinned host key → secret `WINDEV_SSH_KNOWN_HOSTS` (or a committed `scripts/ci/windev.known_hosts` — host keys are public data; committing is acceptable and greppable). The job writes the key to `~/.ssh/id_ed25519` mode 0600 and connects with `-o BatchMode=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=...`. On windev, append the pub key to `authorized_keys` for the CI account; optionally prefix with a forced `command=` restricting it to the bootstrap script. Per CLAUDE.md, the key must never be echoed into logs.
- **Network precheck.** The job container is on the `traefik` docker network; confirm it has egress to `10.100.0.48:22` (a one-off `nc -z -w5 10.100.0.48 22` step during bring-up; docker bridge networks normally route to the LAN, but the network was created for gitea resolution, not egress, so verify rather than assume).
- **Failure visibility.** Per-push runs are first-class CI jobs — a red windev run is a red commit/PR check exactly like `portable`/`java`, and an unreachable windev **fails** (never skips) so a down Windows tier is loud. Nightly runs surface on the repo's Actions page against `main`; because nobody watches that page, add a final `if: failure()` step to the nightly job that opens (or comments on) a Gitea issue via the API using the built-in actions token, so a red nightly produces a notification artifact. Document in `docs/GatewayTesting.md` that a red `windows-x86` job means the Windows tier — not the change — may be down, and how to check (ssh windev, read `C:\build\mxaccessgw-ci\ci.log`).
**Implementation.**
1. **Windev prep (one-time, manual):** create `C:\build\mxaccessgw-ci` as a fresh clone of the Gitea origin (read-only deploy token or the existing credential store); verify `dotnet build src/ZB.MOM.WW.MxGateway.Worker/ZB.MOM.WW.MxGateway.Worker.csproj -p:Platform=x86` and `dotnet test src/ZB.MOM.WW.MxGateway.Worker.Tests/ZB.MOM.WW.MxGateway.Worker.Tests.csproj -p:Platform=x86` succeed there by hand; record wall time (drives the per-push vs nightly split for the test step). Generate the CI keypair, install the pub key on windev, store `WINDEV_SSH_KEY` (+ known-hosts) in Gitea repo secrets.
2. **New `scripts/ci/windev-worker-ci.ps1`** (runs *on windev*, invoked at the tested SHA): parameters `-Sha`, `-Mode build|test|live`; acquires/releases the lock dir; `build` = x86 Worker build; `test` = build + `dotnet test Worker.Tests -p:Platform=x86`; `live` = `test` + `MXGATEWAY_RUN_LIVE_MXACCESS_TESTS=1 dotnet test src/ZB.MOM.WW.MxGateway.IntegrationTests/... --filter FullyQualifiedName~WorkerLiveMxAccessSmokeTests` (+ optional full-slnx build); exits nonzero on any failure.
3. **New `scripts/ci/run-windev-ci.sh`** (runs *in the Linux job*): writes the key/known-hosts from env, builds the base64 `-EncodedCommand` bootstrap (fetch + checkout `$CI_SHA` in `C:\build\mxaccessgw-ci`, then `powershell -File scripts\ci\windev-worker-ci.ps1 -Sha $CI_SHA -Mode $1`), runs `ssh -o BatchMode=yes ci@10.100.0.48 ...`, exits with the ssh exit code. Do not wrap the remote git calls in `$ErrorActionPreference='Stop'` (git writes progress to stderr — known windev gotcha).
4. **ci.yml — add the per-push job** (same commit as steps 56):
```yaml
windows-x86:
# x86/net48 Worker build + Worker.Tests, executed on windev (10.100.0.48) over SSH.
# Runs on a Linux runner so it always schedules (act_runner host-mode on Windows is
# broken; a runs-on gate with no runner wedges the queue — see docs/GatewayTesting.md).
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Worker x86 build + tests on windev
env:
WINDEV_SSH_KEY: ${{ secrets.WINDEV_SSH_KEY }}
WINDEV_SSH_KNOWN_HOSTS: ${{ secrets.WINDEV_SSH_KNOWN_HOSTS }}
CI_SHA: ${{ github.sha }}
run: ./scripts/ci/run-windev-ci.sh test # demote to `build` if measured wall time > ~10 min
```
5. **ci.yml — add the nightly job** gated `if: github.event_name == 'schedule'` (safe: the gate is an `if`, not an unsatisfiable `runs-on`, so push runs mark it skipped, not queued), `runs-on: ubuntu-latest`, calling `run-windev-ci.sh live`; final `if: failure()` step posts a Gitea issue/comment via the API token. Fix the cron comment at `ci.yml:12-14` to describe what the schedule now actually runs.
6. **ci.yml — replace the removal note** at `:123-131` with a short pointer: host-mode act_runner remains broken; the Windows tier runs via the SSH-driven `windows-x86`/nightly jobs; restore native jobs only with a proven runner.
7. **TST-26 doc edits in the same commit** — see TST-26 below.
8. After the first green nightly, mark old **TST-05** addressed (live smoke is scheduled again) and unblock old **TST-24** (client wire tests can join CI).
**Verification.**
- Push a branch: `portable`, `java`, and `windows-x86` all green; confirm on windev that `C:\build\mxaccessgw-ci` sits at the pushed SHA.
- **Deliberate red:** on a scratch branch, add a failing `[Fact]` to `Worker.Tests` (or an intentional x86-only compile error), push, confirm `windows-x86` turns red and the run fails; revert. This proves exit-code propagation end-to-end, the whole point of the SSH design.
- **Unreachable-host red:** temporarily point the script at a bogus port (or stop sshd), confirm the job fails fast with a clear message rather than hanging or passing.
- **Concurrency:** push two branches back-to-back; confirm the second run's remote step waits on the lock and both complete.
- **Nightly:** temporarily set the cron a few minutes ahead (or trigger the schedule path manually), confirm the live job runs with `MXGATEWAY_RUN_LIVE_MXACCESS_TESTS=1` and, with a forced failure, that the on-failure issue step fires.
- Confirm no secret material appears in the job logs.
---
## TST-26 — Docs describe CI jobs that no longer exist `Medium` · `P1 (folded into TST-25)` · Effort S
**Finding.** `docs/GatewayTesting.md:426-431` still documents the **`windows`** job ("the only host that builds the x86/net48 Worker… the definitive guard for the 'regenerate and commit `Generated/`' rule") and the **`live-mxaccess`** scheduled job — both deleted from `.gitea/workflows/ci.yml` in `abb0930`, which touched only the workflow file (a violation of the repo's docs-change-in-same-commit rule). Same stale claim at `scripts/check-codegen.ps1:17` ("guarded by the Windows CI job, not here") and in the `ci.yml:12-14` cron comment ("Nightly live-MXAccess smoke (windev)") though the schedule currently re-runs only `portable`+`java`.
**Impact.** Actively misleading in the worst direction: a reader concludes the worker build is CI-guarded when it is not, and the "definitive guard" claim is doubly wrong — the surviving guard for the Generated-commit rule is the portable job's `check-codegen.ps1` diff check, not a Windows compile.
**Design.** No standalone commit: these edits land **in the same commit as the TST-25 ci.yml change** (that is the same-commit rule the removal violated). Content rules: describe the jobs that exist (`portable`, `java`, `windows-x86`, nightly), name `check-codegen.ps1`'s Generated-diff check as the primary guard for the regenerate-and-commit rule with the windows-x86 net48 compile as the second line, and document the manual windev fallback (isolated `C:\build` worktree) as the degraded-mode procedure with its trigger condition (SSH job red for infra reasons). If TST-25 is somehow delayed, do **not** hold these edits hostage — an interim commit correcting the docs to the two-job reality plus a mandatory manual cadence (per-merge for worker-touching changes, weekly otherwise) is strictly better than the current false claim; rewrite again when the SSH job lands.
**Implementation.**
1. `docs/GatewayTesting.md:426-431` — replace the `windows` and `live-mxaccess` bullets with: `windows-x86` (SSH-driven, per-push, x86 build + Worker.Tests on windev; failure semantics — red job = tier or change broken, how to distinguish) and the nightly scheduled job (full Worker.Tests + live-MXAccess smoke, `MXGATEWAY_RUN_LIVE_MXACCESS_TESTS=1`, never gates a push, on-failure issue notification). Reword the paragraph at `:433-437` so the Generated/ guard attribution is: primary = `check-codegen.ps1` regeneration diff (portable job), secondary = net48 compile (windows-x86).
2. `scripts/check-codegen.ps1:17` — change "are guarded by the Windows CI job, not here" to "are guarded by the SSH-driven `windows-x86` CI job (see docs/GatewayTesting.md § Continuous Integration), not here".
3. `.gitea/workflows/ci.yml:12-14` — cron comment now describes the nightly windev run (covered by TST-25 step 5); `:123-131` removal note replaced (TST-25 step 6).
**Verification.** Greps prove the drift is gone: `grep -n '"windows"' docs/GatewayTesting.md` and `grep -n 'live-mxaccess' docs/GatewayTesting.md` return only descriptions of jobs that exist in `ci.yml`; `grep -n 'Windows CI job' scripts/check-codegen.ps1` returns the corrected text; `grep -rn 'definitive guard' docs/` attributes the Generated/ rule to `check-codegen.ps1`. Read the CI section back against the final `ci.yml` job list — every named job must exist, and every job in `ci.yml` must be named.
---
## TST-27 — `ShowTagValues` config row still says "Reserved" `Medium` · `P1 (doc batch)` · Effort S
**Finding.** `docs/GatewayConfiguration.md:185` reads "Reserved display control for tag values. The dashboard does not show full tag values by default." — but SEC-25 wired the flag live: `src/ZB.MOM.WW.MxGateway.Server/Dashboard/Hubs/DashboardEventBroadcaster.cs:16-29` blanks tag values from the deep-cloned event before mirroring to SignalR when `Dashboard:ShowTagValues` is `false` (the default). `docs/GatewayDashboardDesign.md:170` was updated in the SEC-25 change; the authoritative config reference was not — remediation-created drift, same-commit rule violated.
**Impact.** An operator consulting the options table concludes toggling the flag does nothing, when it actually controls whether tag values reach **every** dashboard hub subscriber — security-relevant precisely because the per-session hub ACL (TST-15) does not exist yet: the redaction is currently the only thing between a low-trust Viewer and other sessions' tag values.
**Design.** Documentation-only correction of the one stale row; no code change. State three things in the row: (1) what `false` (default) does — tag values are blanked from the `MxEvent` copy mirrored to the SignalR events hub; metadata (tag reference, quality, status, timestamps) still renders; (2) the security relevance — with no per-session hub ACL yet (TST-15), `true` exposes all sessions' tag values to every authenticated dashboard viewer; (3) the honest scope limit — the flag gates only the hub mirror, **not** the `/browse` live-value display (the TST-16 residual, tracked separately; do not paper over it by implying full coverage). Ship in the cycle's P1 doc-drift batch (with WRK-26, CLI-42, SEC residuals per the 00-overall roadmap) — one commit for the batch is fine since each edit is independently verifiable.
**Implementation.**
1. `docs/GatewayConfiguration.md:185` — replace the row description with the three-part text above, cross-referencing `docs/GatewayDashboardDesign.md` for the mechanism.
2. While in the file, confirm the shape block's `"ShowTagValues": false` at `:60` needs no change (it doesn't — value and default are correct; only the prose row lies).
3. No edit to `DashboardEventBroadcaster.cs` or `GatewayDashboardDesign.md` (both already correct).
**Verification.** `grep -n 'Reserved' docs/GatewayConfiguration.md` no longer matches the `ShowTagValues` row; read the new row back against `DashboardEventBroadcaster.cs:16-29` (behavior), `Configuration/DashboardOptions.cs` (default `false`), and `GatewayDashboardDesign.md:170` (consistency). Confirm the row does **not** claim `/browse` coverage.
---
## TST-28 — Gateway-side `max_frame_bytes` untested in the CI-run suite `Low` · `P2` · Effort S · relates IPC-02
**Finding.** `src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerClient.cs:988` populates `MaxFrameBytes = (uint)_connection.FrameOptions.MaxMessageBytes` in the `GatewayHello` (wired from `Worker.MaxMessageBytes` via `Sessions/SessionWorkerClientFactory.cs:73-76`), but no test under `src/ZB.MOM.WW.MxGateway.Tests` references `MaxFrameBytes` at all. The adoption side is asserted only in `Worker.Tests` — Windows-only, i.e. inside the TST-25 gap.
**Impact.** A regression to sending `0` (which the worker deliberately treats as "older gateway, use default" per the IPC-02 negotiation contract) would pass every automated test while silently reverting the negotiated frame limit — an invisible downgrade of shipped IPC-02 behavior.
**Design.** One cheap fake-worker assertion; no new infrastructure needed. `FakeWorkerHarness.CompleteStartupAsync` already reads and **returns** the `GatewayHello` envelope (`Tests/Gateway/Workers/Fakes/FakeWorkerHarness.cs:149-165`), and `CreateConnectedPairAsync` already accepts a `maxMessageBytes` override that flows into the `WorkerFrameProtocolOptions` shared with the `WorkerClient` the harness creates — so the test is: start the client, complete the handshake, assert on the returned envelope. Cover both the default (`WorkerFrameProtocolOptions.DefaultMaxMessageBytes`) and an overridden value (e.g. `2 * 1024 * 1024`) as a `[Theory]`, which also pins that the field tracks configuration rather than a constant. Rejected alternative: asserting via the worker-side adoption tests only — that is exactly the Windows-only blind spot this finding names.
**Implementation.**
1. Add a `[Theory]` to `src/ZB.MOM.WW.MxGateway.Tests/Gateway/Workers/WorkerClientTests.cs` (natural home — its `CompleteHandshakeAsync` helper at `:26` already drives this path), e.g. `StartAsync_SendsGatewayHelloWithConfiguredMaxFrameBytes`: create the harness with `CreateConnectedPairAsync(maxMessageBytes: value)` for `value` ∈ {default, 2 MiB}, create/start the `WorkerClient`, capture the envelope from `CompleteStartupAsync`, assert `envelope.GatewayHello.MaxFrameBytes == (uint)value` (and `!= 0` for readability of intent).
2. No production code change; no doc change (`docs/WorkerFrameProtocol.md:25-30` already documents the negotiation).
**Verification.** `dotnet test src/ZB.MOM.WW.MxGateway.Tests/ZB.MOM.WW.MxGateway.Tests.csproj --filter FullyQualifiedName~WorkerClientTests` green on macOS/Linux (i.e. it runs in the `portable` CI job — the point of the finding). Mutation check while developing: temporarily hard-code `MaxFrameBytes = 0` in `WorkerClient.cs:988` and confirm the new test fails, then revert.
---
## TST-29 — Retire `oldtasks.md`; delete root docs-review artifacts `Low` · `P2` · Effort S
**Finding.** TST-14's plan was "keep `oldtasks.md` only until the epic resumes, then delete", but TST-04 made it the durable governance record: `oldtasks.md:27-35,75-88` is now the only tracked statement that Phase 5 (orphan-worker reattach) is **deferred, not planned** and that `EnableOrphanReattach` (Task 26) "does not exist and must not be referenced". CLAUDE.md:79 links to it for exactly that statement. Meanwhile the mechanical half of TST-14 — deleting the five untracked, gitignored root working files (`MxAccessGateway-docs-{issues,fixed,final}.md`, `MxGatewayClient-docs-{issues,fixed}.md`, ~15k lines) — remains undone and keeps tripping greps.
**Impact.** Low, but real: deleting `oldtasks.md` naively would orphan a load-bearing governance record; keeping it forever contradicts its own "just a human-readable mirror" charter (`oldtasks.md:12-14`) and leaves two sources that can drift from `docs/plans/2026-06-15-session-resilience.md.tasks.json`. The untracked artifacts are pure grep noise.
**Design.** Move the durable decisions to where invariant-adjacent decisions live, then delete the mirror. `docs/DesignDecisions.md` gains a "Session-resilience epic scope" entry recording: Phase 5 deferred-not-planned with the rationale (reverses the "gateway restart does not reattach orphan workers" invariant; adoption-manifest store + phone-home protocol; deferred unless a concrete requirement appears); the explicit rule that `EnableOrphanReattach` does not exist and must not be referenced until its task lands; and the settled Phase-4 Viewer-default decision (admin-sees-all, Viewer strictly scoped to owned/granted sessions — `oldtasks.md:71-72`) so the TST-15 implementation doesn't have to rediscover it. The `tasks.json` remains the sole resume state for the still-pending Phase 4 tasks — one authority, no mirror. Do this now rather than waiting for the epic to close: the mirror's residual value (Task 14 Java remainder, Phase 4 checklist) is fully carried by `tasks.json` + the archreview register. Alternative rejected: keep `oldtasks.md` as the governance record — a file named "oldtasks" that must never be deleted is a trap for future cleanup passes.
**Implementation.**
1. `docs/DesignDecisions.md` — add the "Session-resilience epic scope" decision entry (content above, dated, citing the epic plan and TST-04).
2. Update the two tracked references before deletion: CLAUDE.md:79 ("see `oldtasks.md` (session-resilience epic Phase 5)" → point at `docs/DesignDecisions.md`); `stillpending.md:7,165` ("remaining epic tasks tracked in `oldtasks.md`" → point at `docs/plans/2026-06-15-session-resilience.md.tasks.json`). Leave `docs/plans/2026-06-16-stillpending-section8-design.md` (historical plan; a stale pointer in a dated plan is acceptable — optionally annotate).
3. `git rm oldtasks.md` — same commit as steps 12 (the record must never be absent from `main`).
4. `rm` the five untracked root files: `MxAccessGateway-docs-{issues,fixed,final}.md`, `MxGatewayClient-docs-{issues,fixed}.md` (gitignored at `.gitignore:152-154` — deletion is local-only, zero repo impact; no commit involved).
5. Do **not** touch `stillpending.md`'s own promote-or-migrate question, `REVIEW-PROCESS.md`, or `A2-galaxyrepository-adoption-handoff.md` — those remain TST-14's open triage in the prior-cycle doc.
**Verification.** `grep -rn 'oldtasks' --include='*.md' .` (excluding `archreview/`, which is a historical record) returns no tracked references; `grep -n 'EnableOrphanReattach\|deferred, not planned' docs/DesignDecisions.md` finds the migrated statements; `ls *-docs-*.md` at the repo root returns nothing; `git status` clean. Read CLAUDE.md:79 back to confirm the invariant sentence still holds and its pointer resolves.
---
## Cross-domain dependencies
- **TST-25 → old TST-05 / old TST-24:** the SSH-driven nightly is where the scheduled live-MXAccess smoke lands (closes old TST-05), and a working CI Windows tier unblocks wiring client wire-behavior tests into CI (old TST-24).
- **TST-25 ↔ IPC-24/IPC-25:** the nightly windev job is also the natural home for any Windows-side codegen verification the contracts/IPC remediation adds; coordinate job naming so both plans extend the same `windows-x86`/nightly jobs rather than adding parallel ones.
- **TST-26 ⊂ TST-25:** same commit, by rule.
- **TST-27:** ships in the cycle's P1 doc-drift batch alongside WRK-26 and CLI-42 (roadmap item 8); its `/browse` residual stays with TST-16 (prior cycle).
- **TST-28 ← IPC-02:** the test pins the gateway half of the IPC-02 negotiation; the worker half's tests become CI-run once TST-25 lands.