diff --git a/.planning/phases/01-foundation-broker-spike/01-CONTEXT.md b/.planning/phases/01-foundation-broker-spike/01-CONTEXT.md new file mode 100644 index 0000000..4c35696 --- /dev/null +++ b/.planning/phases/01-foundation-broker-spike/01-CONTEXT.md @@ -0,0 +1,108 @@ +# 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* diff --git a/.planning/phases/01-foundation-broker-spike/01-DISCUSSION-LOG.md b/.planning/phases/01-foundation-broker-spike/01-DISCUSSION-LOG.md new file mode 100644 index 0000000..67d434c --- /dev/null +++ b/.planning/phases/01-foundation-broker-spike/01-DISCUSSION-LOG.md @@ -0,0 +1,112 @@ +# Phase 1: Foundation + Broker Spike - Discussion Log + +> **Audit trail only.** Do not use as input to planning, research, or execution agents. +> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered. + +**Date:** 2026-06-04 +**Phase:** 1-Foundation + Broker Spike +**Areas discussed:** Personal-cal spike & fallback, Member color assignment, Infra validation scope + +--- + +## Area Selection + +| Option | Selected | +|--------|----------| +| Personal-cal spike & fallback | ✓ | +| Member color assignment | ✓ | +| Phase 1 landing scope | (skipped → Claude's discretion) | +| Infra validation scope | ✓ | + +--- + +## Personal-cal spike & fallback + +### Where the wife's personal calendar lives +| Option | Description | Selected | +|--------|-------------|----------| +| Fastmail | Fastmail-hosted collection; broker can reach it | ✓ | +| iCloud / Apple Calendar | Broker cannot read; CAL-08 degrades | | +| Not sure yet | Confirm before spike | | +| Both / mixed | Define overlay target | | + +**User's choice:** Fastmail +**Notes:** Removes the iCloud "unreachable" risk; CAL-08 is technically achievable. + +### Broker access model +| Option | Description | Selected | +|--------|-------------|----------| +| Single broker token + share | One token; share+accept per member | | +| Per-member app passwords | Each member's own app password; broker holds N creds | ✓ | +| Prove single-token, keep per-member ready | Hybrid | | + +**User's choice:** Per-member app passwords +**Notes:** Eliminates the unconfirmed cross-account CalDAV ACL discovery risk (the project's original highest risk). Spike simplifies to confirming an app password reads its own account. + +### Spike credential sequencing +| Option | Description | Selected | +|--------|-------------|----------| +| Just mine now | Prove broker with Lucas's app password; wife's added Phase 2 | ✓ | +| Both before Phase 1 done | Get wife's password too; fully close CAL-08 now | | + +**User's choice:** Just mine now +**Notes:** Phase 1 not blocked on coordinating with wife. CAL-08 structurally proven via N-credential broker. + +### Credential storage +| Option | Description | Selected | +|--------|-------------|----------| +| DB table, encrypted at rest | Keyed by oidc_sub, key from env; supports later onboarding | ✓ | +| Env vars / secrets file | Simple, but redeploy to add/rotate | | + +**User's choice:** DB table, encrypted at rest +**Notes:** Supports adding wife later with no redeploy; backend-only, never exposed to frontend. + +--- + +## Member color assignment + +| Option | Description | Selected | +|--------|-------------|----------| +| Auto-assign from palette | Assign on first login, persist on user row | ✓ | +| User-pickable in settings | Default + settings color-picker | | +| Hardcoded per account | Fixed color in config per oidc_sub | | + +**User's choice:** Auto-assign from palette +**Notes:** No settings UI in Phase 1; stable across sessions; works for future members. Pickable color deferred. + +--- + +## Infra validation scope + +### Deployment scope +| Option | Description | Selected | +|--------|-------------|----------| +| Deploy through Pangolin | Real tunnel + Authelia; validate OIDC topology day one | ✓ | +| Local docker-compose only | Localhost; defer tunnel validation | | + +**User's choice:** Deploy through Pangolin +**Notes:** Avoids "worked locally, broke in prod" OIDC failure; same-parent-domain cookies (Pitfall 17). + +### Pangolin SSE smoke test +| Option | Description | Selected | +|--------|-------------|----------| +| Fold it in now | Trivial SSE endpoint over public URL; de-risk Phase 4 | ✓ | +| Defer to Phase 4 | Keep Phase 1 strict to its 5 criteria | | + +**User's choice:** Fold it in now +**Notes:** Cheap while stack+tunnel are up; a failure changes Phase 4 transport but does not block Phase 1. + +--- + +## Claude's Discretion + +- Phase 1 landing page: thin authenticated shell that also displays the one cached event as broker proof. +- Color palette: small set of visually-distinct, accessible hues, round-robin by join order. +- Broker internals (sync-token vs ctag, poll interval), Drizzle schema, OIDC middleware wiring, encryption helper. +- Stack libraries/versions per locked research stack. + +## Deferred Ideas + +- User-pickable member color (settings color-picker) — future settings/profile phase. +- Single-token broker via share+accept — not chosen; alternative on record only. +- Wife's app-password onboarding UX/endpoint — Phase 2 (the encrypted credential table is built in Phase 1 to support it).