---
phase: 1
slug: foundation-broker-spike
status: planned
nyquist_compliant: true
wave_0_complete: false
created: 2026-06-04
---

# Phase 1 — Validation Strategy

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

---

## Test Infrastructure

| Property               | Value                                                                             |
| ---------------------- | --------------------------------------------------------------------------------- |
| **Framework**          | vitest (Vite-native, shared backend + frontend per CLAUDE.md)                     |
| **Config file**        | `apps/api/vitest.config.ts` (environment: node) — created Plan 01 Task 1 (Wave 0) |
| **Quick run command**  | `pnpm vitest run --reporter=dot`                                                  |
| **Full suite command** | `pnpm vitest run`                                                                 |
| **Estimated runtime**  | ~30 seconds                                                                       |

---

## Sampling Rate

- **After every task commit:** Run `pnpm vitest run --reporter=dot`
- **After every plan wave:** Run `pnpm vitest run`
- **Before `/gsd-verify-work`:** Full suite must be green
- **Max feedback latency:** 60 seconds

---

## Per-Task Verification Map

> Mapped to final task IDs. Each phase success criterion maps to at least one automated or manual verification.

| Success Criterion                                 | Requirement             | Plan/Task                                             | Verification approach                                                                                                                                                                                                     | Test Type                                     |
| ------------------------------------------------- | ----------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | -------------------------- |
| SC1 — OIDC login lands on home, no Fastmail creds | AUTH-01                 | 01-02 Task 2 (wiring) + 01-04 Task 3 (live)           | Middleware mounted on /api/\*, /callback wired (tsc/grep); live: unauth /api/me 302→Authelia, login lands on shell                                                                                                        | integration + manual (real Authelia/Pangolin) |
| SC2 — Sessions persist across browser restart     | AUTH-02                 | 01-02 Task 2 + 01-04 Task 3 (live)                    | Backend refresh-token rotation configured (no iframe); live: close browser, revisit, no re-login                                                                                                                          | integration + manual                          |
| SC3 — Stable distinct member color                | AUTH-03                 | 01-02 Task 1 (unit) + 01-04 Task 3 (2nd member, live) | Unit: round-robin palette by join order, idempotent re-upsert, identity by iss+sub (`tests/auth/user.test.ts`); live: 2nd member distinct color                                                                           | unit + manual                                 |
| SC4 — Broker fetches + caches ≥1 real event       | CAL-01                  | 01-03 Task 2/3 (unit) + 01-04 Task 2 (live spike)     | Unit: sync all-day/timed split + UID upsert (`tests/broker/sync.test.ts`), ctag skip (`tests/broker/poller.test.ts`), crypto roundtrip (`tests/broker/crypto.test.ts`); live: ≥1 event row cached + shown on landing page | unit + manual (real Fastmail)                 |
| SC5 — CAL-08 go/no-go documented                  | CAL-08                  | 01-04 Task 2                                          | Manual spike: app password reads shared + personal collections; `CAL-08-DECISION.md` committed with `Decision: GO                                                                                                         | NO-GO` + fallback                             | manual (decision artifact) |
| Bonus — Pangolin SSE pass-through                 | D-08 (de-risks Phase 4) | 01-04 Task 1 (endpoint) + Task 3 (smoke)              | `/api/sse/heartbeat` via streamSSE (tsc/grep); live: stream alive 5+ min over public URL, PASS/FAIL recorded                                                                                                              | integration + manual                          |

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

---

## Wave 0 Requirements (created in Plan 01 Task 1)

- [ ] `apps/api/vitest.config.ts` — Node-environment vitest config
- [ ] `apps/api/tests/helpers/db.ts` — Drizzle test-DB fixtures
- [ ] `apps/api/tests/health.test.ts` — /health 200 (filled Plan 01 Task 2)
- [ ] `apps/api/tests/auth/user.test.ts` — upsertUser color/identity (filled Plan 02 Task 1)
- [ ] `apps/api/tests/broker/crypto.test.ts` — AES-GCM roundtrip/IV/tamper (filled Plan 03 Task 1)
- [ ] `apps/api/tests/broker/sync.test.ts` — all-day DATE split + UID upsert (filled Plan 03 Task 2)
- [ ] `apps/api/tests/broker/poller.test.ts` — ctag skip detection (filled Plan 03 Task 3)
- [ ] Fastmail CalDAV fixtures: captured raw VEVENT samples (timed + all-day) for offline parser unit tests

---

## Manual-Only Verifications

| Behavior                                      | Requirement     | Why Manual                                                 | Plan/Task    |
| --------------------------------------------- | --------------- | ---------------------------------------------------------- | ------------ |
| Authelia OIDC end-to-end login (both members) | AUTH-01         | Requires real Authelia + Pangolin topology                 | 01-04 Task 3 |
| Session persistence across restart            | AUTH-02         | Browser-restart behavior not unit-testable                 | 01-04 Task 3 |
| Real Fastmail event fetch + cache             | CAL-01          | Requires real app password + live calendar                 | 01-04 Task 2 |
| CAL-08 personal-calendar ACL spike            | CAL-08          | Spike produces a human go/no-go judgement                  | 01-04 Task 2 |
| Pangolin SSE pass-through smoke test          | D-08            | Idle-timeout behavior only observable over real public URL | 01-04 Task 3 |
| drizzle-kit push against live MariaDB         | CAL-01 (schema) | Live DB apply; [BLOCKING] gate                             | 01-01 Task 3 |

---

## Validation Sign-Off

- [x] All tasks have an automated verify or a Wave 0 dependency, or are listed under Manual-Only with justification
- [x] Sampling continuity: no 3 consecutive code tasks without automated verify (each TDD task has a vitest verify; integration tasks have tsc/grep)
- [x] Wave 0 covers all MISSING references (created in Plan 01 Task 1)
- [x] No watch-mode flags
- [x] Feedback latency < 60s
- [x] `nyquist_compliant: true` set in frontmatter

**Approval:** planner-mapped
