# Phase 1: Foundation + Broker Spike - Context

**Gathered:** 2026-06-04
**Status:** Ready for planning

<domain>
## Phase Boundary

Stand up the FamilySync stack (Docker Compose + MariaDB, deployed through the real Pangolin tunnel) and deliver: Authelia OIDC login with persistent sessions, stable per-member identity, and an auto-assigned per-member color; a CalDAV broker that reads and caches at least one real event from a Fastmail calendar via a per-member app password; and a documented go/no-go on the personal-calendar overlay (CAL-08).

**In scope:** AUTH-01/02/03, CAL-01, CAL-08 (spike + decision), infra scaffold, Pangolin deployment, SSE pass-through smoke test.
**Out of scope (later phases):** calendar UI/views (Phase 2), event write-back (Phase 3), lists + live sync feature (Phase 4), push (Phase 5). No single-occurrence recurring edits ever in v1.
</domain>

<decisions>
## Implementation Decisions

### Personal-Calendar Access & Spike (CAL-08 gate)
- **D-01:** The wife's personal calendar lives on **Fastmail** (confirmed by user). It is a Fastmail-hosted calendar collection, so the broker can reach it via CalDAV. This removes the iCloud "unreachable" risk entirely.
- **D-02:** Broker access model = **per-member app passwords**, NOT single-token cross-account share+accept. Each member generates their own Fastmail app password; the broker holds N credentials and reads each account directly. This eliminates the unconfirmed cross-account CalDAV ACL discovery risk — the original highest project risk. The CAL-08 spike therefore simplifies to: confirm an app password reads its own account's shared + personal calendars via PROPFIND/REPORT.
- **D-03:** Phase 1 proceeds with **only the primary user's (Lucas) app password**. Success criterion #4 (read+cache a real event) is proven against Lucas's personal + the shared family calendar. The wife's app password is added in Phase 2 — Phase 1 is NOT blocked on coordinating with her. CAL-08 is structurally proven (N-credential broker) without her credential present.
- **D-04:** App passwords are stored in an **encrypted-at-rest DB table**, keyed by user `oidc_sub`, with the encryption key supplied from env. This supports adding the wife later via an onboarding/settings flow with no redeploy, and scales to the N-credential broker. App passwords are backend-only — never exposed to the frontend (locked, security).
- **D-05:** Go/no-go record: since access is per-member app passwords and her calendar is on Fastmail, the expected outcome is GO. The documented fallback (only relevant if a Fastmail app password unexpectedly cannot read a personal calendar) is shared-family-only for v1, moving CAL-08 to v1.x.

### Member Color Assignment (AUTH-03)
- **D-06:** Each member's color is **auto-assigned from a curated palette on first login** and persisted on the user row (keyed by `oidc_iss + oidc_sub`). Stable across sessions, no settings UI in Phase 1, works for both current members and any future member. Not user-pickable in v1 (a settings color-picker is a deferred idea).

