Mechanical reformat — no logic changes. 398 files changed, 19125 insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc (singleQuote:true, semi:true, tabWidth:2, trailingComma:all, printWidth:100). Isolated per D-13-08 for reviewability.
8.8 KiB
Phase 1: Foundation + Broker Spike - Context
Gathered: 2026-06-04 Status: Ready for planning
## Phase BoundaryStand 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 DecisionsPersonal-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 formhttps://caldav.fastmail.com/dav/principals/user/{email}/(Pitfall 1). - D-10: Identity =
oidc_iss + oidc_subcomposite key, never email (AUTH-03). - D-11: Skip the Authelia
groupsclaim — 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 asDATE/{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_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_typecode, grant typesauthorization_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/, generatedCLAUDE.md, and an emptyREADMEexist. 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>
## 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.
- 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