Files
familysync/.planning/milestones/v1.0-phases/03-event-write-back-pwa-install/03-10-SUMMARY.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

7.1 KiB

phase, plan, subsystem, tags, dependency_graph, tech_stack, decisions, key_files, metrics
phase plan subsystem tags dependency_graph tech_stack decisions key_files metrics
03-event-write-back-pwa-install 10 api-broker
tdd
gap-closure
ics-builder
outbox-worker
vevent
rfc5545
credentials
requires provides affects
03-09 (canonical title/start/end form JSON shape in calendarOutbox payload)
ics-builder-wired-to-dispatch (outboxWorker calls buildVeventString for create/update)
wR04-dtend-plus-one (vevent.ts all-day DTEND exclusive RFC-5545 fix)
cr03-fail-closed-credentials (outbox never PUTs with empty auth)
wR01-backoff-15s-first (first retry waits 15s not 60s)
apps/api/src/broker/outboxWorker.ts
apps/api/src/broker/vevent.ts
apps/api/src/routes/events.ts
apps/api/tests/broker/outboxWorker.test.ts
apps/api/tests/broker/vevent.test.ts
added patterns
TDD RED→GREEN per task
vi.hoisted() + per-test crypto mock for loadClientForUser failure scenarios
Table-differentiated db select mock (credential vs outbox queries)
WR-04 owning boundary is vevent.ts only — form/routes pass inclusive end unchanged
CR-03: loadClientForUser throws propagate to outer catch (row stays pending); no empty-cred fallback
WR-01: backoff index is row.attemptCount (the failed attempt, 0-based) not nextAttemptCount
modified
apps/api/src/broker/outboxWorker.ts
apps/api/src/broker/vevent.ts
apps/api/src/routes/events.ts
apps/api/tests/broker/outboxWorker.test.ts
apps/api/tests/broker/vevent.test.ts
duration_minutes completed_date tasks_completed files_modified
6 2026-06-06 2 5

Phase 03 Plan 10: Outbox Worker ICS Builder Wiring Summary

Wire the VEVENT builder into the outbox worker dispatch path, pin the D-13 DATE/DATETIME contract and exclusive all-day DTEND with a direct unit test, and fix three correctness defects: empty-credential PUT fallback (CR-03), wrong backoff index (WR-01), and bare crypto.randomUUID() call (WR-08).

Tasks Completed

Task Name Commit Files
1 RED Add D-13 contract + ICS wiring test (vevent + worker) 813a7ba vevent.test.ts, outboxWorker.test.ts
1 GREEN Wire buildVeventString, fix all-day DTEND+1 (CR-02, WR-04) c03b479 outboxWorker.ts, vevent.ts
2 RED Add CR-03 + WR-01 RED tests (crypto mock, backoff timing) c178dce outboxWorker.test.ts
2 GREEN Fail closed on bad creds, fix backoff index, explicit randomUUID c21b040 outboxWorker.ts, events.ts

Verification

  • cd apps/api && npx vitest run tests/broker/ — 51/51 pass (7 files)
  • cd apps/api && npm run build — clean TypeScript compile
  • grep -c 'buildVeventString' apps/api/src/broker/outboxWorker.ts — 3 (import + 2 call sites, IN-01 closed)
  • grep -c 'D-13 form-parsed contract' apps/api/tests/broker/vevent.test.ts — 1
  • grep -c "createFastmailClient('', '')" apps/api/src/broker/outboxWorker.ts — 0 (CR-03 closed)
  • grep -c "import { randomUUID } from 'node:crypto'" apps/api/src/routes/events.ts — 1 (WR-08 closed)
  • grep -c 'crypto.randomUUID(' apps/api/src/routes/events.ts — 0

Decisions Made

  • WR-04 owning boundary: The RFC-5545 exclusive DTEND (+1 day for all-day events) is fixed in vevent.ts only. The form/route layer continues passing the user-entered inclusive end date unchanged. This is correct because vevent.ts is the single serialization point for all write paths — fixing it there covers all callers.
  • CR-03 approach: Removed the try/catch fallback that called createFastmailClient('', ''). loadClientForUser now throws naturally; the outer per-row catch in runOutboxDrain logs the error and leaves the row pending — it will be retried on the next drain cycle when credentials are available.
  • WR-01 index correction: Changed BACKOFF_SECONDS[nextAttemptCount] to BACKOFF_SECONDS[row.attemptCount]. row.attemptCount is the attempt that just failed (0-indexed), so the first failure uses index 0 = 15s. nextAttemptCount is persisted as the new attemptCount value.

TDD Gate Compliance

Both tasks followed strict RED→GREEN:

  • Task 1: test(03-10) commit (813a7ba) → feat(03-10) commit (c03b479)
  • Task 2: test(03-10) commit (c178dce) → feat(03-10) commit (c21b040)

RED confirmed failing for correct reasons before each GREEN commit:

  • Task 1 RED: vevent DTEND=20260610 not 20260611; worker passed raw JSON not BEGIN:VCALENDAR
  • Task 2 RED: CR-03 worker updated row to 'done' via empty-cred path; WR-01 backoff was 60s not 15s

Deviations from Plan

Auto-fixed Issues

None — plan executed exactly as written.

Infrastructure

The worktree lacks node_modules. Created apps/api/node_modules symlink pointing to the main repo's apps/api/node_modules (standard pnpm-workspace + git-worktree pattern, same as 03-09).

The existing db mock in outboxWorker.test.ts returned the same rows for any db.select().from(anyTable) call. After removing the empty-cred fallback (CR-03), loadClientForUser needed the db mock to return a proper credential row when called with memberCredentials. Extended mockFromFn to distinguish the two tables via JSON.stringify(table).includes('member_credentials') and introduced a wireMockChain() helper shared across all describe blocks.

Issues Closed

ID Description
CR-02 Worker was passing raw form JSON to CalDAV PUT — now builds VCALENDAR via buildVeventString
CR-03 Worker fell back to empty-cred createFastmailClient on any credential error — removed fallback
WR-01 First transient retry used BACKOFF_SECONDS[1]=60s instead of BACKOFF_SECONDS[0]=15s — fixed index
WR-04 All-day events emitted DTEND = DTSTART (no +1 day) — fixed in vevent.ts (owning boundary)
WR-08 events.ts used bare crypto.randomUUID() — replaced with import { randomUUID } from 'node:crypto'
IN-01 buildVeventString was dead code (never called outside vevent.ts) — now has 2 live call sites

Known Stubs

None. All changes are functional code. The worker now builds real RFC-5545 VCALENDAR strings from stored form JSON.

Threat Flags

No new network endpoints, auth paths, or schema changes. The CR-03 fix improves security posture by ensuring the worker never PUTs with empty Basic-auth credentials.

Self-Check: PASSED

  • apps/api/src/broker/outboxWorker.ts: FOUND
  • apps/api/src/broker/vevent.ts: FOUND
  • apps/api/src/routes/events.ts: FOUND
  • apps/api/tests/broker/outboxWorker.test.ts: FOUND
  • apps/api/tests/broker/vevent.test.ts: FOUND
  • 813a7ba (test RED task1): FOUND
  • c03b479 (feat GREEN task1): FOUND
  • c178dce (test RED task2): FOUND
  • c21b040 (feat GREEN task2): FOUND