# Phase 1: Foundation + Broker Spike - Context **Gathered:** 2026-06-04 **Status:** Ready for planning ## 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. ## 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). ## 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) ## 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. ## 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. ## 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. --- *Phase: 1-Foundation + Broker Spike* *Context gathered: 2026-06-04*