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)
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.
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.