Files
familysync/.planning/phases/01-foundation-broker-spike/01-CONTEXT.md
T

8.8 KiB

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_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>

## 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