---
phase: 5
slug: web-push-notifications
status: planned
nyquist_compliant: true
wave_0_complete: false
created: 2026-06-09
---

# Phase 5 — Validation Strategy

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

---

## Test Infrastructure

| Property               | Value                                                                      |
| ---------------------- | -------------------------------------------------------------------------- |
| **Framework**          | vitest 4.x (API + PWA)                                                     |
| **Config file**        | `apps/api/vitest.config.*` / `apps/pwa/vitest.config.*` (existing)         |
| **Quick run command**  | `pnpm --filter @familysync/api test`                                       |
| **Full suite command** | `pnpm --filter @familysync/api test && pnpm --filter @familysync/pwa test` |
| **Estimated runtime**  | ~30 seconds                                                                |

---

## Sampling Rate

- **After every task commit:** Run quick run command for the touched workspace
- **After every plan wave:** Run full suite command
- **Before `/gsd-verify-work`:** Full suite must be green
- **Max feedback latency:** 30 seconds

---

## Per-Task Verification Map

> Populated by the planner from PLAN.md tasks. Each task with `<automated>` verify maps to a row.

| Task ID  | Plan  | Wave | Requirement    | Threat Ref    | Secure Behavior                          | Test Type                     | Automated Command                                                              | File Exists | Status     |
| -------- | ----- | ---- | -------------- | ------------- | ---------------------------------------- | ----------------------------- | ------------------------------------------------------------------------------ | ----------- | ---------- |
| 05-01-T1 | 05-01 | 1    | NOTIF-01/02/03 | T-05-SC       | package legitimacy gate before install   | check                         | `node -e "...deps present..."`                                                 | ✅          | ⬜ pending |
| 05-01-T2 | 05-01 | 1    | NOTIF-01       | T-05-01       | VAPID private key never committed        | check                         | `grep VAPID_* .env.example`                                                    | ✅          | ⬜ pending |
| 05-01-T3 | 05-01 | 1    | NOTIF-01/02/03 | T-05-02       | safe generate+migrate (no db:push)       | check                         | `grep pushSubscriptions schema.ts; ls migrations/0003_*.sql`                   | ✅          | ⬜ pending |
| 05-01-T4 | 05-01 | 1    | NOTIF-01/02/03 | —             | RED scaffolds + setup truncation         | unit                          | `vitest run tests/lib/* tests/broker/* tests/routes/push.test.ts` (RED)        | ✅          | ⬜ pending |
| 05-02-F1 | 05-02 | 2    | NOTIF-01/02/03 | T-05-03/05    | VAPID send + 410/404 prune               | unit                          | `vitest run tests/lib/pushDispatcher.test.ts`                                  | ❌ W0       | ⬜ pending |
| 05-03-F1 | 05-03 | 2    | NOTIF-02       | T-05-06/07    | coalesce burst, suppress actor           | unit                          | `vitest run tests/lib/pushCoalescer.test.ts`                                   | ❌ W0       | ⬜ pending |
| 05-04-T1 | 05-04 | 3    | NOTIF-01/02/03 | T-05-09/10/13 | user-scoped subscribe/unsubscribe        | integration                   | `vitest run tests/routes/push.test.ts`                                         | ❌ W0       | ⬜ pending |
| 05-04-T2 | 05-04 | 3    | NOTIF-01/02/03 | T-05-11/12    | SW waitUntil + denylist                  | build/grep                    | `pnpm --filter @familysync/pwa build` + sw.ts greps                            | ✅          | ⬜ pending |
| 05-04-T3 | 05-04 | 3    | NOTIF-01/02/03 | T-05-09       | tap-gated subscribe (desktop)            | human-verify (playwright-cli) | playwright-cli subscribe round-trip                                            | ✅          | ⬜ pending |
| 05-05-T1 | 05-05 | 4    | NOTIF-02       | T-05-14/15/16 | access-scoped, self-suppressed           | unit                          | `vitest run tests/lib/listChangeDispatcher.test.ts`                            | ❌ W0       | ⬜ pending |
| 05-05-T2 | 05-05 | 4    | NOTIF-02       | T-05-15       | reorder-silent, check-notifies           | unit                          | `vitest run tests/routes/lists.test.ts`                                        | partial     | ⬜ pending |
| 05-06-F1 | 05-06 | 4    | NOTIF-01       | T-05-17/18/19 | shared-only (query), all-day excl, dedup | unit                          | `vitest run tests/broker/reminderScheduler.test.ts`                            | ❌ W0       | ⬜ pending |
| 05-07-F1 | 05-07 | 5    | NOTIF-03       | T-05-20/21/22 | meaningful-only, actor-suppressed        | unit                          | `vitest run tests/lib/eventChangeDispatcher.test.ts tests/broker/sync.test.ts` | ❌ W0       | ⬜ pending |
| 05-08-T1 | 05-08 | 6    | NOTIF-01/02/03 | T-05-23       | silent re-subscribe (granted only)       | build/grep                    | `pnpm --filter @familysync/pwa build` + hook greps                             | ✅          | ⬜ pending |
| 05-08-T2 | 05-08 | 6    | NOTIF-01/02/03 | T-05-25       | master toggle drives DELETE              | build/grep                    | SettingsSheet greps + build                                                    | ✅          | ⬜ pending |
| 05-08-T3 | 05-08 | 6    | NOTIF-01/02/03 | T-05-24       | denied-banner OS-revoked-only (desktop)  | human-verify (playwright-cli) | playwright-cli banner show/hide                                                | ✅          | ⬜ pending |

_Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky_

_Nyquist: no run of 3+ consecutive tasks lacks an automated verify. Every task carries an `<automated>` block; UI-only tasks pair a build/grep gate with a desktop playwright-cli human-verify (iOS-standalone is the only genuinely device-only check, deferred to the phase gate)._

---

## Wave 0 Requirements

- [ ] `web-push` + `@types/web-push` installed in `apps/api` before any push-dispatch task (Plan 05-01 Task 1)
- [ ] `workbox-precaching` / `workbox-core` / `workbox-routing` installed in `apps/pwa` before the SW migration (Plan 05-01 Task 1)
- [ ] Test stubs: reminderScheduler, pushDispatcher (410/404 prune), pushCoalescer, eventChangeDispatcher, push route (Plan 05-01 Task 4)
- [ ] VAPID test keypair fixture for unit tests — no network (`apps/api/tests/fixtures/vapid.ts`, Plan 05-01 Task 4)
- [ ] `push_subscriptions` added to `test/setup.ts` afterEach truncation (Plan 05-01 Task 4)

_Existing vitest infrastructure covers the rest._

---

## Manual-Only Verifications

| Behavior                                                | Requirement                 | Why Manual                                                                  | Test Instructions                                                                                                         |
| ------------------------------------------------------- | --------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| iOS standalone-PWA push delivery + visible notification | NOTIF-01/02/03              | iOS Safari standalone push cannot be driven by playwright-cli (device-only) | Install PWA on iPhone (Home Screen), grant permission, trigger event reminder + list change, confirm visible notification |
| iOS subscription survives inactivity (health-check)     | NOTIF (success criterion 4) | Requires real APNs + elapsed time on device                                 | Leave PWA idle, fire push after extended inactivity, confirm still delivered                                              |
| iOS permission-denied banner + re-enable flow           | NOTIF (D-10)                | iOS standalone Settings deep-link is device-only                            | Revoke notifications in iOS Settings, confirm banner + instruction sheet                                                  |

_Desktop/Chromium push flows (permission prompt, subscribe, dispatch, notificationclick deep-link, settings toggle, denied banner) ARE automatable via playwright-cli — Plans 05-04 Task 3 and 05-08 Task 3._

---

## Validation Sign-Off

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

**Approval:** planned
