docs(01-04): record CAL-08 GO decision — broker read path proven live
Live spike against me@lucasberger.ca: one app password enumerated both account calendars (PROPFIND), and a REPORT fetched + cached 503 real events into MariaDB with correct D-13 timed/all-day handling (CAL-01). Per-member app-password model (D-09) validated; no cross-account ACL needed; no fallback required. Gate 2 (live Authelia/Pangolin login + SSE smoke test) remains pending operator action.
This commit is contained in:
@@ -3,10 +3,11 @@
|
|||||||
**Requirement:** CAL-08
|
**Requirement:** CAL-08
|
||||||
**Phase:** 01-foundation-broker-spike
|
**Phase:** 01-foundation-broker-spike
|
||||||
**Spike script:** `apps/api/src/broker/spike.ts`
|
**Spike script:** `apps/api/src/broker/spike.ts`
|
||||||
|
**Run:** 2026-06-04, live against Fastmail account `me@lucasberger.ca`
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
**Decision: [PENDING — fill in after running the spike]**
|
**Decision: GO** (per-member app-password model — D-09)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -14,50 +15,63 @@
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd apps/api
|
cd apps/api
|
||||||
FASTMAIL_EMAIL=<lucas@fastmail.com> \
|
# build first (no tsx dependency): pnpm --filter @familysync/api build
|
||||||
|
FASTMAIL_EMAIL=<you@fastmail.com> \
|
||||||
FASTMAIL_APP_PASSWORD=<app-password-from-fastmail-settings> \
|
FASTMAIL_APP_PASSWORD=<app-password-from-fastmail-settings> \
|
||||||
pnpm exec tsx src/broker/spike.ts
|
node dist/broker/spike.js
|
||||||
```
|
```
|
||||||
|
|
||||||
The script will print every calendar collection returned by Fastmail's CalDAV PROPFIND.
|
The script prints every calendar collection returned by Fastmail's CalDAV PROPFIND (URL,
|
||||||
|
displayName, ctag, syncToken). It never logs the password.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Results
|
## Results
|
||||||
|
|
||||||
> Fill in after running the spike.
|
|
||||||
|
|
||||||
### Calendars Discovered
|
### Calendars Discovered
|
||||||
|
|
||||||
| displayName | URL | ctag returned? | syncToken returned? |
|
| displayName | URL | ctag returned? | syncToken returned? |
|
||||||
|-------------|-----|----------------|---------------------|
|
|-------------|-----|----------------|---------------------|
|
||||||
| (fill in) | (fill in) | (fill in) | (fill in) |
|
| 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
|
### Questions Resolved
|
||||||
|
|
||||||
| Question | Finding |
|
| Question | Finding |
|
||||||
|----------|---------|
|
|----------|---------|
|
||||||
| Does Lucas's app password see the shared family calendar? | (fill in) |
|
| 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 Lucas's app password see Lucas's personal calendar? | (fill in) |
|
| 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? | (fill in) |
|
| 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
|
||||||
|
|
||||||
**Decision: GO / NO-GO** (circle one — delete the other after filling in)
|
**Decision: GO**
|
||||||
|
|
||||||
**Rationale:** (fill in after seeing the spike output)
|
**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.
|
||||||
|
|
||||||
**If NO-GO — Fallback:**
|
**Unified-view consequence:** No degradation. The unified color-coded calendar (shared +
|
||||||
Proceed with shared-family-only calendar in Phase 1. Personal calendar overlay moves to v1.x.
|
each member's personal) is achievable by storing one encrypted app password per member and
|
||||||
Document the Fastmail restriction that prevented per-member personal calendar access.
|
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
|
## Notes
|
||||||
|
|
||||||
- App password scope required: "Mail, Contacts & Calendars"
|
- App password scope required: "Mail, Contacts & Calendars" (Fastmail Settings → Privacy & Security → App Passwords).
|
||||||
- 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.
|
||||||
- The password is passed via env for this one-off read-only enumeration; 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.
|
||||||
- Results here resolve RESEARCH Open Questions 1 + Assumptions A1/A2/A3
|
- 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.
|
||||||
|
|||||||
Reference in New Issue
Block a user