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
@@ -3,7 +3,7 @@ phase: 01-foundation-broker-spike
plan: 03
type: execute
wave: 2
depends_on: ["01-01"]
depends_on: ['01-01']
files_modified:
- apps/api/src/broker/crypto.ts
- apps/api/src/broker/client.ts
@@ -19,40 +19,40 @@ requirements: [CAL-01]
must_haves:
truths:
- "App passwords are encrypted at rest with AES-256-GCM (key from env) and decrypt losslessly; never exposed to the frontend"
- "The broker creates a Fastmail CalDAV client (Basic auth, app password) and fetches calendars via PROPFIND"
- "syncCalendar parses VEVENTs with ical.js and upserts them into calendar_events, storing all-day events as DATE (dtstart_date) never DATETIME"
- 'App passwords are encrypted at rest with AES-256-GCM (key from env) and decrypt losslessly; never exposed to the frontend'
- 'The broker creates a Fastmail CalDAV client (Basic auth, app password) and fetches calendars via PROPFIND'
- 'syncCalendar parses VEVENTs with ical.js and upserts them into calendar_events, storing all-day events as DATE (dtstart_date) never DATETIME'
- "The poller skips DB writes when a calendar's ctag is unchanged (sync-token with ctag fallback)"
- "GET /api/events returns cached events from MariaDB (never a live Fastmail call per request)"
- 'GET /api/events returns cached events from MariaDB (never a live Fastmail call per request)'
artifacts:
- path: "apps/api/src/broker/crypto.ts"
provides: "encryptPassword/decryptPassword (AES-256-GCM, key from APP_PASSWORD_ENCRYPTION_KEY)"
exports: ["encryptPassword", "decryptPassword"]
- path: "apps/api/src/broker/client.ts"
provides: "createFastmailClient(email, appPassword) → tsdav DAVClient"
exports: ["createFastmailClient"]
- path: "apps/api/src/broker/sync.ts"
provides: "syncCalendar: REPORT → ical.js → calendar_events upsert"
exports: ["syncCalendar"]
- path: "apps/api/src/broker/poller.ts"
provides: "startBrokerPoller (node-cron 5-min) with ctag change detection"
exports: ["startBrokerPoller"]
- path: "apps/api/src/routes/events.ts"
provides: "GET /api/events → cached events from DB"
exports: ["eventsRouter"]
- path: 'apps/api/src/broker/crypto.ts'
provides: 'encryptPassword/decryptPassword (AES-256-GCM, key from APP_PASSWORD_ENCRYPTION_KEY)'
exports: ['encryptPassword', 'decryptPassword']
- path: 'apps/api/src/broker/client.ts'
provides: 'createFastmailClient(email, appPassword) → tsdav DAVClient'
exports: ['createFastmailClient']
- path: 'apps/api/src/broker/sync.ts'
provides: 'syncCalendar: REPORT → ical.js → calendar_events upsert'
exports: ['syncCalendar']
- path: 'apps/api/src/broker/poller.ts'
provides: 'startBrokerPoller (node-cron 5-min) with ctag change detection'
exports: ['startBrokerPoller']
- path: 'apps/api/src/routes/events.ts'
provides: 'GET /api/events → cached events from DB'
exports: ['eventsRouter']
key_links:
- from: "apps/api/src/broker/poller.ts"
to: "apps/api/src/broker/crypto.ts"
via: "decryptPassword before client creation"
- from: 'apps/api/src/broker/poller.ts'
to: 'apps/api/src/broker/crypto.ts'
via: 'decryptPassword before client creation'
pattern: "decryptPassword\\("
- from: "apps/api/src/broker/sync.ts"
to: "apps/api/src/db/client.ts"
via: "calendarEvents upsert"
pattern: "calendarEvents"
- from: "apps/api/src/routes/events.ts"
to: "apps/api/src/db/client.ts"
via: "cache read (no live CalDAV)"
pattern: "from ['\"].*db/client"
- from: 'apps/api/src/broker/sync.ts'
to: 'apps/api/src/db/client.ts'
via: 'calendarEvents upsert'
pattern: 'calendarEvents'
- from: 'apps/api/src/routes/events.ts'
to: 'apps/api/src/db/client.ts'
via: 'cache read (no live CalDAV)'
pattern: 'from [''"].*db/client'
---
<objective>
@@ -80,6 +80,7 @@ Output: crypto helper, broker client/sync/poller, /api/events router, all unit-t
</context>
<artifacts_produced>
## Artifacts this phase produces (Plan 03)
New files: `apps/api/src/broker/crypto.ts`, `apps/api/src/broker/client.ts`, `apps/api/src/broker/sync.ts`, `apps/api/src/broker/poller.ts`, `apps/api/src/routes/events.ts`.
@@ -113,6 +114,7 @@ New env vars: `APP_PASSWORD_ENCRYPTION_KEY` (64-char hex = 32 bytes).
Add `APP_PASSWORD_ENCRYPTION_KEY` to `.env.example` with a comment showing the generator: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`.
Fill `tests/broker/crypto.test.ts` GREEN: set a fixed test key in the test, assert (a) roundtrip lossless; (b) two encrypts of same plaintext differ; (c) tampering authTag causes decrypt to throw.
</action>
<verify>
<automated>cd apps/api && pnpm vitest run tests/broker/crypto.test.ts --reporter=verbose</automated>
@@ -149,6 +151,7 @@ New env vars: `APP_PASSWORD_ENCRYPTION_KEY` (64-char hex = 32 bytes).
Create `src/routes/events.ts` exporting `eventsRouter` (Hono): GET / reads from `calendarEvents` via `db` (cache only — NEVER call Fastmail per request, ARCHITECTURE anti-pattern), returns the rows (id, uid, allDay, dtstart_utc, dtstart_date, raw_vevent or a minimal shape). This router is mounted in Plan 04.
Fill `tests/broker/sync.test.ts` GREEN with a mocked tsdav client returning captured raw VEVENT strings (timed + all-day fixtures — Wave 0 fixture requirement). Assert the dtstart_utc vs dtstart_date split, all_day flag, and UID-upsert idempotency.
</action>
<verify>
<automated>cd apps/api && pnpm vitest run tests/broker/sync.test.ts --reporter=verbose && pnpm exec tsc --noEmit</automated>
@@ -183,6 +186,7 @@ New env vars: `APP_PASSWORD_ENCRYPTION_KEY` (64-char hex = 32 bytes).
Create `src/broker/poller.ts` exporting `startBrokerPoller()` (and an internal `runPoll()` exported for tests). Per RESEARCH poller pattern: `schedule('*/5 * * * *', runPoll)` using node-cron v4 (Pitfall 4 — basic 5-field cron API is stable). `runPoll`: select all `memberCredentials`; for each, `decryptPassword`, `createFastmailClient`, `fetchCalendars`; for each davCal, compare `davCal.ctag ?? davCal.syncToken ?? null` to the stored calendars row ctag — if equal and non-null, `continue` (skip); else `syncCalendar`. Make `runPoll` injectable/testable (accept the db + client factory or use module mocks) so the unit test can assert skip-on-unchanged without hitting Fastmail.
Fill `tests/broker/poller.test.ts` GREEN: mock fetchCalendars to return a calendar with a known ctag matching a stored row → assert syncCalendar spy NOT called; then a changed ctag → assert syncCalendar IS called.
</action>
<verify>
<automated>cd apps/api && pnpm vitest run tests/broker/poller.test.ts --reporter=verbose</automated>
@@ -199,24 +203,26 @@ New env vars: `APP_PASSWORD_ENCRYPTION_KEY` (64-char hex = 32 bytes).
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| Boundary | Description |
| -------------------------------- | ------------------------------------------------------------------------------------ |
| member_credentials (DB) → broker | App passwords stored encrypted; only broker/crypto.ts decrypts; never leaves backend |
| Broker → Fastmail CalDAV | Outbound Basic auth over TLS; sole holder of Fastmail I/O |
| Hono /api/events → browser | Returns only cached event data; never credentials or raw app passwords |
| Broker → Fastmail CalDAV | Outbound Basic auth over TLS; sole holder of Fastmail I/O |
| Hono /api/events → browser | Returns only cached event data; never credentials or raw app passwords |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-03-01 | Information Disclosure | Fastmail app password at rest | mitigate | AES-256-GCM with 96-bit IV + auth tag (crypto.ts); key from APP_PASSWORD_ENCRYPTION_KEY env, never committed/logged (ASVS V6) |
| T-03-02 | Information Disclosure | App password leaking via /api/events | mitigate | events route reads only calendar_events (event data); never joins/returns member_credentials; broker is the only credential reader (D-04) |
| T-03-03 | Tampering | Encrypted-credential integrity | mitigate | GCM auth tag verified on decrypt; tampered ciphertext throws, never silently used |
| T-03-04 | Information Disclosure | Credentials in logs | mitigate | No console logging of decrypted passwords or the encryption key in client.ts / poller.ts |
| T-03-05 | Tampering | Caching client-side event versions | mitigate | Only server-returned objects cached (raw VEVENT verbatim — D-13, Pitfall 14); no write-back in Phase 1 |
| T-03-SC | Tampering | tsdav / ical.js / node-cron installs | accept | All [OK] in RESEARCH § Package Legitimacy Audit (tsdav 3+ yrs official repo, ical.js Mozilla-maintained, node-cron 8+ yrs); no [ASSUMED]/[SUS]/[SLOP] |
| Threat ID | Category | Component | Disposition | Mitigation Plan |
| --------- | ---------------------- | ------------------------------------ | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| T-03-01 | Information Disclosure | Fastmail app password at rest | mitigate | AES-256-GCM with 96-bit IV + auth tag (crypto.ts); key from APP_PASSWORD_ENCRYPTION_KEY env, never committed/logged (ASVS V6) |
| T-03-02 | Information Disclosure | App password leaking via /api/events | mitigate | events route reads only calendar_events (event data); never joins/returns member_credentials; broker is the only credential reader (D-04) |
| T-03-03 | Tampering | Encrypted-credential integrity | mitigate | GCM auth tag verified on decrypt; tampered ciphertext throws, never silently used |
| T-03-04 | Information Disclosure | Credentials in logs | mitigate | No console logging of decrypted passwords or the encryption key in client.ts / poller.ts |
| T-03-05 | Tampering | Caching client-side event versions | mitigate | Only server-returned objects cached (raw VEVENT verbatim — D-13, Pitfall 14); no write-back in Phase 1 |
| T-03-SC | Tampering | tsdav / ical.js / node-cron installs | accept | All [OK] in RESEARCH § Package Legitimacy Audit (tsdav 3+ yrs official repo, ical.js Mozilla-maintained, node-cron 8+ yrs); no [ASSUMED]/[SUS]/[SLOP] |
</threat_model>
<verification>
@@ -227,12 +233,13 @@ New env vars: `APP_PASSWORD_ENCRYPTION_KEY` (64-char hex = 32 bytes).
</verification>
<success_criteria>
- App passwords encrypted at rest (AES-256-GCM), lossless roundtrip, tamper-detecting
- Broker discovers calendars and syncs VEVENTs into the cache with correct all-day DATE handling
- Poller skips unchanged calendars (ctag detection)
- /api/events serves cached events without a live Fastmail call
- All three unit test files green
</success_criteria>
</success_criteria>
<output>
Create `.planning/phases/01-foundation-broker-spike/01-03-SUMMARY.md` when done.