docs(01-foundation-broker-spike): create phase plan (4 plans, 3 waves) + SKELETON + validation map

This commit is contained in:
Lucas Berger
2026-06-04 08:16:48 -04:00
parent 25460c9bd9
commit 3c51e423ff
7 changed files with 977 additions and 33 deletions
@@ -1,8 +1,8 @@
---
phase: 1
slug: foundation-broker-spike
status: draft
nyquist_compliant: false
status: planned
nyquist_compliant: true
wave_0_complete: false
created: 2026-06-04
---
@@ -18,8 +18,8 @@ created: 2026-06-04
| Property | Value |
|----------|-------|
| **Framework** | vitest (Vite-native, shared backend + frontend per CLAUDE.md) |
| **Config file** | none — Wave 0 installs vitest + workspace config |
| **Quick run command** | `pnpm vitest run --changed` |
| **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 |
@@ -27,7 +27,7 @@ created: 2026-06-04
## Sampling Rate
- **After every task commit:** Run `pnpm vitest run --changed`
- **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
@@ -36,48 +36,54 @@ created: 2026-06-04
## Per-Task Verification Map
> Populated by the planner against the final task IDs. Each phase success criterion maps to at least one automated or manual verification below.
> Mapped to final task IDs. Each phase success criterion maps to at least one automated or manual verification.
| Success Criterion | Requirement | Verification approach | Test Type |
|-------------------|-------------|-----------------------|-----------|
| SC1 — OIDC login lands on home, no Fastmail creds | AUTH-01 | E2E: unauthenticated request to protected route 302→Authelia; authenticated session reaches home | manual (real Authelia) + integration (middleware mounted) |
| SC2 — Sessions persist across browser restart | AUTH-02 | Integration: signed JWT session cookie issued; refresh-token interval configured; unit test cookie attrs (httpOnly, same-site, maxAge) | integration + manual |
| SC3 — Stable distinct member color | AUTH-03 | Unit: color assignment is deterministic round-robin by join order, persisted on user row keyed by oidc_iss+oidc_sub, unchanged on re-login | unit |
| SC4 — Broker fetches + caches ≥1 real event | CAL-01 | Integration: broker PROPFIND discovers collections, REPORT returns VEVENT, parsed + written to cache table (raw blob + dtstart_utc/date); all-day DATE handling unit-tested | integration + manual (real Fastmail) |
| SC5 — CAL-08 go/no-go documented | CAL-08 | Manual: spike confirms app password reads shared + personal collections; decision recorded in a committed doc | manual (decision artifact) |
| 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 tracked per task by the planner. Status legend: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*
*Status legend: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*
---
## Wave 0 Requirements
## Wave 0 Requirements (created in Plan 01 Task 1)
- [ ] `vitest` + workspace test config installed (no framework exists — greenfield)
- [ ] Shared test fixtures: in-memory/throwaway MariaDB or test schema for Drizzle integration tests
- [ ] `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
- [ ] Crypto helper test vectors for AES-256-GCM encrypt/decrypt round-trip
---
## Manual-Only Verifications
| Behavior | Requirement | Why Manual | Test Instructions |
|----------|-------------|------------|-------------------|
| Authelia OIDC end-to-end login | AUTH-01 | Requires real Authelia + Pangolin topology; no mock substitutes for redirect/cookie behavior | Reach public URL → redirected to Authelia → authenticate → land on home with no Fastmail prompt |
| Session persistence across restart | AUTH-02 | Browser-restart behavior not unit-testable | Log in, fully close browser, revisit URL → no re-login |
| Real Fastmail event fetch + cache | CAL-01 | Requires real app password + live calendar | Run broker against Lucas's account → confirm ≥1 event row cached |
| CAL-08 personal-calendar ACL spike | CAL-08 | Spike produces a human go/no-go judgement | Confirm app password reads shared + personal collections via PROPFIND/REPORT; record GO/NO-GO + fallback |
| Pangolin SSE pass-through smoke test | (D-08, de-risks Phase 4) | Idle-timeout behavior only observable over real public URL | Open long-lived SSE endpoint over Pangolin URL ≥5 min → confirm stream not cut |
| 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
- [ ] All tasks have an automated verify or a Wave 0 dependency, or are listed under Manual-Only with justification
- [ ] Sampling continuity: no 3 consecutive tasks without automated verify
- [ ] Wave 0 covers all MISSING references
- [ ] No watch-mode flags
- [ ] Feedback latency < 60s
- [ ] `nyquist_compliant: true` set in frontmatter once planner maps task IDs
- [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:** pending
**Approval:** planner-mapped