Files
familysync/.planning/phases/03-event-write-back-pwa-install/03-VALIDATION.md
T

100 lines
6.0 KiB
Markdown

---
phase: 3
slug: event-write-back-pwa-install
status: draft
nyquist_compliant: true
wave_0_complete: false
created: 2026-06-05
---
# Phase 3 — Validation Strategy
> Per-phase validation contract for feedback sampling during execution.
> Sourced from 03-RESEARCH.md §Validation Architecture.
---
## Test Infrastructure
| Property | Value |
|----------|-------|
| **Framework (API)** | Vitest 4.x, environment: `node` |
| **Framework (PWA)** | Vitest 4.x + `jsdom` + `@testing-library/react` |
| **Config (API)** | `apps/api/vitest.config.ts` |
| **Config (PWA)** | `apps/pwa/vitest.config.ts` |
| **Quick run (API)** | `pnpm --filter @familysync/api test` |
| **Quick run (PWA)** | `pnpm --filter @familysync/pwa test` |
| **Full suite** | `pnpm test` (from repo root — runs both apps) |
| **Estimated runtime** | ~20-40 seconds (mocked DB + CalDAV; no network) |
---
## Sampling Rate
- **After every task commit:** Run the filtered quick command for the app touched
(`pnpm --filter @familysync/api test -- <path>` or `pnpm --filter @familysync/pwa test -- <name>`).
- **After every plan wave:** Run `pnpm test` (full suite, both apps).
- **Before `/gsd-verify-work`:** Full suite must be green.
- **Max feedback latency:** ~40 seconds (full suite).
---
## Per-Task Verification Map
| Req ID | Behavior | Plan | Wave | Test Type | Automated Command | File Exists | Status |
|--------|----------|------|------|-----------|-------------------|-------------|--------|
| CAL-04 | `buildVeventString` → VCALENDAR for a timed event (DTSTART UTC) | 02 | 2 | unit | `pnpm --filter @familysync/api test -- broker/vevent` | ❌ W0 (03-01) | ⬜ pending |
| CAL-04 | `buildVeventString` → all-day event uses DATE not DATETIME (D-13) | 02 | 2 | unit | same | ❌ W0 (03-01) | ⬜ pending |
| CAL-04 | POST /api/events/create → 202 + inserts pending outbox row | 03 | 2 | unit (mocked DB) | `pnpm --filter @familysync/api test -- routes/events` | ❌ W0 (03-01) | ⬜ pending |
| CAL-05 | PATCH /api/events/:uid/edit → 202 + inserts row with etag | 03 | 2 | unit | same | ❌ W0 (03-01) | ⬜ pending |
| CAL-06 | DELETE /api/events/:uid → 202 + inserts delete row | 03 | 2 | unit | same | ❌ W0 (03-01) | ⬜ pending |
| CAL-07 | `buildVeventString` with `rruleString` → RRULE property | 02 | 2 | unit | `pnpm --filter @familysync/api test -- broker/vevent` | ❌ W0 (03-01) | ⬜ pending |
| CAL-04/05/06 | Outbox worker: pending→done (204), pending→failed (412), pending→backoff (500), pending→dead (max attempts) | 02/04 | 2 | unit | `pnpm --filter @familysync/api test -- broker/outboxWorker` | ❌ W0 (03-01) | ⬜ pending |
| CAL-04/05/06 | GET /api/events/sync-status returns outbox status (D-09) | 03 | 2 | unit | `pnpm --filter @familysync/api test -- routes/events` | ❌ W0 (03-01) | ⬜ pending |
| CAL-04/05/07 | GET /api/events/writable-calendars returns D-03 set; never another member's read-only personal (V4) | 03 | 2 | unit | `pnpm --filter @familysync/api test -- routes/events` | ❌ W0 (03-01) | ⬜ pending |
| D-08 | 412 → conflict (not retry), mark failed, trigger re-sync | 04 | 2 | unit | `pnpm --filter @familysync/api test -- broker/outboxWorker` | ❌ W0 (03-01) | ⬜ pending |
| D-04 | Edit-as-move emits DELETE+CREATE pair; create runs first | 04 | 2 | unit | same | ❌ W0 (03-01) | ⬜ pending |
| D-03/V4 | create rejects write to non-owned/non-shared calendar (403) | 03 | 2 | unit | `pnpm --filter @familysync/api test -- routes/events` | ❌ W0 (03-01) | ⬜ pending |
| PWA-01 | `vite.config.ts` produces valid `manifest.webmanifest` with required fields | 06 | 3 | smoke (build output) | `pnpm --filter @familysync/pwa build` + manifest field check | ❌ W0 (03-01) | ⬜ pending |
| PWA-02 | `isIOSSafariNonStandalone()` true on mock iOS Safari non-standalone UA | 06 | 3 | unit | `pnpm --filter @familysync/pwa test -- InstallPrompt` | ❌ W0 (03-01) | ⬜ pending |
| PWA-02 | `useAndroidInstallPrompt` sets `canInstall=true` on `beforeinstallprompt` | 06 | 3 | unit (mock event) | same | ❌ W0 (03-01) | ⬜ pending |
*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*
---
## Wave 0 Requirements
The Wave 0 RED test scaffold is created in **plan 03-01 Task 4** (these files import
not-yet-existing modules so they fail RED until later waves implement them):
- [ ] `apps/api/tests/broker/vevent.test.ts` — CAL-04, CAL-07 (VEVENT builder, DATE/DATETIME split, RRULE)
- [ ] `apps/api/tests/broker/write.test.ts` — tsdav call shapes, etag/If-Match, response interpretation
- [ ] `apps/api/tests/broker/outboxWorker.test.ts` — outbox state machine (done/failed/backoff/dead), edit-as-move ordering (D-04/D-07/D-08)
- [ ] `apps/api/tests/routes/events.test.ts` — EXTEND existing: POST /create, PATCH /edit, DELETE /:uid, GET /sync-status, GET /writable-calendars, 403 ownership (preserve existing GET /api/events block)
- [ ] `apps/pwa/src/components/InstallPrompt.test.tsx` — iOS detection, Android `beforeinstallprompt` capture (PWA-02)
Existing test files (`broker/sync`, `routes/events` GET block, `auth/devBypass`) remain in place.
---
## Manual-Only Verifications
| Behavior | Requirement | Why Manual | Test Instructions |
|----------|-------------|------------|-------------------|
| SW `navigateFallbackDenylist` excludes `/callback` | PWA-01 | Requires a real production build + SW registration over HTTPS | Verify against prod build; confirm `/callback` not intercepted by SW |
| iOS standalone PWA login completes without leaving standalone | Gate 2 | Requires a physical iPhone, installed PWA, Authelia OIDC round-trip | Follow `docs/deployment.md` Gate 2 checklist (Plan 07) |
---
## Validation Sign-Off
- [x] All tasks have `<automated>` verify or a Wave 0 RED dependency (created in 03-01 Task 4)
- [x] Sampling continuity: no 3 consecutive tasks without automated verify
- [x] Wave 0 covers all MISSING references (five RED files in 03-01)
- [x] No watch-mode flags
- [x] Feedback latency < 40s
- [x] `nyquist_compliant: true` set in frontmatter
**Approval:** approved 2026-06-05