docs: sync from backend 1757f1e - postgres cutover stores

This commit is contained in:
Siavash Sameni
2026-06-01 11:51:48 +04:00
parent e8a1bba471
commit 7b5dbb2683
6 changed files with 77 additions and 25 deletions

View File

@@ -4,7 +4,7 @@ tags: [data-model, migration, postgres, drizzle, plan, runbook]
aliases: [Drizzle Migration Plan, PG Migration Plan]
created: 2026-05-31
companion: "[[MongoDB to PostgreSQL Migration Guide]]"
updated: 2026-05-31 for backend integrate-main-into-development@cab0719
updated: 2026-06-01 for backend integrate-main-into-development@1757f1e backend 2.8.9
---
# MongoDB → PostgreSQL Migration Plan (Drizzle)
@@ -17,7 +17,7 @@ updated: 2026-05-31 for backend integrate-main-into-development@cab0719
> **Scope reminder:** partial migration (Phases 05) is the recommended stopping point — ≈1628 engineer-weeks. Full migration of Chat/Notification/sessions is explicitly deferred.
> [!warning] Current implementation status
> Backend `2.6.80` has completed the first implementation slice of this plan: Postgres/Drizzle infra, schemas/migrations through `0008`, `id_map`, `pg_dualwrite_gaps`, Drizzle/Mongo/Dual repo implementations, backfill/verify tooling, conditional oracle `payment_quotes` persistence, and the `PurchaseRequest`/`RequestTemplate` budget enum alignment with PG `budget_currency`. It has **not** completed service-layer wiring or runtime cutover. Mongo remains authoritative for normal traffic. See [[Postgres Runtime Cutover Status]].
> Backend `2.8.9` has started the runtime cutover with store-specific raw Postgres facades: auth-owned users/Telegram auth records behind `AUTH_STORE=postgres`, confirmation-threshold config/history behind `CONFIG_STORE=postgres`, user address CRUD behind `ADDRESS_STORE=postgres`, and the first marketplace/reference domains behind `CATEGORY_STORE=postgres`, `LEVEL_CONFIG_STORE=postgres`, `SHOP_SETTINGS_STORE=postgres`, and `REVIEW_STORE=postgres`. This checkout does **not** currently contain the old `src/db/` Drizzle repository layer described below, so treat the Drizzle sections as the target architecture/runbook, not a statement of current code. Mongo remains authoritative unless a per-store flag is explicitly flipped. See [[Postgres Runtime Cutover Status]].
---
@@ -342,16 +342,19 @@ Same phases as the guide §2, here with Drizzle-concrete entry/exit gates. Each
### Phase 1 — Address pilot (12 wk)
- Smallest real domain; proves backfill → dual-write → verify → cutover end-to-end.
- **Status 2026-06-01:** `/api/addresses` has an opt-in PG runtime path through `ADDRESS_STORE=postgres`; PG writes/deletes mirror to Mongo for rollback.
- Reimplement the **one-primary-per-user** pre-save invariant as either a partial unique index `UNIQUE (user_id) WHERE primary = true` or a trigger.
- **Exit:** `addresses` in `pg` mode in prod, invariant proven under concurrent writes, verify green, dual-write removed.
### Phase 2 — Reference/config (23 wk)
- `Category` (self-FK, soft-delete), `LevelConfig`, `ConfigSetting`, `ConfigSettingHistory`, `ShopSettings`, `Review`.
- **Status 2026-06-01:** confirmation-threshold `ConfigSetting` / `ConfigSettingHistory`, categories, level config, shop settings, and reviews have opt-in PG runtime paths through their per-store flags; writes mirror back to Mongo where still-Mongo consumers need compatibility.
- Port seeds to run in dependency order. Enforce `ShopSettings.sellerId` unique, Category `parentId` ON DELETE SET NULL.
- **Exit:** these read from PG; seeds run in PG.
### Phase 3 — User + auth core (35 wk)
- `User` is the FK hub — **must precede the money core** so `id_map` for users is authoritative.
- **Status 2026-06-01:** auth-owned user data is opt-in PG-backed through `AUTH_STORE=postgres`, with a Mongo legacy mirror for still-Mongo consumers. Broader user consumers are not fully cut over.
- Normalize `profile`/`preferences`/`points`/`referralStats` into columns; extract `passkeys[]`, `refreshTokens[]` to child tables; partial-unique `email`/`referralCode`; reimplement `toJSON()` stripping; passkey `default: Date.now()` in app code.
- Redis session/rate-limit + in-memory passkey challenge store stay as-is.
- **Exit:** `users` in `pg` mode; referral self-FK intact; all auth flows pass; user uuids authoritative in `id_map`.

View File

@@ -3,48 +3,63 @@ title: Postgres Runtime Cutover Status
tags: [data-model, postgres, migration, runtime-status]
aliases: [Postgres Status, PG Cutover Status, Mongo vs Postgres Runtime]
created: 2026-05-31
source: backend origin/integrate-main-into-development@cab0719
source: backend integrate-main-into-development@1757f1e
---
# Postgres Runtime Cutover Status
> **Current branch:** backend `origin/integrate-main-into-development` at `cab0719`, version `2.6.80`.
> **Current branch:** backend `integrate-main-into-development` at `1757f1e`, version `2.8.9`.
>
> **Bottom line:** this branch is **Postgres-capable**, not fully Postgres-backed. MongoDB remains the live source of truth for normal app traffic until services are actually rewired through the repository factory and cutover flags are verified in a running environment.
> **Bottom line:** this branch is **Postgres-capable**, not fully Postgres-backed. Auth-owned user data can run through Postgres when `AUTH_STORE=postgres`; confirmation-threshold runtime config/history can run through Postgres when `CONFIG_STORE=postgres`; user address CRUD can run through Postgres when `ADDRESS_STORE=postgres`; marketplace categories, level config, shop settings, and reviews can run through Postgres with their own store flags. All PG-backed stores require `PG_URL`. Mongo remains the default and the compatibility store for still-Mongo domains.
## What Uses Postgres Now
| Area | Runtime status | Notes |
|---|---|---|
| Postgres connection | Available when `PG_URL` is set | `src/db/client.ts` fail-fast requires an explicit DSN before importing PG code. |
| Schema/migrations | Implemented through migration `0008` | Drizzle schemas cover users, categories, purchase requests, seller offers, payments, funds ledger, derived destinations, trezor accounts, points, `id_map`, `pg_dualwrite_gaps`, and `payment_quotes`. |
| Repository implementations | Implemented but not broadly called | `src/db/repositories/{mongo,drizzle,dual}` and `factory.ts` exist for user, payment, points, and marketplace domains. |
| Postgres connection | Available when `PG_URL` is set | Current runtime code uses `src/infrastructure/postgres/client.ts`; there is no `src/db/` Drizzle layer in this checkout. |
| Runtime schema bootstrap | Implemented for auth, config, address, and reference stores | Auth tables are bootstrapped from `src/services/auth/postgresAuthSchema.ts`; store facades bootstrap their own tables at startup when their `*_STORE=postgres` flag is enabled. |
| Auth-owned user store | Opt-in with `AUTH_STORE=postgres` | Auth, passkey, Telegram auth/link/session/temp-verification, and `/api/user` profile paths use an auth-store facade. In PG mode, users are stored in Postgres and mirrored back to Mongo through `legacy_object_id` for compatibility with still-Mongo services. |
| Confirmation-threshold runtime config | Opt-in with `CONFIG_STORE=postgres` | `ConfigSetting` / `ConfigSettingHistory` access for `/api/admin/settings/confirmation-thresholds` and transaction-safety confirmation thresholds uses a config-store facade. PG-mode writes mirror back to Mongo for rollback. |
| User addresses | Opt-in with `ADDRESS_STORE=postgres` | `/api/addresses` CRUD uses an address-store facade. PG mode enforces one primary address per user with a partial unique index and mirrors writes/deletes back to Mongo for rollback. |
| Marketplace categories | Opt-in with `CATEGORY_STORE=postgres` | `CategoryService` and the default `General` category path use a category-store facade. PG-mode writes mirror back to Mongo for rollback and still-Mongo request/template references. |
| Level configuration | Opt-in with `LEVEL_CONFIG_STORE=postgres` | `PointsService` level reads use a level-config facade. `PointTransaction` and user points remain Mongo-backed. |
| Shop settings | Opt-in with `SHOP_SETTINGS_STORE=postgres` | Shop settings controller, seller payment rail resolution, and review enable/disable checks use a shop-settings facade. PG-mode writes mirror back to Mongo. |
| Marketplace reviews | Opt-in with `REVIEW_STORE=postgres` | Review list/summary/create routes use a review-store facade. PG-mode list responses still hydrate `reviewerId` from the user mirror to preserve frontend shape. |
| Repository implementations | Not present in current checkout | The old Drizzle plan still describes the target architecture, but this branch's actual cutover work is currently store-specific raw PG facades. |
| Oracle quote persistence | Conditional runtime PG write | `/api/payment/request-network/intents` lazily imports `quoteRepo` only when `ORACLE_QUOTING_ENABLED=true`; it writes `payment_quotes` if the PG parent payment row exists, mirrors to Mongo `Payment.quote`, and records `pg_dualwrite_gaps` if PG is behind. |
| Backfill/verify scripts | Available as operator tooling | `MIGRATION_PG_URL` drives backfill scripts; guards restrict allowed target hosts. These scripts are not run automatically by app startup. |
## What Is Still Mongo-Backed
The service layer still imports Mongoose models directly. A source scan on 2026-05-31 found no runtime calls to `createRepositories()` / `getPaymentRepo()` / `getMarketplaceRepo()` outside `src/db/repositories/factory.ts`; the only normal service import of `src/db/client.ts` is `priceOracle/quoteRepo.ts`.
Most of the service layer still imports Mongoose models directly. Auth-owned paths now have an auth-store boundary; confirmation-threshold config, user addresses, categories, level config, shop settings, and reviews have store boundaries. Broad marketplace requests/offers/templates, payment, funds ledger, points transactions, chat, notification, and admin paths remain Mongo-first.
| Domain | Current live store | Why not Postgres yet |
|---|---|---|
| Auth, users, passkeys, refresh tokens | MongoDB | Auth/user controllers call `User` and related Mongoose models directly. |
| Marketplace requests/offers/templates/categories/shop settings | MongoDB | Marketplace, checkout, and seller-offer services call `PurchaseRequest`, `SellerOffer`, `RequestTemplate`, `Category`, and `ShopSettings` directly. |
| Legacy/broad user consumers | MongoDB mirror | Auth-owned users can be PG-backed, but still-Mongo domains expect Mongo ObjectId user references. PG-mode writes therefore maintain a Mongo mirror until those domains are cut over. |
| Admin cleanup / seed address tooling | MongoDB | User-facing address CRUD is PG-capable, but admin cleanup and seed scripts still operate on Mongo first. Seed scripts backfill addresses to PG when `ADDRESS_STORE=postgres`. |
| Marketplace requests/offers/templates | MongoDB | Marketplace, checkout, and seller-offer services still call `PurchaseRequest`, `SellerOffer`, and `RequestTemplate` directly. Category and shop-settings reads/writes are PG-capable through facades, but request/template documents still hold Mongo ObjectId references. |
| Payments and escrow state | MongoDB primary | Request Network, AMN scanner, webhook, admin, release/refund, adapter, reconciliation, and legacy payment paths still create/update `Payment` Mongoose documents directly. |
| Funds ledger | MongoDB primary | `FundsLedgerEntry` remains the ledger used by current services; PG ledger tables exist but are not the live write target. |
| Derived destinations and sweeps | MongoDB | Wallet destination allocation and sweep services call `DerivedDestination` directly. |
| Points/referrals/levels | MongoDB | Points service uses `User`, `PointTransaction`, `LevelConfig`, and Mongo transactions. |
| Points/referrals/transactions | MongoDB | Level configuration is PG-capable, but `User` points fields, `PointTransaction`, referral aggregation, and Mongo transactions remain Mongo-backed. |
| Chat/messages | MongoDB | Chat intentionally remains document-shaped and is not part of the current PG cutover. |
| Notifications | MongoDB | Notification TTL/read-state paths remain Mongo-backed. |
| Disputes/reviews/blog/content/admin cleanup | MongoDB | These services still call their Mongoose models directly. |
| Runtime config | MongoDB | `ConfigSetting` and `ConfigSettingHistory` remain Mongo-backed for admin-editable configuration. |
| Telegram link/session/temp verification | MongoDB | These session/link/TTL records are not cut over to PG. |
| Disputes/blog/content/admin cleanup | MongoDB | Reviews are PG-capable; disputes, blog/content, and admin cleanup still call their Mongoose models directly. |
| Runtime config outside confirmation thresholds | MongoDB | `ConfigSetting` and `ConfigSettingHistory` are PG-capable for confirmation thresholds only; any future admin-editable settings need to route through the same config-store boundary before they count as cut over. |
| Telegram link/session/temp verification | Default MongoDB, PG-capable with auth store | These records move with `AUTH_STORE=postgres`; default runtime remains Mongo until the environment flag is flipped. |
## Env Flag Reality
| Flag | Current meaning |
|---|---|
| `PG_URL` | Makes PG code importable/reachable; does not cut over app domains by itself. |
| `AUTH_STORE` | `mongo` by default. Set `AUTH_STORE=postgres` to route auth-owned users, refresh tokens, passkeys, Telegram links/sessions, and temp verifications through Postgres. |
| `CONFIG_STORE` | `mongo` by default. Set `CONFIG_STORE=postgres` to route confirmation-threshold settings/history through Postgres. |
| `ADDRESS_STORE` | `mongo` by default. Set `ADDRESS_STORE=postgres` to route `/api/addresses` through Postgres. |
| `CATEGORY_STORE` | `mongo` by default. Set `CATEGORY_STORE=postgres` to route marketplace category reads/writes through Postgres. |
| `LEVEL_CONFIG_STORE` | `mongo` by default. Set `LEVEL_CONFIG_STORE=postgres` to route level configuration reads and seed replacement through Postgres. `LEVEL_STORE=postgres` is accepted as a compatibility alias. |
| `SHOP_SETTINGS_STORE` | `mongo` by default. Set `SHOP_SETTINGS_STORE=postgres` to route shop settings, review gates, and seller payment rails through Postgres. |
| `REVIEW_STORE` | `mongo` by default. Set `REVIEW_STORE=postgres` to route marketplace reviews through Postgres. |
| `PG_URL` | Makes PG code importable/reachable. Required for any `*_STORE=postgres` flag; does not cut over unrelated app domains by itself. |
| `MIGRATION_PG_URL` | Used by backfill scripts and migration runbooks; not part of normal request handling. |
| `REPO_USER`, `REPO_PAYMENT`, `REPO_POINTS`, `REPO_MARKETPLACE`, `REPO_DEFAULT` | Repository factory flags exist, but broad services are not yet wired through the factory. Treat them as migration controls that need integration verification before relying on them. |
| `ORACLE_QUOTING_ENABLED` | Enables server-side quote computation and the only current PG write path in normal checkout: `payment_quotes`, when a PG parent row can be resolved. |
@@ -52,11 +67,20 @@ The service layer still imports Mongoose models directly. A source scan on 2026-
## Next Cutover Work
1. Apply Drizzle migrations to the target Postgres database.
2. Run non-prod backfills in dependency order and record row-count/checksum results.
3. Wire services to repository interfaces one domain at a time.
4. Enable `dual` mode per domain only after wiring is proven by tests and smoke checks.
5. Run shadow-read/reconcile during a soak window.
6. Flip reads to `pg` per domain only after zero-diff shadow reads and a rollback plan are in place.
2. For auth cutover, run `PG_URL=... npm run backfill:auth:postgres`, verify counts, then set `AUTH_STORE=postgres`.
3. For confirmation-threshold config cutover, run `PG_URL=... npm run backfill:config:postgres`, verify counts/history, then set `CONFIG_STORE=postgres`.
4. For address cutover, run `PG_URL=... npm run backfill:address:postgres`, verify one-primary invariants, then set `ADDRESS_STORE=postgres`.
5. For reference-domain cutover, run:
- `PG_URL=... npm run backfill:category:postgres`
- `PG_URL=... npm run backfill:level-config:postgres`
- `PG_URL=... npm run backfill:shop-settings:postgres`
- `PG_URL=... npm run backfill:review:postgres`
6. Run `PG_URL=... MONGODB_URI=... scripts/smoke/reference-stores-postgres.sh`, then set `CATEGORY_STORE=postgres LEVEL_CONFIG_STORE=postgres SHOP_SETTINGS_STORE=postgres REVIEW_STORE=postgres` together in non-prod.
7. Run non-prod backfills for the remaining larger domains in dependency order and record row-count/checksum results.
8. Wire remaining services to repository interfaces one domain at a time.
9. Enable `dual` mode per large domain only after wiring is proven by tests and smoke checks.
10. Run shadow-read/reconcile during a soak window.
11. Flip reads to `pg` per domain only after zero-diff shadow reads and a rollback plan are in place.
## Related Docs

