---
phase: 06.5
slug: static-client-asset-split-zero-cost-client-only-fly-deploys
status: draft
nyquist_compliant: false
wave_0_complete: false
created: 2026-05-16
---

# Phase 06.5 — Validation Strategy

> Per-phase validation contract for feedback sampling during execution.

---

## Test Infrastructure

| Property | Value |
|----------|-------|
| **Framework** | vitest 1.x (existing project default — `apps/server/__test__/`, `apps/client/src/__test__/`) |
| **Config file** | `vitest.config.ts` (per-package) |
| **Quick run command** | `pnpm --filter @rebno/server test -- --run static-assets` |
| **Full suite command** | `pnpm clean && pnpm build && pnpm test` |
| **Estimated runtime** | ~25 seconds (quick) / ~120 seconds (full) |

CI fast-path E2E is gated by a throwaway staging deploy — see Manual-Only Verifications.

---

## Sampling Rate

- **After every task commit:** Run quick command for the touched file's package.
- **After every plan wave:** Run full suite (`pnpm clean && pnpm build && pnpm test`).
- **Before `/gsd-verify-work`:** Full suite must be green AND one successful client-only fast-path staging deploy on a real commit.
- **Max feedback latency:** 30 seconds for unit; 5 minutes for fast-path E2E.

---

## Per-Task Verification Map

> Planner fills in concrete Task IDs after PLAN.md is generated. Skeleton below maps verification types to known plan slices.

| Task ID | Plan | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status |
|---------|------|------|-------------|------------|-----------------|-----------|-------------------|-------------|--------|
| 06.5-XX-01 | static-assets resolver | 1 | REQ-DEP-01 | — | Env-driven path resolution with bundled fallback; no path traversal | unit | `pnpm --filter @rebno/server test -- --run static-assets` | ❌ W0 | ⬜ pending |
| 06.5-XX-02 | static-assets resolver | 1 | REQ-DEP-01 | — | Missing dir → falls back to `/app/public`; empty dir behavior defined | unit | `pnpm --filter @rebno/server test -- --run static-assets-fallback` | ❌ W0 | ⬜ pending |
| 06.5-XX-03 | server static mount integration | 1 | REQ-DEP-01, REQ-CLI-08 | — | GET `/` returns index.html from configured dir; cache headers preserved | integration | `pnpm --filter @rebno/server test -- --run server-static-mount` | ❌ W0 | ⬜ pending |
| 06.5-XX-04 | client-release.sh script | 2 | REQ-DEP-04 | — | Stage→check→atomic swap (`mv -T`); never publish broken release | unit | `bash scripts/test/client-release.bats` | ❌ W0 | ⬜ pending |
| 06.5-XX-05 | client-release.sh script | 2 | REQ-DEP-04 | — | GC skips `current` target via readlink; keeps last 5 | unit | `bash scripts/test/client-release-gc.bats` | ❌ W0 | ⬜ pending |
| 06.5-XX-06 | GH Actions path filter | 2 | REQ-DEP-04 | — | Client-only diff → fast path; mixed/server diff → full image path | integration | manual workflow_dispatch dry-run + assertion script | ❌ W0 | ⬜ pending |
| 06.5-XX-07 | fast-path deploy E2E | 3 | REQ-DEP-04, REQ-CLI-08 | — | Real fast-path deploy on staging; 3-check probe passes; rollback symlink works | E2E (manual UAT) | operator-driven staging fast-path deploy | ❌ W0 | ⬜ pending |
| 06.5-XX-08 | fly.toml env injection | 1 | REQ-DEP-01 | — | `STATIC_ASSETS_DIR` set on staging + prod toml | config-lint | `node scripts/check-fly-env.mjs` | ❌ W0 | ⬜ pending |

*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*

---

## Wave 0 Requirements

- [ ] `apps/server/src/__test__/static-assets.test.ts` — resolver unit tests (env-set, env-unset, dir-missing, empty-dir).
- [ ] `apps/server/src/__test__/server-static-mount.test.ts` — express integration: serves from resolved dir.
- [ ] `scripts/test/client-release.bats` (or vitest harness on the shell script via `execa`) — staging-then-swap, GC safety.
- [ ] `scripts/check-fly-env.mjs` — assert `STATIC_ASSETS_DIR` present in fly.toml + fly.staging.toml.
- [ ] Manual UAT checklist file `docs/deploy/uat-06.5-client-fast-path.md` — operator runbook for one full fast-path deploy + rollback drill.

---

## Manual-Only Verifications

| Behavior | Requirement | Why Manual | Test Instructions |
|----------|-------------|------------|-------------------|
| Real Fly machine staging deploy via client-only fast path | REQ-DEP-04 | Requires live Fly machine + flyctl auth + real volume state — can't run inside CI test harness | Operator drives a `apps/client/**`-only PR through CI, watches the deploy run, then asserts: (1) no Docker image push event in Fly logs, (2) `/data/client-assets/current` symlink points to new SHA, (3) 3-check probe green, (4) prior release dir still present. |
| Rollback drill — symlink flip back to N-1 | REQ-DEP-04 | Symlink replay on real volume | After a successful fast-path deploy, operator SSHes in, runs `ln -s /data/client-assets/releases/<prev> /data/client-assets/current.new && mv -T current.new current`, then re-runs 3-check probe. Document timing. |
| Cold-start bundled-public fallback | REQ-DEP-01 | Requires a fresh machine where `/data/client-assets/current` doesn't exist yet | After Wave 1 server changes ship, destroy + recreate staging machine, confirm `/` returns the bundled `/app/public/index.html` before any release upload runs. |
| Phase 06.4 deploy-flow regression check | (06.4 carryover) | Requires running the full-image path with `c01038f` escape hatches | One full-path staging deploy (e.g., touching `apps/server/**`) must still honor `workflow_dispatch` skip inputs, commit-message escape hatches, `SKIP_PHASE_4_CARRYOVER`, Buildx GHA cache. |

---

## Validation Sign-Off

- [ ] All tasks have `<automated>` verify or Wave 0 dependencies
- [ ] Sampling continuity: no 3 consecutive tasks without automated verify
- [ ] Wave 0 covers all MISSING references
- [ ] No watch-mode flags
- [ ] Feedback latency < 30s (unit) / 300s (E2E)
- [ ] `nyquist_compliant: true` set in frontmatter

**Approval:** pending