### Infrastructure & Deployment Scope
- **D-07:** Phase 1 **deploys through the real Pangolin tunnel + Authelia**, not local-only. OIDC redirect URIs, HTTPS, and session cookies are validated in the real topology from day one to avoid a "worked locally, broke in prod" OIDC failure. Ensure FamilySync and Authelia share the same parent domain so Authelia session cookies are same-site (Pitfall 17).
- **D-08:** Phase 1 **folds in the Pangolin SSE pass-through smoke test** (a trivial long-lived SSE endpoint confirmed over the public URL). De-risks Phase 4 transport choice early (issue #1034). A failure here changes the Phase 4 real-time transport decision; it does NOT block Phase 1's auth/broker success criteria.

### Locked Upstream (carried forward — do NOT re-litigate)
- **D-09:** CalDAV-only via `tsdav`; broker auth = Fastmail **app password**, never JMAP/API token. Principal URL form `https://caldav.fastmail.com/dav/principals/user/{email}/` (Pitfall 1).
- **D-10:** Identity = `oidc_iss + oidc_sub` composite key, never email (AUTH-03).
- **D-11:** **Skip the Authelia `groups` claim** — two equal members; authentication not authorization (Pitfall 16).
- **D-12:** **Backend confidential client holds the refresh token**; no iframe silent renewal (Pitfall 17). Persistent session (AUTH-02) via backend-managed refresh, not browser iframe.
- **D-13:** Calendar cache: store raw VEVENT blob + `dtstart_utc`; all-day events as `DATE` / `{date, allDay}` struct, never coerced to DATETIME/UTC (Pitfall 3). Write-through cache invalidation; only cache server-returned objects (Pitfall 14). Use sync-token (WebDAV-Sync) with ctag-poll fallback from day one (Pitfall 4).

### Claude's Discretion
- **Phase 1 landing page:** a thin authenticated shell that ALSO displays the one cached event as broker proof (not a bare health page, not a real calendar UI). Confirms end-to-end auth + broker in one screen.
- **Color palette:** a small set of visually-distinct, accessible hues assigned round-robin by join order. Exact values are Claude's choice.
- **Broker internals:** sync-token vs ctag detection, poll interval (research suggests conservative 5-min / 60s acceptable for v1), Drizzle schema specifics, OIDC middleware wiring, encryption helper implementation.
- **Stack libraries/versions:** per locked research stack (Hono + Drizzle/mysql2 + tsdav + ical.js + rrule + @hono/oidc-auth).
</decisions>

<canonical_refs>
## Canonical References

**Downstream agents MUST read these before planning or implementing.**

### Phase definition & requirements
- `.planning/ROADMAP.md` § "Phase 1: Foundation + Broker Spike" — goal + 5 success criteria (the scope anchor)
- `.planning/REQUIREMENTS.md` — AUTH-01, AUTH-02, AUTH-03, CAL-01, CAL-08 (full requirement text + traceability)
- `.planning/PROJECT.md` — constraints, key decisions, household context

### Research (read before planning — flagged NEEDS research-phase by SUMMARY)
- `.planning/research/SUMMARY.md` — cross-cutting findings; Phase 1 section + confidence assessment
- `.planning/research/PITFALLS.md` — Phase-1-relevant pitfalls: #1 (CalDAV-only), #3 (all-day DATE), #4 (ETag/sync-token), #7 (personal-cal sharing), #14 (cache double-write), #16 (Authelia groups), #17 (Authelia silent renewal/cookies), #18 (Pangolin WS/SSE)
- `.planning/research/STACK.md` — locked library versions + import paths
- `.planning/research/ARCHITECTURE.md` — broker-cache pattern, component layout, MariaDB schema guidance

### External docs (authoritative)
- Fastmail CalDAV principal URL + app passwords (see SUMMARY/PITFALLS Sources)
- Authelia OIDC client config — PKCE S256, `client_secret_basic`, response_type `code`, grant types `authorization_code`+`refresh_token` (see CLAUDE.md "Authelia OIDC Integration")
- Pangolin WebSocket/SSE issue #1034 (referenced in PITFALLS #18)

</canonical_refs>

<code_context>
## Existing Code Insights

### Reusable Assets
- None — greenfield repo. Only `.planning/`, generated `CLAUDE.md`, and an empty `README` exist. No source tree yet.

### Established Patterns
- None established yet. Phase 1 sets the foundational patterns (Docker Compose layout, Drizzle schema/migrations, broker module boundary, OIDC session middleware) that later phases inherit.

### Integration Points
- Authelia (already deployed, both members have accounts) — register FamilySync as an OIDC confidential client; no Authelia deploy/provisioning.
- Pangolin/Newt tunnel (already running) — FamilySync gets a public hostname under the same parent domain as Authelia.
- MariaDB + Redis available in the Unraid stack; no PostgreSQL.

</code_context>

<specifics>
## Specific Ideas

- The broker module must be a hard boundary: all Fastmail I/O isolated in `broker/`; nothing else imports Fastmail credentials or tsdav directly (architecture note).
- "No per-member credential juggling" is a core-value phrase, but the user explicitly accepted per-member app passwords as the access model — the juggling avoided is calendar-credential *login* friction for members (they still log in only via Authelia SSO), not broker-side secrets.

</specifics>

<deferred>
## Deferred Ideas

- **User-pickable member color** (settings color-picker) — deferred; v1 auto-assigns. Could land in a later settings/profile phase.
- **Single-token broker via Fastmail share+accept** — not chosen; per-member app passwords selected instead. Kept on record only as an alternative if credential management ever becomes painful.
- **Wife's app password onboarding flow** — the encrypted-credential DB table is built in Phase 1 to support it, but the actual onboarding UX/endpoint is Phase 2 work.

### Reviewed Todos (not folded)
- "Kick off FamilySync with /gsd:new-project" — stale project-kickoff todo, already completed during initialization; not relevant to Phase 1 scope.

</deferred>

---

*Phase: 1-Foundation + Broker Spike*
*Context gathered: 2026-06-04*
