diff --git a/.planning/phases/01-foundation-broker-spike/01-01-SUMMARY.md b/.planning/phases/01-foundation-broker-spike/01-01-SUMMARY.md new file mode 100644 index 0000000..c61f6a2 --- /dev/null +++ b/.planning/phases/01-foundation-broker-spike/01-01-SUMMARY.md @@ -0,0 +1,214 @@ +--- +phase: 01-foundation-broker-spike +plan: "01" +subsystem: infra +tags: [hono, drizzle, mariadb, mysql2, vitest, docker, pnpm, react, vite, typescript] + +# Dependency graph +requires: [] +provides: + - pnpm monorepo workspace (apps/api + apps/pwa) + - Hono API scaffold with /health route (unauthenticated, real DB round-trip) + - Drizzle ORM schema: users, memberCredentials, calendars, calendarEvents + - drizzle(mysql2 pool) db singleton + - Docker Compose stack: api + mariadb:11 (healthcheck) + redis + - Vitest harness with Wave 0 test stubs + - React PWA shell fetching /health + - drizzle.config.ts for drizzle-kit push/migrate +affects: + - 01-02 (OIDC auth — imports db, users schema) + - 01-03 (broker — imports db, all schemas, crypto pattern) + - 01-04 (spike — imports broker module) + +# Tech tracking +tech-stack: + added: + - hono@4.12.23 + - "@hono/node-server@2.0.4" + - "@hono/oidc-auth@1.8.3" + - "@hono/zod-validator@0.8.0" + - drizzle-orm@0.45.2 + - drizzle-kit@0.31.10 + - mysql2@3.22.4 + - tsdav@2.2.2 + - ical.js@2.2.1 + - zod@^3.25.0 + - node-cron@^4.2.1 + - vitest@^4.1.8 + - react@^19.0.0 + - "@tanstack/react-query@5.101.0" + - zustand@5.0.14 + - vite@8.0.16 + patterns: + - Hono app exported from src/index.ts for testability (no server start on import) + - db singleton pattern (drizzle mysql2 pool, connectionLimit 10) + - vi.mock at module top level for test isolation (Vitest hoisting) + - Wave 0 test stubs using it.todo to document future tests before implementation + +key-files: + created: + - package.json (root workspace, pnpm@11.5.1) + - pnpm-workspace.yaml (apps/*, allowBuilds.esbuild: true) + - .gitignore (.env excluded — secrets never committed) + - .env.example (all env var names documented) + - docker-compose.yml (api + mariadb:11 + redis) + - docker-compose.dev.yml (dev overrides) + - apps/api/package.json (pinned deps) + - apps/api/tsconfig.json (strict, NodeNext, ES2023) + - apps/api/Dockerfile (node:22-alpine, multi-stage) + - apps/api/vitest.config.ts (environment: node, globals: true) + - apps/api/drizzle.config.ts (dialect: mysql) + - apps/api/src/db/schema.ts (users/memberCredentials/calendars/calendarEvents) + - apps/api/src/db/client.ts (db export) + - apps/api/src/routes/health.ts (GET / with SELECT 1 round-trip) + - apps/api/src/index.ts (Hono app, /health mounted before auth) + - apps/api/tests/health.test.ts (2 tests pass) + - apps/api/tests/helpers/db.ts (mock helpers + sample VEVENTs) + - apps/api/tests/auth/user.test.ts (5 todos — Plan 02) + - apps/api/tests/broker/crypto.test.ts (5 todos — Plan 03) + - apps/api/tests/broker/sync.test.ts (6 todos — Plan 03) + - apps/api/tests/broker/poller.test.ts (5 todos — Plan 03) + - apps/pwa/package.json + - apps/pwa/tsconfig.json + - apps/pwa/vite.config.ts (proxy /health + /api to :3000) + - apps/pwa/index.html + - apps/pwa/src/main.tsx (QueryClientProvider) + - apps/pwa/src/App.tsx (fetches /health, renders stack: up/down) + modified: [] + +key-decisions: + - "Export app from src/index.ts without auto-starting server: enables direct import in Vitest tests without binding a port" + - "Use vi.mock at module top level (not inside test): Vitest hoists vi.mock — placing inside describe/it causes warnings" + - "pnpm-workspace.yaml allowBuilds.esbuild: true: pnpm 11 uses allowBuilds syntax, not onlyBuiltDependencies" + - "zod pinned at ^3.25.0 (not ^4): conservative per RESEARCH — @hono/zod-validator@0.8.0 accepts both but v3 avoids unknown v4 API differences" + +patterns-established: + - "Pattern: Hono testability — export app from index.ts, use import.meta.url guard to start server only when run directly" + - "Pattern: db mock — vi.mock('../src/db/client.js') at module level; override per-test with vi.mocked().mockRejectedValueOnce" + - "Pattern: Wave 0 stubs — it.todo with plan reference so future agents know which plan fills each test" + +requirements-completed: [CAL-01] + +# Metrics +duration: 6min +completed: "2026-06-04" +--- + +# Phase 01 Plan 01: Walking Skeleton — Summary + +**pnpm monorepo with Hono API, Drizzle/MariaDB schema (4 tables), Docker Compose stack, and /health route with real DB round-trip — Tasks 1-2 complete; stopped at checkpoint Task 3 (drizzle-kit push requires running Docker stack)** + +## Performance + +- **Duration:** ~6 min +- **Started:** 2026-06-04T13:46:55Z +- **Completed:** 2026-06-04T13:53:00Z (Tasks 1-2; Task 3 is a human-action checkpoint) +- **Tasks:** 2 of 3 complete (Task 3 is a blocking checkpoint) +- **Files modified:** 28 + +## Accomplishments + +- Full pnpm monorepo scaffold: apps/api (Hono + Drizzle + all pinned deps) and apps/pwa (Vite/React 19 + TanStack Query) +- Drizzle schema with all 4 tables (users, memberCredentials, calendars, calendarEvents) following D-10 (oidc_iss+oidc_sub composite key) and D-13 (separate dtstart_utc/dtstart_date for all-day events) +- /health route with real DB round-trip (SELECT 1) — GREEN: 2 tests pass, 503 on DB error +- Docker Compose stack with mariadb:11 healthcheck, api depends_on service_healthy, redis stub +- Wave 0 test harness: 5 test files, 21 todos (auth/user, broker/crypto, broker/sync, broker/poller) + 2 passing health tests +- React PWA shell fetching /health and rendering stack: up/down + +## Task Commits + +Each task committed atomically: + +1. **Task 1: Scaffold monorepo, Docker Compose stack, and Vitest harness** — `3f59156` (chore) +2. **Task 2: RED gate (failing health test)** — `f31711a` (test) +3. **Task 2: Drizzle schema + DB client + /health slice (GREEN)** — `96cda58` (feat) + +## Files Created/Modified + +Key files (full list in frontmatter key-files): + +- `apps/api/src/db/schema.ts` — 4 Drizzle mysqlTable definitions with all constraints +- `apps/api/src/db/client.ts` — `db` singleton export (drizzle mysql2 pool) +- `apps/api/src/routes/health.ts` — GET /health with SELECT 1 round-trip +- `apps/api/src/index.ts` — Hono app, /health before auth, serveStatic +- `apps/api/drizzle.config.ts` — drizzle-kit push/migrate config +- `apps/pwa/src/App.tsx` — React shell fetching /health +- `docker-compose.yml` — mariadb:11 + healthcheck + api depends_on service_healthy + +## Decisions Made + +- Exported `app` from `src/index.ts` without auto-starting server (import.meta.url guard) so Vitest tests can import it directly without a real HTTP port +- pnpm 11 uses `allowBuilds.esbuild: true` in pnpm-workspace.yaml (not `onlyBuiltDependencies`) — pnpm rewrote this during install +- zod pinned `^3.25.0` per RESEARCH recommendation (not v4) + +## Deviations from Plan + +None — plan executed exactly as specified. One minor pnpm API difference (allowBuilds syntax) was auto-handled. + +### Auto-fixed Issues + +**1. [Rule 3 - Blocking] pnpm 11 allowBuilds syntax** +- **Found during:** Task 1 (pnpm install) +- **Issue:** `pnpm install` failed with `ERR_PNPM_IGNORED_BUILDS: esbuild@*`. pnpm 11 uses `allowBuilds` map (not `onlyBuiltDependencies` list used in older versions) +- **Fix:** Set `allowBuilds.esbuild: true` in pnpm-workspace.yaml +- **Files modified:** pnpm-workspace.yaml +- **Verification:** `pnpm install` succeeded; all deps installed +- **Committed in:** `3f59156` (Task 1 commit) + +**2. [Rule 1 - Bug] vi.mock hoisting in health test** +- **Found during:** Task 2 (writing TDD RED test) +- **Issue:** Placing `vi.mock()` inside `describe()` blocks caused Vitest hoisting warnings; tests used `resetModules` approach which conflicted with hoisting behavior +- **Fix:** Moved `vi.mock` to module top level; used `vi.mocked().mockRejectedValueOnce()` for per-test override +- **Files modified:** apps/api/tests/health.test.ts +- **Verification:** Both health tests pass; no hoisting warnings +- **Committed in:** `96cda58` (Task 2 feat commit) + +--- + +**Total deviations:** 2 auto-fixed (1 blocking, 1 bug) +**Impact on plan:** Both fixes necessary for install/tests to work. No scope creep. + +## Issues Encountered + +- pnpm 11 changed the `onlyBuiltDependencies` API to `allowBuilds` map syntax — resolved automatically + +## User Setup Required + +Task 3 requires manual steps. See checkpoint details returned to orchestrator: + +1. Copy `.env.example` to `.env` and set `DB_PASSWORD` + `DB_ROOT_PASSWORD` +2. `docker compose up -d mariadb` and wait for healthy +3. `DB_HOST=127.0.0.1 ... pnpm exec drizzle-kit push` from apps/api +4. Confirm `SHOW TABLES` lists 4 tables +5. `docker compose up -d` and `curl http://localhost:3000/health` → `{"ok":true,"db":"up"}` + +## Known Stubs + +None. The `serveStatic` warning for `./public` in tests is expected (no built PWA in test env) and is not a stub — the route exists and will serve correctly when the PWA is built. + +## Threat Surface Scan + +No new threat surface beyond what was planned in the threat model: +- T-01-01: `.env` excluded from git via `.gitignore` ✓ +- T-01-02: `familysync` user (not root) in docker-compose.yml ✓ +- T-01-03: `/health` unauthenticated, returns only `{ok, db}` ✓ + +## Next Phase Readiness + +- Task 3 checkpoint must be cleared first (drizzle-kit push + Docker stack verification) +- After Task 3: Plan 02 (OIDC auth) can proceed — imports `db`, `users` schema +- Plan 03 (broker) depends on Plan 02 identity layer + this schema + +## Self-Check: PASSED + +- `apps/api/src/db/schema.ts` exists: FOUND +- `apps/api/src/db/client.ts` exists: FOUND +- `apps/api/src/routes/health.ts` exists: FOUND +- `apps/api/src/index.ts` exists: FOUND +- `docker-compose.yml` exists with healthcheck: FOUND +- `apps/pwa/src/App.tsx` fetches /health: FOUND +- Commits 3f59156, f31711a, 96cda58: FOUND + +--- +*Phase: 01-foundation-broker-spike* +*Completed: 2026-06-04 (Tasks 1-2; Task 3 at checkpoint)*