docs(03-01): complete foundation-scaffold plan

This commit is contained in:
Lucas Berger
2026-06-05 17:41:41 -04:00
parent bbfccda756
commit a487569c7e
4 changed files with 161 additions and 27 deletions
@@ -0,0 +1,132 @@
---
phase: 03-event-write-back-pwa-install
plan: 01
subsystem: database, testing, infra
tags: [drizzle, mariadb, vitest, vite-plugin-pwa, caldav, outbox]
# Dependency graph
requires:
- phase: 02-calendar-read-display
provides: calendarEvents table, sync.ts upsert loop, existing test infrastructure
provides:
- calendarOutbox table live in MariaDB (calendar_outbox, 3 indexes)
- calendarEvents.objectUrl column live in MariaDB (object_url varchar 1024)
- vite-plugin-pwa installed in apps/pwa
- Five Wave 0 RED test files covering all Phase 3 behaviors (vevent, write, outboxWorker, events routes, InstallPrompt)
affects: [03-02, 03-03, 03-04, 03-05, 03-06, 03-07, 03-08]
# Tech tracking
tech-stack:
added: [vite-plugin-pwa@1.3.0]
patterns:
- mysqlEnum for outbox status/operation columns in Drizzle schema
- objectUrl stored on calendarEvents from obj.url during sync upsert
- Wave 0 RED scaffold: import not-yet-existing modules so test suite fails before implementation
key-files:
created:
- apps/api/tests/broker/vevent.test.ts
- apps/api/tests/broker/write.test.ts
- apps/api/tests/broker/outboxWorker.test.ts
- apps/pwa/src/components/InstallPrompt.test.tsx
modified:
- apps/api/src/db/schema.ts
- apps/api/src/broker/sync.ts
- apps/api/tests/routes/events.test.ts
- apps/pwa/package.json
key-decisions:
- "D-Task5-DDL: drizzle-kit push is unsafe on MariaDB 11 with mysql dialect — misreads metadata and schedules truncate on populated tables. Additive DDL (calendar_outbox CREATE + object_url ALTER) was hand-applied and verified. Adopt drizzle-kit generate+migrate workflow before next schema change (tracked in todos/pending/adopt-drizzle-migrations-workflow.md)."
patterns-established:
- "Outbox pattern: calendar_outbox table with status enum (pending/done/failed/dead), groupId for edit-as-move pairing, nextAttemptAt for exponential backoff"
- "objectUrl stored from tsdav obj.url on every sync upsert — enables If-Match header on CalDAV update/delete"
- "Wave 0 RED scaffold: all phase test files created before any implementation so GREEN gate is explicit"
requirements-completed: [CAL-04, CAL-05, CAL-06, CAL-07, PWA-01, PWA-02]
# Metrics
duration: ~45min
completed: 2026-06-05
---
# Phase 03 Plan 01: Foundation Scaffold Summary
**calendarOutbox table + calendarEvents.objectUrl pushed live to MariaDB, vite-plugin-pwa installed, and five Wave 0 RED test files covering all Phase 3 write-back and PWA behaviors**
## Performance
- **Duration:** ~45 min
- **Started:** 2026-06-05T21:18Z
- **Completed:** 2026-06-05T22:10Z
- **Tasks:** 5 (Tasks 1-5; Task 1 was human-verify gate, Task 5 was human-action gate)
- **Files modified:** 8
## Accomplishments
- Extended Drizzle schema with `calendarOutbox` table (12 columns, 3 indexes: idx_outbox_user_status, idx_outbox_next_attempt, idx_outbox_uid) and `calendarEvents.objectUrl` column; both live in MariaDB
- Populated `objectUrl: obj.url ?? null` in both `.values()` and `.onDuplicateKeyUpdate()` blocks of the calendarEvents upsert in sync.ts — enables If-Match writes (D-08)
- Installed `vite-plugin-pwa` (legitimacy-gated via Task 1 supply-chain checkpoint T-03-SC)
- Created five Wave 0 RED test files covering every Phase 3 behavior: VEVENT builder, CalDAV write layer, outbox state machine, events API routes, and PWA InstallPrompt
## Task Commits
1. **Task 1: Supply-chain gate T-03-SC** — no commit (verification-only checkpoint)
2. **Task 2: Extend Drizzle schema + install vite-plugin-pwa**`78f0dee` (feat)
3. **Task 3: Populate calendarEvents.objectUrl in sync.ts**`0c0bcef` (feat)
4. **Task 4: Wave 0 RED test scaffold**`bbfccda` (test)
5. **Task 5: Push schema to MariaDB** — hand-applied DDL by orchestrator (no code commit; DB verified)
## Files Created/Modified
- `apps/api/src/db/schema.ts` — added mysqlEnum import, calendarOutbox table definition, groupId column, 3 indexes; added objectUrl column to calendarEvents
- `apps/api/src/broker/sync.ts` — set `objectUrl: obj.url ?? null` in values and onDuplicateKeyUpdate blocks
- `apps/api/tests/broker/vevent.test.ts` — RED: VCALENDAR/VEVENT builder tests (timed, all-day D-13, RRULE)
- `apps/api/tests/broker/write.test.ts` — RED: createCalendarEvent, updateCalendarEvent (If-Match), deleteCalendarEvent
- `apps/api/tests/broker/outboxWorker.test.ts` — RED: outbox state machine (pending→done/failed/backoff/dead), edit-as-move ordering (D-04)
- `apps/api/tests/routes/events.test.ts` — extended with POST create, PATCH edit, DELETE, sync-status, writable-calendars, D-03 access control
- `apps/pwa/src/components/InstallPrompt.test.tsx` — RED: isIOSSafariNonStandalone(), useAndroidInstallPrompt
- `apps/pwa/package.json` — added vite-plugin-pwa dependency
## Decisions Made
- **D-Task5-DDL:** `drizzle-kit push` with the `mysql` dialect against a live MariaDB 11 instance produces a FALSE destructive diff — it misreads MariaDB-11 metadata and schedules `truncate table` on `calendars`, `calendar_events`, and `users` (503 events at risk). The two genuinely additive statements were hand-applied by the orchestrator and verified. A follow-up todo (`.planning/todos/pending/adopt-drizzle-migrations-workflow.md`) tracks migrating to `drizzle-kit generate` + `drizzle-kit migrate` before any future schema change. No `drizzle-kit push` should be run against this instance again.
## Deviations from Plan
### Task 5: drizzle-kit push replaced by hand-applied additive DDL
**Category:** Orchestrator-resolved deviation (not a Rule 14 auto-fix; resolved by human operator per gate instructions)
- **Found during:** Task 5 (blocking human-action gate)
- **Issue:** `drizzle-kit push` with the Drizzle `mysql` dialect against MariaDB 11 misread database metadata and reported a destructive plan including `truncate table` on `calendars`, `calendar_events`, and `users`. This is a known incompatibility — drizzle-kit 0.31.10 has no `mariadb` dialect; the `mysql` dialect misinterprets MariaDB-11 server metadata.
- **Fix:** Orchestrator manually ran only the two additive statements: `CREATE TABLE calendar_outbox (...)` matching schema.ts exactly, and `ALTER TABLE calendar_events ADD COLUMN object_url varchar(1024)`. Data verified intact (calendars=1, calendar_events=503).
- **Files modified:** None (DB DDL only; schema.ts was already correct)
- **Verification:** `SHOW TABLES LIKE 'calendar_outbox'` → 1 row; `SHOW COLUMNS FROM calendar_events LIKE 'object_url'` → 1 row
- **Follow-up:** `.planning/todos/pending/adopt-drizzle-migrations-workflow.md` created to track migrating to generate+migrate workflow
---
**Total deviations:** 1 (Task 5 DDL approach replaced; resolved by operator at the blocking gate)
**Impact on plan:** No scope creep. Schema is correct. Must-haves fully satisfied. Follow-up todo prevents recurrence.
## Issues Encountered
None beyond the Task 5 drizzle-kit deviation documented above.
## User Setup Required
None — no external service configuration required for this plan. The schema push was a one-time operation handled by the orchestrator at the Task 5 gate.
## Next Phase Readiness
- Wave 0 RED scaffold is in place — plans 03-02 through 03-08 can proceed to GREEN implementation
- `calendarOutbox` and `calendarEvents.objectUrl` are live; outbox worker and write routes can reference them immediately
- `vite-plugin-pwa` is installed; PWA manifest configuration (Plan 03-06) can proceed
- **Action before next schema change:** Adopt `drizzle-kit generate` + `drizzle-kit migrate` (see pending todo) — do NOT run `drizzle-kit push` again
---
*Phase: 03-event-write-back-pwa-install*
*Completed: 2026-06-05*