docs(03-10): complete outbox ICS builder wiring plan summary
CR-02, CR-03, WR-01, WR-04, WR-08, IN-01 closed.
This commit is contained in:
@@ -0,0 +1,124 @@
|
|||||||
|
---
|
||||||
|
phase: 03-event-write-back-pwa-install
|
||||||
|
plan: "10"
|
||||||
|
subsystem: api-broker
|
||||||
|
tags: [tdd, gap-closure, ics-builder, outbox-worker, vevent, rfc5545, credentials]
|
||||||
|
dependency_graph:
|
||||||
|
requires:
|
||||||
|
- 03-09 (canonical title/start/end form JSON shape in calendarOutbox payload)
|
||||||
|
provides:
|
||||||
|
- 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)
|
||||||
|
affects:
|
||||||
|
- 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
|
||||||
|
tech_stack:
|
||||||
|
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)"
|
||||||
|
decisions:
|
||||||
|
- "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"
|
||||||
|
key_files:
|
||||||
|
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
|
||||||
|
metrics:
|
||||||
|
duration_minutes: 6
|
||||||
|
completed_date: "2026-06-06"
|
||||||
|
tasks_completed: 2
|
||||||
|
files_modified: 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
|
||||||
Reference in New Issue
Block a user