Fix the events router's two blockers that made the write path dead on arrival: align the server zod schema to the PWA's CreateEventPayload shape (title/start/end), and implement real OIDC iss/sub → users.id resolution on all five write handlers via upsertUser.
cd apps/api && npx vitest run tests/routes/events.test.ts: 19 tests pass
npx tsc --noEmit in apps/api: clean (no errors)
grep -n 'summary\|dtstart\|dtend' eventFieldsSchema: CLEAN (no old names)
grep -c 'For now return 401' events.ts: 0 stubs remain
grep -c 'upsertUser' events.ts: 3 (import + call in resolveUserId)
Decisions Made
D-CR01: Server adopts client field names title/start/end. No internal rename map — one canonical name set end-to-end from PWA through events router to calendarOutbox payload to outbox worker (plan 03-10).
D-CR06: resolveUserId is now async. Dev-bypass path (c.get('user')) is unchanged. Production OIDC path calls getAuth(c) then upsertUser(iss, sub, email) to resolve DB user id. Returns null only when no session exists.
Deviations from Plan
Auto-fixed Issues
None. The plan was executed exactly as written, including updating the three existing write tests that previously used the old field names (summary/dtstart/dtend) — this was the correct fix since those tests were testing against the wrong boundary (as the review noted).
Test Infrastructure Deviation (Rule 3)
The worktree has no node_modules — the pnpm workspace installs them in the main repo. Created a symlink apps/api/node_modules → /home/luc/Projects/familysync/apps/api/node_modules so vitest could run from within the worktree. This is a standard git-worktree-with-pnpm-workspace setup requirement.
No new network endpoints, auth paths, file access patterns, or schema changes introduced. The resolveUserId change closes a security gap (CR-06) by ensuring unauthenticated requests correctly 401 while authenticated OIDC sessions get through.