Skip to content

feat(everything-dev): PGlite is the only local database — retire docker-compose (ADR 0038) - #676

Open
elliotBraem wants to merge 4 commits into
mainfrom
pglite-only-local-db
Open

elliotBraem wants to merge 4 commits into
mainfrom
pglite-only-local-db

Conversation

@elliotBraem

Copy link
Copy Markdown
Contributor

Problem

Every scaffolded app ships a docker-compose.yml with hardcoded host ports 5432–5435, so concurrent local projects collide (Bind for 0.0.0.0:5433 failed: port is already allocated — observed between two local apps). The zero-config default was ephemeral pglite (pglite:.bos/<slug>/:memory: — data lost every restart), which is why operators opted into compose Postgres just to persist data.

Decision (ADR 0038, grill-confirmed)

Delete compose entirely; make local persistence a file-backed PGlite data dir.

  • Persistent zero-config default: pglite:.bos/db per workspace process (api/.bos/db, plugins/<id>/.bos/db …). No ports, no docker — cross-project collision is structurally impossible. Data survives restarts.
  • Two-database contract unchanged: auth keeps its own dir; api + in-process plugins share one dir with plugin_<slug> schema isolation (the same-URL fallback now yields shared data instead of disjoint in-memory islands). Multi-instance access per data dir verified for pglite 0.4.6 — same-process and cross-process (so db:* while dev runs works).
  • Legacy-state migration: bos dev once rewrites compose-era postgres://everythingdev:…@localhost:{5432–5435}/… URLs in .env (dev → pglite:.bos/db, test → pglite::memory:; drift ports matched by database name) and relocates a scaffold-shaped docker-compose.yml to .bos/compose-retired/. Operator-authored URLs/compose files untouched.
  • db:* on pglite: drizzleCredentialsFor(url) selects the drizzle-kit pglite dialect by URL scheme (all six workspace drizzle.config.ts); pnpm run init (= db:migrate) works zero-config; resolveDatabaseUrl falls back to the persistent default. Driver mkdirs the data dir recursively (PGlite only creates the leaf — boot-time ENOENT bug fixed).
  • Tests: .env.test carries only the test secret + in-memory URLs; regression_env's dev-data guard now refuses pglite:.bos/db and legacy dev postgres URLs; the four CI regression jobs run zero-config pglite (service containers removed); the deploy smoke boots the image on in-container pglite (host Postgres + host.docker.internal dependency gone).
  • Parity escape hatch kept: TEST_DATABASE=postgres + an explicit external URL (pglite/pg savepoint semantics diverge — plan 008). No committed compose anywhere.

Production unchanged: postgres:// URLs, Neon/Railway (ADR 0035).

Verification

  • pnpm run typecheck (14 packages) and pnpm run lint green; oxlint warnings at HEAD baseline (36).
  • Framework unit suite: 1377 pass; the 15 remaining failures pre-exist at HEAD (config-resolution/version-manifest, unrelated).
  • Regression unit suite 7/7 (new dev-data guard semantics).
  • New CI-covered persistence unit test: driver write → close → reopen → read-back on one data dir, relative-cwd mkdir recursion, namespace isolation.
  • Live boot drill during development: full stack reached APP READY with zero docker; api/.bos/db, plugins/auth/.bos/db, per-plugin dirs created; plugin dists confirmed carrying the new defaults.

Notes for children

Children receive this via bos upgrade (changeset: minor on everything-dev). Stale compose-era .env URLs self-migrate on the next bos dev in each repo; old docker-compose.yml files are moved aside, not deleted. TEST_DATABASE=postgres remains available against any external postgres.

Tickets + outcomes: .scratch/pglite-only-local-db/ (local issue-tracker convention, gitignored).

…er-compose (ADR 0038)

