---
plan: 06.5-05-e2e-uat-and-verification
status: complete
verified: 2026-05-16T23:53:56Z
operator: RE
verdict: approved
deploy_sha: 824014ae3a1792234c9654e9b907378f4c2a19e9
---

<!-- [doc->REQ-DEP-04] [doc->REQ-CLI-08] [doc->REQ-DEP-01] -->

# Plan 06.5-05 — E2E UAT and Verification (SUMMARY)

Operator-driven 5-drill E2E verification of the Phase 06.5 client-only fast-path
deploy infrastructure. **All five drills passed**; one gap surfaced and closed
inline during the drill.

## Outcome

| Drill | Requirement | Status | Wall-clock | Notes |
|-------|-------------|--------|------------|-------|
| 1 — Cold-start fallback | REQ-DEP-01 | PASS | ~30s machine restart | bundled `/app/public` served HTTP 200, log emit `static_mount_resolved source=bundled dir=/app/public` |
| 2 — Fast-path deploy | REQ-DEP-04 | PASS (after inline gap closure) | 47s (2a) / 57s (2b) | path-filter routing correct; symlink swap to `releases/<sha>` correct; GC ≤5; served bundle live |
| 3 — Rollback drill | REQ-DEP-04 | PASS | 0.835s total (swap 0.468 + probe 0.367) | `mv -T` atomic; readlink target reached; bundle truly served prior asset hash |
| 4 — Two-player CLI-08 | REQ-CLI-08 | PASS | n/a | operator-driven; both windows walked + chatted, milestone behaviour intact |
| 5 — Mixed-diff + 06.4 regression | REQ-DEP-04 | PASS | ~5min × 2 deploy cycles | 5A: mixed-diff → full-path / fast-path SKIPPED + skip-smoke commit-msg flag honored. 5C: workflow_dispatch with skip_verification + skip_smoke inputs both honored. 5B consolidated under 5A. |

## Gap closure (mid-drill)

**Defect:** Plan 04 workflow `Post-swap 3-check probe` step called `curl -fsS $URL/`
without an Authorization header, so under `STAGING_MODE=1` the step failed HTTP 401
even though the semantic deploy succeeded (build → tarball → SSH upload → atomic
symlink swap all completed).

**Fix:** commit `0c0e055` — added `STAGING_INVITE_TOKEN` to the step's `env:` block
(sourced from `secrets.STAGING_INVITE_TOKEN`, already used elsewhere in the workflow
for the full-path Playwright smoke) and attached `-H "Authorization: Bearer ..."`
to the `/` probe via a conditional bash array. `/assets/*` and `/health` probes
unchanged — they are middleware-exempt per `apps/server/src/staging-invite.js`.
Falls through cleanly if the secret is unset (non-staging-mode environment).

**Revalidation:** run `25975772140` (commit `585cf2d`, fast-path with patched
workflow) went green in 57s. Drill 2 retest row in VERIFICATION.md captures both
2a (original fail) and 2b (post-fix pass) for traceability.

## Traceability

```text
$ pnpm trace:check  (filtered to Phase 06.5 reqs; 2026-05-16T23:50Z)
  [OK] REQ-CLI-08  required: [doc, int]            stages: +doc +impl +unit +int
  [OK] REQ-DEP-01  required: [doc, impl, int]      stages: +doc +impl +unit +int
  [OK] REQ-DEP-04  required: [doc, impl, int]      stages: +doc +impl +unit +int
```

Overall `pnpm trace:check` exit was non-zero solely from pre-existing
`parse_error` / `undeclared_id` findings in older phase artifacts
(`REQ-DEP-NN`, `REQ-SRV-XX`, `REQ-X`, `REQ-CLI-XX`, plus malformed tag tokens
in Phase 04/05/06.x). Carried forward, unchanged by this phase — out of scope
per Plan 02's documented scope-boundary rule.

## Artifacts updated

| File | Change |
|------|--------|
| `.planning/phases/06.5-.../06.5-VERIFICATION.md` | Filled all 5 drill rows, Status → passed, Traceability fence, Signoff RE 2026-05-16 |
| `docs/deploy/uat-06.5-client-fast-path.md` | All drill checkboxes ticked + per-drill operator initials/timestamps; Signoff table + free-form notes |
| `.github/workflows/deploy-staging.yml` | Workflow probe step now passes `STAGING_INVITE_TOKEN` Bearer on `/` probe (gap closure commit `0c0e055`) |

## Drill 2 / 3 / 5 GitHub Actions run URLs

- 2a (failing baseline): https://github.com/SaberMage/rebno/actions/runs/25975399531
- 2b (post-fix retest, green): https://github.com/SaberMage/rebno/actions/runs/25975772140
- 5A (mixed-diff routing + skip-smoke commit-msg): https://github.com/SaberMage/rebno/actions/runs/25976038704
- 5C (workflow_dispatch + skip_verification + skip_smoke): https://github.com/SaberMage/rebno/actions/runs/25976148153

## Deviations from PLAN

1. **Drill 5B consolidation.** PLAN spec called out three sub-tests in Drill 5
   (5A mixed-diff, 5B server-only with `[skip-staging-smoke]`, 5C
   workflow_dispatch). 5A already exercised the `[skip-staging-smoke]` env
   propagation path (commit-msg flag → full-path env → 3 Playwright steps
   SKIPPED) and the full-path routing branch. 5B's distinct dimension
   (server-only diff vs mixed) does not alter either signal: both routes hit
   `non_client == true → full-path`, and both honor the same env. Documented
   as consolidated; not a fail. Operator confirmed "full Drill 5" intent
   pre-execution.

2. **flyctl ssh console substitution.** PLAN and ROLLBACK.md both call
   `flyctl ssh console -C` for non-interactive commands. On the Windows host
   used for UAT, flyctl errors with "The handle is invalid" under both bash
   and PowerShell. Substituted `flyctl machine exec <id> -a rebno-staging "sh -c '<cmd>'"`
   for every non-interactive ssh call (Drill 1 wipe, Drill 3 atomic symlink
   swap, hashed-asset manifest read). The Linux semantics inside the container
   are identical; this is a host-side TTY workaround, not a procedure change.
   Linux CI runners and operators on macOS/Linux hosts can continue to use the
   ssh-console form unchanged.

3. **Forward-roll deferred.** ROLLBACK.md step 6 ("deploy a no-op client commit
   to leave volume in a clean state") was satisfied implicitly by Drill 5A's
   full-path deploy of commit `824014a`, which re-mounted the Docker image and
   left the volume's `current` symlink pointing at `releases/4c24630` (the
   rolled-back target from Drill 3). The 4c24630 bundle and the 585cf2d bundle
   are functionally identical (one-line comment delta on `main.ts`); leaving
   the volume at 4c24630 is documented and intentional.

## Follow-up TODOs (out of scope, surface to roadmap)

1. **Workflow split — Playwright smoke into 3rd job.** Operator-suggested
   during Drill 5 scoping. Currently `full-path` job runs build → deploy →
   Playwright smoke serially, so observability tools waiting on
   `gh run watch` cannot detect deploy completion without waiting on smoke
   (which has a known 06.4 D-51c/CLI-08 carry-over failure mode). Splitting
   smoke into a downstream job decouples deploy success from smoke result.
   File as `tools/workflow-split-playwright-smoke` todo (future phase, not 06.5).
