Skip to content

feat(appkit): add appkit db verify - #674

Draft
ditadi wants to merge 5 commits into
stack/database-dev-flow/05-db-initfrom
stack/database-dev-flow/06-db-verify
Draft

ditadi wants to merge 5 commits into
stack/database-dev-flow/05-db-initfrom
stack/database-dev-flow/06-db-verify

Conversation

@ditadi

@ditadi ditadi commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Stacked on #672.

Summary

Adds appkit db verify, a read-only check that answers "will my app work with its database?".

  • appkit db verify checks the branch .env points at, which is normally your dev or session branch:

    • every table and column config/database/schema.ts declares exists (the same check the plugin runs on start);
    • every committed migration has been applied;
    • no committed migration is one the migrator will never run there. That covers a migration merged out of order (older than one the branch already ran) and one edited after it ran.
  • appkit db verify --prod checks that the deployed app is set up to use the branch databricks.yml deploys to, through the Databricks API only:

    • the app exists;
    • its postgres resource names that branch and database, with CAN_CONNECT_AND_CREATE;
    • its service principal has a role on the branch;
    • the app is running.

    A running app has applied its migrations and checked its schema on start (feat(appkit): apply committed database migrations on deploy start #654). --prod reads no database, so it needs no Postgres role on the deploy branch.

Both exit 1 on any finding, and each finding names its fix.

Details

  • The local check runs in one READ ONLY transaction that is rolled back, then closes the connection.
  • Stranded migrations come from comparing the journal table's (created_at, hash) rows with the committed journal and files. The migrator itself is unchanged: it still runs only entries newer than the last recorded one.
  • With --prod, a crashed, stopped, failed or in-progress app is reported with the app's own status message and the command to run next (databricks apps logs, start or deploy).
  • The app name comes from the default target's resources.apps override, else the top-level app. A name with ${…} is reported as unresolvable rather than looked up.
  • findSchemaProblems is split out of the plugin's startup check, so verify reports the same problems the app would fail on.

Tests

  • The local tests cover:

    • a matching branch;
    • missing columns and pending migrations;
    • no journal table;
    • out-of-order and edited migrations;
    • the read-only transaction, including rollback and connection cleanup on failure.
  • The --prod tests use a faked Apps and Lakebase API where the developer has no role on the deploy branch. They cover:

    • each binding mismatch;
    • each app state;
    • a missing app, branch or database;
    • an undeclared or interpolated app name.

    They also assert that no database connection and no write happens.

  • Checked live, read-only, against a workspace:

    • the apps API's postgres resource format and a deleted app's 404;
    • a stopped app's response (it has no active deployment);
    • the service principal's role lookup.
  • Checked against a local Postgres 14 with the real migrator: no false positives, and both stranded cases detected.

defineSchema() without a schemaName now places its tables in the Postgres
schema "app" instead of "public". The deployed app's service principal can
create and own a schema of its own, but has no CREATE on public, so tables
in public could not be migrated on deploy without a manual grant.

Signed-off-by: ditadi <victordperd@gmail.com>
`appkit db migrate` generates a migration from config/database/schema.ts
with drizzle-kit, regenerates shared/appkit-types/database.d.ts, and
applies pending migrations to the branch .env points at. It applies only
to a branch created for this developer (dev-…) or this checkout
(session-…); on any other branch, including the project default and any
branch databricks.yml deploys to, it generates and says why nothing was
applied. It connects only after the Lakebase API confirms that PGHOST is
a host of LAKEBASE_ENDPOINT.

The logic lives in a new @databricks/appkit/database-tooling entry point
that the shared CLI loads lazily, so the runtime never imports drizzle-kit.
drizzle-kit is an optional peer dependency: apps that generate migrations
install it as a dev dependency, and the command says how when it is
missing. `appkit db` stays hidden from --help until the database plugin
is offered by `databricks apps init`.

Signed-off-by: ditadi <victordperd@gmail.com>
`appkit db introspect` reads an existing Postgres schema (default: app) from
pg_catalog and writes config/database/schema.ts from it, keeping the previous
file as schema.ts.bak. It also writes the migration that brings history up to
the database: a first 0000_baseline, which the deployed app adopts on start
when its tables already exist, or the next migration on a branch created for
this developer or checkout, where it is recorded as applied. What the schema
builder cannot express is skipped and reported, and what it declares
differently from the database (a timestamp precision, an index) is listed.

With history, it runs only on a branch that has applied every committed
migration, and it refuses a migration that would drop something it skipped,
since that migration runs on deploy. If anything fails after schema.ts is
written, schema.ts, its backup and the migrations are put back.

Signed-off-by: ditadi <victordperd@gmail.com>
`appkit db init` gives a checkout its own Lakebase branch: a per-developer
dev branch or a per-checkout session branch (7-day TTL), forked from the
project's default branch. It makes sure the branch has a read-write endpoint
and a Postgres role for the developer, hands the objects the fork copied from
its parent to the developer (role delete with reassign_owned_to, only on that
branch), points .env at it, and can start the schema by migrating,
introspecting or cleaning. It refuses the project's default branch, protected
branches and branches databricks.yml deploys to before changing anything:
those belong to the deployed app.

`appkit db clean` drops the app schema and `appkit db seed` runs
config/database/seed.sql with the app schema on the search_path, both only on
a branch created for this developer or checkout.

Signed-off-by: ditadi <victordperd@gmail.com>
`appkit db verify` checks, read-only, that the branch .env points at matches
config/database/schema.ts and has every committed migration, including the
ones the migrator would never run there: merged out of order, or edited after
they ran.

With --prod it checks, through the Databricks API only, that the deployed app
is set up to use the branch databricks.yml deploys to: the app exists, its
postgres resource names that branch and database with CAN_CONNECT_AND_CREATE,
its service principal has a role there, and it is running. It reads no
database, so it needs no role on the deploy branch.

Signed-off-by: ditadi <victordperd@gmail.com>

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