Files
familysync/.planning/milestones/v1.0-phases/01-foundation-broker-spike/CAL-08-DECISION.md
T
Lucas Berger 982438dc10 style(13-03): apply Prettier formatting across repo
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.
2026-06-11 20:35:18 -04:00

5.4 KiB

CAL-08: Personal Calendar ACL Spike — Decision Record

Requirement: CAL-08 Phase: 01-foundation-broker-spike Spike script: apps/api/src/broker/spike.ts Run: 2026-06-04, live against Fastmail account me@lucasberger.ca

Status

Decision: GO (per-member app-password model — D-09)


How to Run the Spike

cd apps/api
# build first (no tsx dependency): pnpm --filter @familysync/api build
FASTMAIL_EMAIL=<you@fastmail.com> \
FASTMAIL_APP_PASSWORD=<app-password-from-fastmail-settings> \
node dist/broker/spike.js

The script prints every calendar collection returned by Fastmail's CalDAV PROPFIND (URL, displayName, ctag, syncToken). It never logs the password.


Results

Calendars Discovered

displayName URL ctag returned? syncToken returned?
Calendar https://caldav.fastmail.com/dav/calendars/user/me@lucasberger.ca/2180A37A-806E-11EB-872C-AE53E9CB9923/ yes (1615249618-218118) yes (data:,1615249618-218118)
USA Holidays https://caldav.fastmail.com/dav/calendars/user/me@lucasberger.ca/2da291bc-7962-4a0e-94bc-7bb135c90d6e/ yes (1635037263-211516) yes (data:,1635037263-211516)

Questions Resolved

Question Finding
Does one Fastmail app password see all of that account's calendars? Yes — a single app password (scope "Mail, Contacts & Calendars") enumerated every collection owned by the account via PROPFIND.
Does the broker need cross-account ACL sharing to read a member's personal calendar? No — under the locked design (D-09) each member supplies their own app password; the broker stores one encrypted credential per member and aggregates. Cross-account ACL sharing (the original CAL-08 risk) is not on the critical path.
Which change-detection field does Fastmail use — ctag or syncToken? Both are returned. The poller uses ctag as the primary change signal with syncToken available as a fallback (matches the SKELETON poller design).
Can the broker actually fetch + cache real events (CAL-01)? Yes — a live REPORT against the Calendar collection fetched and cached 503 events into calendar_events, with correct D-13 handling (timed events → dtstart_utc set, dtstart_date null, all_day false).

Decision

Decision: GO

Rationale: The personal-calendar read path is proven end-to-end against live Fastmail. A single app password reaches every calendar owned by its account, and the broker successfully fetched and cached real events (CAL-01). The original CAL-08 worry — whether the broker token could see another member's personal calendar via Fastmail cross-account share+accept — is moot: the project already locked the per-member app-password model (D-09), where each member contributes their own credential. That model is validated here. The wife's personal calendar is reached the same way (her own app password), onboarded in Phase 2.

Unified-view consequence: No degradation. The unified color-coded calendar (shared + each member's personal) is achievable by storing one encrypted app password per member and aggregating their collections — no fallback to shared-family-only is needed.

Fallback (not exercised): Had a single token been required to span accounts and failed, the fallback was shared-family-only in v1 with personal overlay deferred. Not needed.


Notes

  • App password scope required: "Mail, Contacts & Calendars" (Fastmail Settings → Privacy & Security → App Passwords).
  • The password was passed via a gitignored apps/api/.env.spike for this one-off read; it is not committed anywhere.
  • CAL-01 proof seeded a placeholder broker user (oidc_sub = broker-me@lucasberger.ca) in the dev MariaDB to satisfy the FK; real users are created via Authelia OIDC login. The 503 cached events remain in the dev DB and back the landing-page EventProof.
  • Encrypted-credential storage (member_credentials via AES-256-GCM) is unit-tested; the live insert + poller-driven sync per member is wired in Phase 2 (wife onboarding) once users exist via OIDC.
  • Results here resolve RESEARCH Open Question 1 + Assumptions A1/A2/A3.
  • Gate 2 (live Authelia login over Pangolin + SSE smoke test) remains pending operator action; tracked as HUMAN-UAT for Phase 1.