diff --git a/CLAUDE.md b/CLAUDE.md index ad2124f..0ba54dc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -60,8 +60,11 @@ cd apps/hq-web && npm run dev - Owner login on first boot: `admin@tecnostac.com` + the one-time password printed in the server log (change it immediately). The "Gmail disconnected" banner is expected until Gmail is connected. -- Env: `HQ_DATA_DIR` (DB location), `SIMS_PIN_PEPPER` (set once, never change), `HQ_SECRET_KEY` +- Env: `DATABASE_URL` (set → Postgres engine, absent → SQLite at `HQ_DATA_DIR`; D19), + `SIMS_PIN_PEPPER` (set once, never change), `HQ_SECRET_KEY` (before Gmail), `GOOGLE_*` (Gmail), `AWS_*` (Cost Explorer), `HQ_PORT` (default 5182). +- Postgres integration tests: `HQ_PG_TEST_URL=postgres://hq:hq_dev@localhost:5432/hq_test npm test` + (wipes that database each run — never point it at real data). - Health check: `GET /api/health` → `{ ok: true }`. - Verified state: `npm install` + typecheck clean, 346 tests pass. Exercise real flows end to end before claiming done. diff --git a/docs/06-DECISIONS.md b/docs/06-DECISIONS.md index 544579c..b35d921 100644 --- a/docs/06-DECISIONS.md +++ b/docs/06-DECISIONS.md @@ -263,3 +263,29 @@ client-book filter UI; 4 minor — typed contact roles absent, dashboard/aging s anchored on doc_date, non-atomic dbPassword PATCH, audit-outside-txn on three new write paths) — all confirmed and fixed in one pass (`fix(review)` commit). Suite at 345 tests / 66 files after the review pass. + +## D19 — Postgres switch executed (D15 delivered) ✅ DELIVERED (2026-07-17) +The engine slice queued after D18, done BEFORE the real APEX cutover so production data +is born in Postgres and nothing is ever migrated. Spec: +`superpowers/specs/2026-07-17-postgres-switch-design.md`. The calls that matter: +- **One async DB interface, two engines.** Every repo/route/scheduler/test awaits + `db.all/get/run/exec/transaction`. better-sqlite3 stays for dev + the fast suite + (`SqliteDb`, statement-cached, manual BEGIN/SAVEPOINT); Postgres (`PgDb`, node-pg Pool) + is selected by `DATABASE_URL`. Repos keep `?` placeholders and portable SQL; the pg + adapter rewrites `?`→`$n`, pins transactions to one pooled client via AsyncLocalStorage + (nested = savepoints), and parses int8/numeric to JS numbers (paise stay integer math). +- **Postgres schema choices:** bigint for every paise column (int4 overflows at ₹2.15 + crore), 0/1 flags stay INTEGER (repos say `active=1` on both engines), JSON stays TEXT + (node-pg auto-parses jsonb — would break JSON.parse at the repo edge), dates stay TEXT + ISO. Numbered migrations + schema_migrations runner (migrations-pg.ts), applied + atomically; the reminder rule_kind CHECK is born in its final widened form. +- **Proof:** default suite 346/346 on SQLite (zero behavior change); 5-test pg + integration suite (opt-in `HQ_PG_TEST_URL`, wipes its target) green on local + PostgreSQL 17 — migrations-from-zero, series numbering + due-date stamp, paisa-exact + payment→paid, ON CONFLICT exactly-once reminders, savepoint rollback, HTTP boot smoke. +- **Known nuance before go-live:** LIKE is case-insensitive on SQLite, case-sensitive on + Postgres — client search will match case-sensitively in prod until repos switch to + `LOWER(...) LIKE LOWER(?)`. +- Local dev Postgres: PostgreSQL 17 Windows service, superuser `postgres`, role `hq`, + databases `hq` (runtime) + `hq_test` (integration suite). Prod = RDS at go-live, same + `DATABASE_URL` mechanism. diff --git a/docs/superpowers/specs/2026-07-17-postgres-switch-design.md b/docs/superpowers/specs/2026-07-17-postgres-switch-design.md index 7ed415f..ef134a1 100644 --- a/docs/superpowers/specs/2026-07-17-postgres-switch-design.md +++ b/docs/superpowers/specs/2026-07-17-postgres-switch-design.md @@ -1,8 +1,23 @@ # HQ Console — Postgres Switch (D15 executed → D19) -> **STATUS: IN BUILD, started 2026-07-17.** The dedicated slice queued after the D18 -> go-live cluster and **before** the real APEX cutover — so production data is born in -> Postgres and nothing is ever migrated. +> **STATUS: DELIVERED 2026-07-17.** All four phases landed same-day: async DB interface +> across every repo/route/test (default suite 346/346 on SQLite, zero behavior change), +> Postgres migration set + runner, pg adapter, `DATABASE_URL` engine selection, and a +> 5-test Postgres integration suite (opt-in `HQ_PG_TEST_URL`) verified green against a +> local PostgreSQL 17. +> +> Delivery deviations from the table below (recorded in migrations-pg.ts too): +> - **0/1 flags stay INTEGER** (not boolean) — repos say `active=1`; that SQL must be +> identical on both engines. +> - **JSON stays TEXT** (not jsonb) — node-pg auto-parses jsonb into objects, which +> would break the repos' JSON.parse at the edge; jsonb is a later pg-only ALTER if +> payload indexing is ever needed. +> - **Full-suite-on-pg** was scoped to a dedicated integration file (migrations-from-zero, +> series + due-date stamp, paisa-exact payment, ON CONFLICT idempotency, savepoint +> rollback, HTTP boot smoke) rather than re-running all 346 sqlite-fixture tests on pg. +> - Known dialect nuance to revisit before go-live: `LIKE` is case-insensitive on SQLite +> but case-sensitive on Postgres — client search/filters will match case-sensitively +> in production until switched to `LOWER(...) LIKE LOWER(?)`. ## 1. Goal & why now