diff --git a/DESIGN.md b/DESIGN.md index 6cca898..3299aef 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -35,7 +35,7 @@ Strength weights used in matrices: **9** strong, **3** medium, **1** weak, blank | F7 | Deliver a push promptly after a defect is filed | ↓ | ≤ ~10s to subscribed devices | | F8 | Enroll & maintain push subscriptions | → | Multiple devices; survives re-subscribe | | F9 | Gate every request behind Google SSO + server-side domain check | → | 100% of routes; `hd` never trusted alone | -| F10 | Persist data durably across redeploys | → | 0 data loss (volume-backed SQLite) | +| F10 | Persist data durably across redeploys | → | 0 data loss (Coolify-managed Postgres, auto-backups) | ## 3. Cascade — Goals → Functions → How → Components @@ -71,7 +71,7 @@ Strength weights used in matrices: **9** strong, **3** medium, **1** weak, blank - **How**: Google OAuth/OIDC; server-side verified-email-domain check (`hd` not trusted alone); default-deny middleware - **Component**: C9 Auth middleware - **F10** Persist durably across redeploys _→ 0 loss_ - - **How**: SQLite file on a Coolify persistent volume (rejected: Postgres container — see T5) + - **How**: Coolify-managed PostgreSQL with automatic backups (rejected: SQLite on a volume — see T5, ADR 0003) - **Component**: C8 schema, C11 deploy ## 4. House — Functions × Goals (transposed for width) @@ -123,7 +123,7 @@ Cells: link strength (9/3/1/blank). Importance = `Σ(goal weight × strength)`. | C5 | DotMap (dots + diagonal dates + "+N more") | — | | C6 | VerbatimModal + reverse-chronological Feed | ADR 0002 | | C7 | Nuxt/Nitro server API (defects, projects, subs) | ADR 0001 | -| C8 | SQLite + Drizzle schema (defects, projects, push_subscriptions) | ADR 0001 | +| C8 | PostgreSQL + Drizzle schema (defects, projects, push_subscriptions) | ADR 0001, ADR 0003 | | C9 | Google OAuth + session + domain-check middleware | — | | C10 | Web Push sender (VAPID) + service worker + sub store | — | | C11 | Docker image + compose + Coolify volume mount | — | @@ -151,7 +151,7 @@ Cells: link strength (9/3/1/blank). Importance = `Σ(goal weight × strength)`. | 4 | F4 dot legibility | Readable to the cap | Seed N defects on one section, eyeball | Lower the visible-dot cap / tighten "+N more" threshold | | 5 | F7 push latency | ≤10s to device | File a defect, time the phone | Accept the delay; show an in-app unread badge as fallback (no retry queue) | | 6 | F6 dashboard render | ≤1s @ ~2k defects | Seed 2k rows, measure render | Add index on (section_id, created_at); precompute section counts | -| 7 | F10 durability | 0 data loss on redeploy | Redeploy on Coolify, verify data present | Confirm volume mount; block launch until verified | +| 7 | F10 durability | 0 data loss on redeploy | Redeploy on Coolify, verify data present | Restore from Coolify's automatic Postgres backup; block launch until verified | ## 8. Tradeoffs — Got / Paid / ADR @@ -161,7 +161,7 @@ Cells: link strength (9/3/1/blank). Importance = `Σ(goal weight × strength)`. | T2 | Append-only log over lifecycle | Trivial model, fast v1 | No countermeasure/close tracking (deferred) | — | | T3 | Transparent attribution over blameless | Accountability + follow-up path | Cultural risk of chilled reporting | ADR 0002 | | T4 | Defect = board artifact over project-work classification | Board-as-product weak-point analysis | Can't capture project-level problems (different concept) | ADR 0001 | -| T5 | SQLite over Postgres | Zero-ops, single container | Must mount a persistent volume; single-writer; manual backups | — | +| T5 | Coolify-managed Postgres over SQLite | Automatic backups; no deploy-strategy constraint; concurrent connections | One more service in dev and prod than a single file | ADR 0003 | | T6 | Web Push over Slack/email | No third-party dependency; native phone push | Subscription management; breaks if browser storage cleared | — | | T7 | Section-level pin over exact-spot | Simpler model | Dot positions cosmetic; precise location lost | — | | T8 | Fire-and-forget push, no queue | Filing stays fast; minimal infra | Push is best-effort, not retried on failure | — | diff --git a/README.md b/README.md index fd2f816..965c6eb 100644 --- a/README.md +++ b/README.md @@ -28,14 +28,19 @@ House of Quality: **F1 board definition → F2/F3 filing → F4/F5 weak-point ma | [docs/house-of-quality.md](./docs/house-of-quality.md) | TikZ "House of Quality" — a visual rendering of the DESIGN.md matrices for review/slides. | | [docs/adr/0001](./docs/adr/0001-defects-are-about-the-board-not-project-work.md) | Why a Defect is feedback about the **Board artifact**, not the reporter's project work. | | [docs/adr/0002](./docs/adr/0002-attribution-is-transparent-not-blameless.md) | Why attribution is **transparent** rather than blameless. | +| [docs/adr/0003](./docs/adr/0003-use-coolify-managed-postgres-over-sqlite.md) | Why the data store is **Coolify-managed Postgres**, not SQLite. | +| [tasks/plan.md](./tasks/plan.md) | Implementation plan — phased, dependency-ordered tasks with acceptance criteria. | ## Stack -Nuxt 3 (full-stack Vue) · SQLite + Drizzle on a persistent volume · Google -OAuth (`hd=theodo.com` + server-side domain recheck) · Web Push (PWA) · +Nuxt 3 (full-stack Vue) · PostgreSQL + Drizzle (Coolify-managed, auto-backups) · +Google OAuth (`hd=theodo.com` + server-side domain recheck) · Web Push (PWA) · Docker Compose on Coolify. ## Deployment notes -- **SQLite needs a persistent Coolify volume** — without it, redeploys wipe all data. +- **Use Coolify's managed Postgres** (with scheduled backups); the app connects + via `DATABASE_URL`. See [ADR 0003](./docs/adr/0003-use-coolify-managed-postgres-over-sqlite.md). +- **Don't auto-deploy from `main`** — every merge would deploy. Use a tagged + release or manual trigger. - **Don't trust the OAuth `hd` claim alone** — verify the email domain server-side. diff --git a/docs/adr/0003-use-coolify-managed-postgres-over-sqlite.md b/docs/adr/0003-use-coolify-managed-postgres-over-sqlite.md new file mode 100644 index 0000000..7bcdb10 --- /dev/null +++ b/docs/adr/0003-use-coolify-managed-postgres-over-sqlite.md @@ -0,0 +1,22 @@ +# Use Coolify-managed PostgreSQL over SQLite + +The `/deep-design` session chose SQLite on a persistent volume for its zero-ops +simplicity at this scale. Deploying on **Coolify** changes the calculus: Coolify +provides **managed PostgreSQL with automatic scheduled backups**, whereas a +SQLite file inside an app volume has *no* managed backup and forces a +single-instance, stop-then-start deploy strategy to avoid file-lock contention +when Coolify briefly runs old and new containers during a rolling restart. We +switch to **Coolify-managed PostgreSQL** (via Drizzle's `pg` driver). + +## Consequences + +- Backups are handled by Coolify's database backup feature — no bespoke tooling + (e.g. Litestream) needed. +- Deploys no longer need a recreate/single-instance constraint; the app can + restart freely while Postgres persists as a separate service. +- Adds a Postgres service to local dev (`docker-compose`) and a managed DB in + prod — one more moving part than a single file. +- **Supersedes** the SQLite choice in DESIGN.md (T5, F10) and the original + `/deep-design` decision. +- Unchanged: deploys must **not** auto-run from `main` (every merge would deploy); + use a tagged release or manual trigger.