Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/deploy-pr-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ jobs:
#
# To restore per-PR databases: flip that resolver back to "managed" for
# `pr` and re-add the steps this comment replaced (PlanetScale CLI setup,
# `planetscale-pr-branch.ts up`, `db:ensure-privileges`, `db:migrate`,
# `planetscale-pr-branch.ts up`, `db:migrate`,
# `db:normalize-preview`, `electric-pr-branch.ts up`, and the matching
# `planetscale-pr-branch.ts down` teardown). Every script is still in the
# repo, dormant.
Expand Down
5 changes: 3 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,9 @@ database), reached from Workers via the Hyperdrive binding `MAPLE_DB`.
- Migrations: `bun run --cwd packages/db db:generate`. **The prd deploy applies them**: the
PlanetScale `main` branch is an alchemy `Planetscale.PostgresBranch` in `alchemy.run.ts` with
`migrations` pointed at `packages/db/drizzle`; never run `drizzle-kit migrate` against prd. It
migrates as a temporary role, so a migration creating a table must `GRANT` it `TO PUBLIC` itself
(the ingest gateway reads only through PUBLIC). PGlite applies them at layer build.
migrates as a temporary role dropped with `postgres` as successor, so every runtime role must
inherit `postgres` (`pg_has_role(rolname, 'postgres', 'usage')`) to read what it creates. PGlite
applies them at layer build.
- **PR preview deploys are label-gated** (2026-08, cost — re-enabled by `fd00bcd412`). A PR gets a
preview only while it carries the `preview` label; `deploy-pr-preview.yml` triggers on
`opened, reopened, synchronize, labeled, unlabeled, closed` and tears the stack down the moment
Expand Down
35 changes: 12 additions & 23 deletions docs/persistence.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,32 +92,21 @@ The prd deploy applies migrations: `alchemy.run.ts` declares the PlanetScale `ma
`Planetscale.PostgresBranch` with `migrations` pointed at `packages/db/drizzle`, and the api, ai and
alerting Workers carry its name in their env so they upload after it. Bookkeeping is alchemy's
`__alchemy_migrations`; `drizzle.__drizzle_migrations` was copied in once and is frozen, so never run
`drizzle-kit migrate` against prd. The deploy migrates as a temporary role, not `postgres`, so the
branch's default privileges do not cover the tables it creates: a migration that creates one grants
it `TO PUBLIC` itself. The deploy reads `PLANETSCALE_API_TOKEN_ID` / `PLANETSCALE_API_TOKEN` /
`PLANETSCALE_ORGANIZATION` from Infisical prod; `bun dev` leaves the PlanetScale provider out.

The first v1 migrate on a database migrated by drizzle 0.x upgrades `drizzle.__drizzle_migrations`
in place (adds `name` and `applied_at`), matching every existing row to a local folder by
`created_at` truncated to the second, then by hash, and **refusing the whole run if any row matches
nothing**. A row like that is a migration that was applied and later renumbered or re-timestamped,
or one applied from a branch that never merged. Check before migrating. The report prints a
DELETE for a superseded row and an UPDATE for a renumbered row whose SQL is byte-identical; a row
whose SQL changed after it ran gets a `git diff` instead, because relabelling it would record
statements this database never saw as applied.

The report also lists every local migration no row matches, because the v1 migrator applies all
of them where the 0.x migrator only applied those newer than the newest recorded timestamp. A
migration whose DDL reached the schema without a row (a `db:push`, a run that died after its
transaction committed) used to be skipped silently and now fails on the objects that already
exist. Compare each pending folder's first statement with the schema; record the ones already
applied with the INSERT the report prints rather than replaying them:
`drizzle-kit migrate` against prd. The deploy migrates as a temporary role that is dropped with
`postgres` as its successor, so the tables it creates end up owned by `postgres` with no other grants.
Every runtime role must therefore inherit `postgres` (`USAGE`, not mere membership, which only
grants `SET ROLE`). Inheritance is fixed when PlanetScale creates the role and `GRANT postgres` is
refused, so a role without it is replaced: mint the new one with `--inherited-roles postgres`, rotate
the consumer's URL, then delete the old role. This must list no runtime credential (a personal dev
credential may appear):

```bash
bun run --cwd packages/db db:migrate:preflight # DATABASE_URL, defaults to the docker Postgres
bun run --cwd packages/db ps:migrations-preflight main # a PlanetScale branch, read-only
```sql
SELECT rolname FROM pg_roles WHERE rolname LIKE 'pscale\_api\_%' AND NOT pg_has_role(rolname, 'postgres', 'usage')
```

The deploy reads `PLANETSCALE_API_TOKEN_ID` / `PLANETSCALE_API_TOKEN` /
`PLANETSCALE_ORGANIZATION` from Infisical prod; `bun dev` leaves the PlanetScale provider out.

PGlite applies the same bundled migrations while its layer is built. The test harness caches a
fresh migrated PGlite snapshot and restores it per test, so integration tests exercise the
PostgreSQL schema without a shared server.
Expand Down
3 changes: 0 additions & 3 deletions packages/db/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,11 @@
"test": "vitest run --passWithNoTests",
"db:generate": "drizzle-kit generate --config ./drizzle.config.ts",
"db:migrate": "drizzle-kit migrate --config ./drizzle.config.ts",
"db:migrate:preflight": "bun scripts/migrations-preflight.ts",
"db:push": "drizzle-kit push --config ./drizzle.config.ts",
"db:studio": "drizzle-kit studio --config ./drizzle.config.ts",
"db:ensure-privileges": "bun scripts/ensure-privileges.ts",
"db:reset-preview": "bun scripts/reset-preview-branch.ts",
"db:audit-raw-sql": "bun scripts/audit-raw-sql.ts",
"db:normalize-preview": "bun scripts/normalize-preview-ownership.ts",
"ps:migrations-preflight": "bun scripts/planetscale-migrations-preflight.ts",
"db:restore-test": "bun scripts/restore-test.ts",
"db:backfill:dashboards-v3": "bun scripts/backfill-dashboard-datasource-v3.ts"
},
Expand Down
28 changes: 0 additions & 28 deletions packages/db/scripts/ensure-privileges.test.ts

This file was deleted.

151 changes: 0 additions & 151 deletions packages/db/scripts/ensure-privileges.ts

This file was deleted.

Loading
Loading