{ "_meta": { "updated_at": "2026-06-09T00:00:00Z", "commit": "01f7456b81dd55d477d0bd7530df818f61a873c2", "version": 1 }, "entries": { "broker-cache-api-pattern": { "title": "Broker-Cache-API pattern (two planes never cross)", "decision": "Backend split into a broker plane (apps/api/src/broker/) that owns all Fastmail I/O and an API plane (apps/api/src/routes/) that reads only from MariaDB. Broker crons are not reachable from the HTTP layer.", "files": ["apps/api/src/broker/poller.ts", "apps/api/src/broker/outboxWorker.ts", "apps/api/src/routes/"] }, "write-broker-boundary": { "title": "Write-broker boundary invariant", "decision": "No route file imports tsdav or createFastmailClient; no broker file handles HTTP requests. Routes enqueue calendar_outbox rows and return 202 (optimistic-accept); the outbox worker performs the Fastmail write asynchronously.", "files": ["apps/api/src/routes/events.ts", "apps/api/src/broker/write.ts"] }, "identity-keying": { "title": "Identity keyed on oidc_iss + oidc_sub", "decision": "Users are keyed on oidc_iss + oidc_sub (never email). A hex color from the palette is auto-assigned on first login.", "files": ["apps/api/src/routes/me.ts", "apps/api/src/auth/middleware.ts"] }, "D-03-writable-set": { "title": "D-03 calendar ownership / writable-set predicate", "decision": "Every writable-set query uses WHERE userId = currentUser.id OR isShared = true. Another member's personal calendar is a read-only overlay.", "files": ["apps/api/src/routes/events.ts"] }, "D-13-dual-field-dtstart": { "title": "D-13 all-day vs timed events (dual dtstart fields)", "decision": "dtstart_utc is NULL for all-day events; dtstart_date is NULL for timed events. Never coerce DATE to DATETIME.", "files": ["apps/api/src/db/schema.ts"] }, "D-16-shared-fastmail-account": { "title": "D-16 shared Fastmail account, per-member credentials", "decision": "Both members share one Fastmail account. Calendar identity in DB is (userId, url) — the same collection URL appears once per member credential. CalDAV credential per member is stored AES-256-GCM encrypted in member_credentials.", "files": ["apps/api/src/db/schema.ts", "apps/api/src/broker/poller.ts"] }, "outbox-status-machine": { "title": "Outbox status machine", "decision": "calendar_outbox rows transition pending -> done | failed | dead. failed rows retry up to a limit; dead is terminal. The sync-status endpoint surfaces worst-status-first per uid.", "files": ["apps/api/src/broker/outboxWorker.ts", "apps/api/src/routes/events.ts"] }, "oidc-behind-pangolin": { "title": "OIDC behind Pangolin requires OIDC_AUTH_EXTERNAL_URL", "decision": "OIDC_AUTH_EXTERNAL_URL must be set to the public HTTPS URL to construct a correct redirect_uri; without it the callback resolves to the internal container address.", "files": ["apps/api/src/auth/middleware.ts", "apps/api/src/index.ts"] }, "dev-auth-bypass": { "title": "Dev auth bypass", "decision": "DEV_AUTH_BYPASS=true with NODE_ENV!=production injects DEV_USER via Hono context; OIDC middleware is never mounted in this mode.", "files": ["apps/api/src/auth/devBypass.js", "apps/api/src/index.ts"] }, "pwa-static-serving": { "title": "PWA static serving + SPA fallback", "decision": "Hono serveStatic serves ./public (Vite build output); SPA routes fall through to an index.html catch-all registered after /health, /api/*, and /callback so those win.", "files": ["apps/api/src/index.ts"] }, "schedule-x-routing": { "title": "Schedule-X calendar routing", "decision": "Events are routed to Schedule-X calendars by isShared ? 'shared' : String(ownerUserId) — never by calendarId. hydrateEvents.ts enforces this.", "files": ["apps/pwa/src/hydrateEvents.ts", "apps/pwa/src/components/CalendarShell.tsx"] }, "state-ownership": { "title": "Client state ownership split", "decision": "Server state is owned by TanStack Query; UI-only state (selected range, color map, drawer) by Zustand. Schedule-X renders the calendar UI.", "files": ["apps/pwa/src/store/calendarStore.ts", "apps/pwa/src/components/CalendarShell.tsx"] } } }