View File

@@ -36,7 +36,7 @@ The base port is set via `PORT` env var; in `development` it defaults to `5001`.
Health checks:
- `GET /health` (not under `/api`) → `{ success, message, timestamp, environment, version }` — used by Docker and Gatus.
- `GET /api/health` (added in commit `44579d6`, backend v2.6.49) → deeper JSON with database and Redis connectivity status, plus the version string. Used by Gatus monitoring.
- `GET /api/health` (added in commit `44579d6`, backend v2.6.49) → deeper JSON with MongoDB, Postgres, Redis, Request Network registry/API connectivity status, plus the version string. Used by Gatus monitoring. Postgres is marked `required` when any `*_STORE=postgres` flag is enabled.
API discovery endpoint: `GET /api` → returns a map of available service prefixes.

View File

@@ -135,8 +135,11 @@ endpoints:
interval: 30s
conditions:
- "[STATUS] == 200"
- "[BODY].status == ok"
- "[BODY].status == \"ok\""
- "[BODY].checks.db.ok == true"
- "[BODY].checks.postgres.ok == true"
- "[BODY].checks.postgres.configured == true"
- "[BODY].checks.postgres.required == true"
- "[BODY].checks.redis.ok == true"
- "[BODY].checks.rnChainRegistry.ok == true"
- "[BODY].checks.rnChainRegistry.chainCount >= 1"
@@ -163,8 +166,9 @@ endpoints:
interval: 30s
conditions:
- "[STATUS] == 200"
- "[BODY].status == ok"
- "[BODY].status == \"ok\""
- "[BODY].checks.db.ok == true"
- "[BODY].checks.postgres.ok == true"
- "[BODY].checks.redis.ok == true"
- "[BODY].checks.rnChainRegistry.chainCount >= 1"
- "[BODY].checks.rnTokenRegistry.tokenCount >= 1"

View File

@@ -14,7 +14,7 @@ What's instrumented today and what to watch. Today's stack is intentionally lean
Two paths are registered (both are public, rate-limited, not auth-gated):
- `GET /health` — simple ping used by Docker healthchecks. Returns `200 { success, message, timestamp, environment, version }`. Does **not** probe MongoDB or Redis.
- `GET /api/health` — deep health check added in commit `44579d6` (backend v2.6.49). Calls `runHealthChecks` from `backend/src/services/health/healthCheckService.ts`. Probes MongoDB, Redis, Request Network registry data, and Request Network API reachability. Returns `503` only when `report.status === 'down'`. As of backend `2.6.84`, the RN API subcheck is cached for 60 seconds and treats non-5xx HTTP responses, including `429`, as upstream reachable so Gatus/perf probes do not turn a live backend into `degraded`.
- `GET /api/health` — deep health check added in commit `44579d6` (backend v2.6.49). Calls `runHealthChecks` from `backend/src/services/health/healthCheckService.ts`. Probes MongoDB, Postgres, Redis, Request Network registry data, and Request Network API reachability. Returns `503` only when `report.status === 'down'`. As of backend `2.8.9`, Postgres is a hard dependency only when at least one `*_STORE=postgres` flag is enabled; otherwise an unconfigured Postgres check is reported as skipped.
`GET /api/health` response shape (from `healthCheckService`):
```json
@@ -24,6 +24,14 @@ Two paths are registered (both are public, rate-limited, not auth-gated):
"timestamp": "...",
"checks": {
"db": { "ok": true, "latencyMs": 4 },
"postgres": {
"ok": true,
"latencyMs": 5,
"configured": true,
"required": true,
"database": "amanat_dev",
"user": "amanat"
},
"redis": { "ok": true, "latencyMs": 1 },
"rnChainRegistry": { "ok": true, "latencyMs": 0, "chainCount": 7 },
"rnTokenRegistry": { "ok": true, "latencyMs": 0, "tokenCount": 12 },

View File

@@ -11,6 +11,19 @@ entries on top. Maintained by agents per the rule in `../AGENTS.md`.
---
### 2026-06-01 — backend@1757f1e, frontend@600dd0d, deployment@6db02b0 — Postgres runtime cutover stores and health monitoring
**Commits:** backend `1757f1e`, frontend `600dd0d`, deployment `6db02b0` (backend `2.8.9`, frontend `2.8.9`)
**Touched:**
- Backend: `src/infrastructure/postgres/client.ts`, `src/infrastructure/database/connection.ts`, auth/config/address/category/level/shop/review store facades, backfill scripts, smoke scripts, `/api/health`, `package.json`, `package-lock.json`
- Frontend: `package.json`, `Dockerfile` version metadata only.
- Deployment: `docker-compose.yml` Postgres service/store env wiring, `gatus/config.yaml` Postgres health assertions.
**Why:** Continue the MongoDB-to-Postgres runtime cutover by moving auth-owned users and Telegram auth records, confirmation thresholds, user addresses, and the first reference/marketplace domains behind opt-in Postgres store flags while keeping Mongo as the default and rollback mirror. Monitoring now treats Postgres as a required health dependency when any PG-backed store is enabled.
**Verification:** Backend `npm run typecheck -- --pretty false`; backend `npm run build`; backend `PG_URL=postgres://escrow:throwaway@127.0.0.1:5434/escrow_migration_test MONGODB_URI=mongodb://127.0.0.1:27018/reference-smoke DB_NAME=reference-smoke scripts/smoke/reference-stores-postgres.sh`; backend focused health smoke asserted `checks.postgres.ok/configured/required`; local backend `BASE_URL=http://127.0.0.1:5011 scripts/smoke/auth-basic.sh`; local backend `BASE_URL=http://127.0.0.1:5011 scripts/smoke/confirmation-thresholds.sh`; local backend `BASE_URL=http://127.0.0.1:5011 JWT_SECRET=test-secret scripts/smoke/addresses-basic.sh`; frontend `npx tsc --noEmit --ignoreDeprecations 6.0`; deployment `ruby -e 'require "yaml"; YAML.load_file("gatus/config.yaml")'`.
**Linked docs updated:** [[Postgres Runtime Cutover Status]], [[MongoDB to PostgreSQL Migration Plan (Drizzle)]], [[API Overview]], [[Monitoring]], [[Gatus Monitoring - Proposed Config]]
---
### 2026-05-31 — backend@8e03360, frontend@228eed2 — keep auth and health checks resilient under load
**Commits:** backend `8e03360`, frontend `228eed2` (backend `2.6.84`, frontend `2.7.24`)