The zero-config dev default is now a persistent, file-backed PGlite data dir
(pglite:.bos/db, per workspace process). docker-compose is retired entirely:
root + scaffold template + CLI auto-start + child scripts + CI service
containers + deploy-smoke host Postgres. bos dev migrates leftover compose-era
state once (.env URL rewrites, scaffold compose relocated to
.bos/compose-retired). db:* commands select the drizzle-kit pglite dialect by
URL scheme; the driver mkdirs the data dir recursively (pglite only creates
the leaf). Tests run on in-memory pglite; TEST_DATABASE=postgres + an external
URL remains the parity escape hatch. Production postgres:// URLs unchanged.
…eam + stale-guard cleanup (ADR 0038)

With in-memory pglite the stack database lives in its own process, so the
direct-DB regression fixtures could never reach it and several tests still
assumed the compose era. This lands the follow-ups that make CI green:

- HTTP seeding (tests/regression/lib/http-seed.mjs): sessions minted via
  sign-in/anonymous, orgs created via Better Auth routes, approvals and
  role grants through the admin routes with a boot-time bootstrap admin
  API key; cookie snaps keep the same .bos/regression/*.json artifacts
- bootstrap-admin seam (plugins/auth): when AUTH_BOOTSTRAP_ADMIN_API_KEY
  is set, the auth plugin ensures a platform admin + real API key row
  (hashed like @better-auth/api-key's default hasher, key must be ≥64
  chars) at boot; dev/test-only, loud WARN, declared in bos.app.ts +
  plugin.dev.ts so stack env reaches the plugin
- AdminUsersService.setRole: API-key callers update the DB directly —
  better-auth admin endpoints re-read the server session store and
  null the api-key hook's hydration, so they can never authorize an
  API-key caller; the typed requireAdmin guard already checked the role
- start-container.mjs: skip non-postgres URLs in the DB probe (mirrors
  start-stack.mjs), fixing "test database never became reachable: " in
  the start-mode stacks
- init.compose.test.ts rewritten as a negative guard (init never emits
  docker-compose.yml); compose assertions stripped from
  init.relative-directory.test.ts and the .env.example wording updated
- stale docs: CONTRIBUTING.md docker step + bun→pnpm commands,
  host/.env.example compose-era postgres URL, docs/agents/testing.md
  fixture guidance, framework env.go comment, ADR 0038 addendum
- dead code: docker COMMAND_TIMEOUTS entry, "generate env/docker" label

Verified locally: typecheck (12), lint (0 errors), framework tests
(1695), Go HTTP regression dev suite (full pass incl.
TestSeedRegressionData), regression lib node tests, and a live dev:ssr
stack seeded end-to-end through the new HTTP path.
…v boots; HTTP-ify onboard-rescan spec

- onboard-rescan.spec.ts is now pure HTTP (ADR 0038): organizer + joiner
  sessions via anonymous sign-in helpers from http-seed.mjs, org approval
  through reviewOrganization (bootstrap key), and the "member removed but
  stale active-org" state produced by the organizer removing the joiner
  (removeMember leaves the removed user's session intact). Fixes the
  start:ssr shard crash from the deleted auth-test-instance.ts import.
- concurrent bos dev sessions in one config dir no longer fail artifact
  generation: writeResolvedConfig now writes .bos/bos.resolved-config.json
  atomically (tmp + rename) so a sibling session can't read a torn file
  (a torn read resolved plugins without localPath and made the stale sweep
  delete another boot's fresh gen files), and the sweep's rmSync gains
  force:true so a file vanishing between existsSync and the removal
  cannot crash the boot with "ENOENT … lstat".
- codegen under concurrency pinned by a unit test running two real
  syncApiContractBridge syncs plus a resolved-config write in parallel
  against one temp config dir, plus the stale-sweep removal guard.

Verified: onboard-rescan spec green twice against a live dev:ssr stack,
framework Go suite (BOS_TEARDOWN_ADOPTION=1, 2 stack slots) green,
framework vitest suite green (1697), typecheck + lint clean.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant