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.
This commit is contained in:
Lucas Berger
2026-06-11 20:35:18 -04:00
parent 4bc0445173
commit 982438dc10
398 changed files with 19050 additions and 16382 deletions
@@ -1,6 +1,6 @@
---
phase: 01-foundation-broker-spike
plan: "03"
plan: '03'
subsystem: api
tags: [caldav, ical.js, tsdav, node-cron, aes-256-gcm, drizzle, mariadb, vitest]
@@ -46,22 +46,22 @@ key-files:
- .env.example (APP_PASSWORD_ENCRYPTION_KEY with generator comment)
key-decisions:
- "Store dtstartDate as JS Date at T00:00:00Z (not raw string): Drizzle date column expects a Date or null; ical.js toString().slice(0,10) gives the YYYY-MM-DD, appending T00:00:00Z avoids TZ ambiguity"
- "Export runPoll separately from startBrokerPoller: lets tests invoke one poll cycle synchronously with vi.mock injected deps, avoiding real cron schedule in tests"
- "ctag skip condition: null ctag means first sync (no row stored) → must always sync; only skip when both sides have a non-null matching ctag"
- 'Store dtstartDate as JS Date at T00:00:00Z (not raw string): Drizzle date column expects a Date or null; ical.js toString().slice(0,10) gives the YYYY-MM-DD, appending T00:00:00Z avoids TZ ambiguity'
- 'Export runPoll separately from startBrokerPoller: lets tests invoke one poll cycle synchronously with vi.mock injected deps, avoiding real cron schedule in tests'
- 'ctag skip condition: null ctag means first sync (no row stored) → must always sync; only skip when both sides have a non-null matching ctag'
- "Per-credential try/catch in runPoll: one corrupted or expired credential must not block other members' calendars from syncing"
- "events route imports db but never tsdav or crypto: enforces broker hard boundary (T-03-02)"
- 'events route imports db but never tsdav or crypto: enforces broker hard boundary (T-03-02)'
patterns-established:
- "Pattern: broker boundary — tsdav and credentials are imported exclusively under apps/api/src/broker/; routes never touch Fastmail I/O"
- "Pattern: D-13 dtstart split — use ical.js ICAL.Time.isDate to route all-day vs timed into separate nullable columns"
- "Pattern: ctag null-defence — always use davCal.ctag ?? davCal.syncToken ?? null; Fastmail may return either field"
- 'Pattern: broker boundary — tsdav and credentials are imported exclusively under apps/api/src/broker/; routes never touch Fastmail I/O'
- 'Pattern: D-13 dtstart split — use ical.js ICAL.Time.isDate to route all-day vs timed into separate nullable columns'
- 'Pattern: ctag null-defence — always use davCal.ctag ?? davCal.syncToken ?? null; Fastmail may return either field'
requirements-completed: [CAL-01]
# Metrics
duration: ~multi-session
completed: "2026-06-04"
completed: '2026-06-04'
---
# Phase 01 Plan 03: CalDAV Broker Slice — Summary
@@ -100,7 +100,7 @@ Each task committed with TDD RED → GREEN cycle:
- `apps/api/src/broker/crypto.ts` — encryptPassword / decryptPassword using node:crypto aes-256-gcm; 96-bit IV; JSON payload {iv, authTag, ciphertext} as hex
- `apps/api/src/broker/client.ts` — createFastmailClient(email, appPassword) → tsdav DAVClient; FastmailClient type alias
- `apps/api/src/broker/sync.ts` — syncCalendar: upserts calendars row, fetches REPORT objects, ical.js parses VEVENTs, upserts calendarEvents with D-13 split; onDuplicateKeyUpdate on calendarId+uid
- `apps/api/src/broker/poller.ts` — startBrokerPoller (node-cron */5 * * * *) + runPoll (exported for tests); per-credential try/catch
- `apps/api/src/broker/poller.ts` — startBrokerPoller (node-cron _/5 _ \* \* \*) + runPoll (exported for tests); per-credential try/catch
- `apps/api/src/routes/events.ts` — eventsRouter GET / reads from db.select().from(calendarEvents); no tsdav import
- `apps/api/tests/broker/crypto.test.ts` — roundtrip, IV uniqueness, tamper-throws
- `apps/api/tests/broker/sync.test.ts` — timed dtstart_utc, all-day dtstart_date, same-UID idempotency
@@ -125,6 +125,7 @@ None — the draft poller.ts written by the interrupted agent passed all tests o
## User Setup Required
Add to `.env`:
```
APP_PASSWORD_ENCRYPTION_KEY=<64-char hex> # node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
```
@@ -139,6 +140,7 @@ No external service configuration required for this plan. Live Fastmail integrat
## Threat Surface Scan
No new surface beyond the plan's threat model:
- T-03-01: AES-256-GCM with 96-bit IV + auth tag — implemented in crypto.ts
- T-03-02: /api/events reads cache only, no tsdav import in routes/events.ts
- T-03-03: GCM auth tag verified on decrypt; tampered ciphertext throws (test asserts this)
@@ -157,5 +159,6 @@ No new surface beyond the plan's threat model:
## Self-Check: PASSED
---
*Phase: 01-foundation-broker-spike*
*Completed: 2026-06-04*
_Phase: 01-foundation-broker-spike_
_Completed: 2026-06-04_