Skip to content

feat(appkit): report schema drift and Lakebase host mismatches at startup - #607

Merged
ditadi merged 4 commits into
mainfrom
feat/database-startup-diagnostics
Oct 7, 2026
Merged

ditadi merged 4 commits into
mainfrom
feat/database-startup-diagnostics

Conversation

@ditadi

@ditadi ditadi commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Why the change

When a DatabasePlugin app's database didn't match its declared schema, or PGHOST pointed at a different Lakebase branch, the app started normally and then failed every request with "Database operation failed", so these changes make startup and the server log name the actual problem.

Special things to note

  • Behavior change: setup now fails when a declared table or column is missing. Before, the app started and then failed later, on every request. The check reads pg_catalog, not information_schema, because information_schema hides objects the role can't access and would report a privilege gap as a missing column.
  • The server log now includes Postgres' own error text, but only for SQLSTATE classes that name connections, credentials or schema objects (08, 28, 3D, 3F, 42, 53, 57), and for Node system errors such as ENOTFOUND. Data (22), constraint (23) and PL/pgSQL (P0) errors still log only their code, because their text can include row values. The client gets the same stable message as before.
  • The PR also commits a regenerated docs/docs/api/appkit/Interface.PluginManifest.md. feat: deprecate serving plugin in favor of agents #596 added PluginManifest.deprecated without regenerating it, so CI's generated-docs check fails on any PR that touches packages/ until the file is committed.

Change outline

Three independent changes, one commit each:

 createDatabaseState()                                  # plugins/database/lifecycle.ts
   await dataPath.raw`select 1`
+  await assertSchemaMatchesDatabase(dataPath, schema)  # new schema-check.ts: one pg_catalog query
   catch (error)
+    setup-phase DatabasePluginError → rethrown as is
+      "table public.notes is missing columns board_id, body"
     anything else → "Database setup failed"            # connector details stay hidden

 classifyDriverError(error)                             # database/runtime/engine/drizzle-data-path.ts
-  log "classified as INTERNAL (SQLSTATE 42703)"
+  log "classified as INTERNAL (SQLSTATE 42703): column notes.board_id does not exist"
   thrown error and client message unchanged

 initializeLakebasePool() / LakebasePlugin.setup()      # connectors/lakebase, plugins/lakebase
-  user = await getUsernameWithApiLookup(config)
+  [user] = await Promise.all([
+    getUsernameWithApiLookup(config),
+    warnOnEndpointHostMismatch(config),                # new endpoint-host.ts
+  ])

warnOnEndpointHostMismatch reads LAKEBASE_ENDPOINT from the workspace API and logs a warning with both hosts when PGHOST isn't one of them, or when the endpoint no longer exists. It runs once per endpoint and host, gives up after 3 seconds, and never throws.

The database.md setup section and the lakebase.md environment section describe the new checks, and the playground's database page tooltip no longer says the plugin never introspects tables.

This pull request and its description were written by Isaac.

@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

📦 Bundle size report

Compared against bundle-size-baseline.json (main).

@databricks/appkit

npm tarball (packed): 1.2 MB (+7.6 KB) — gzipped download (dist + bin; excludes release-only docs/NOTICE).

dist raw gzip
JS (runtime) 1.2 MB (+8.5 KB) 445 KB (+3.5 KB)
Type declarations 455 KB (+87 B) 165 KB (+82 B)
Source maps 2.4 MB (+15 KB) 834 KB (+5.8 KB)
Other 11 KB 3.7 KB
Total 4.1 MB (+24 KB) 1.4 MB (+9.4 KB)
Per-entry composition (own code — deps external (as shipped))
Entry Initial (gz) Lazy (gz) Total (gz) node_modules (min) Own code (min)
. 102 KB (+830 B) 2.5 KB 104 KB (+830 B) external 330 KB (+2.0 KB)
./beta 98 KB (+1.5 KB) 484 B 99 KB (+1.5 KB) external 297 KB (+4.2 KB)
./testing 42 KB (+16 B) 32 KB 74 KB (+16 B) external 214 KB (+32 B)
./tsdown 520 B 0 B 520 B external 813 B
./type-generator 23 KB 0 B 23 KB external 65 KB

Chunks:

Entry Chunk Load Size (gz)
. index.js initial 97 KB
. utils.js initial 4.6 KB
. remote-tunnel-manager.js lazy 2.5 KB
./beta beta.js initial 81 KB
./beta stream-manager.js initial 5.9 KB
./beta service-context.js initial 4.2 KB
./beta databricks.js initial 3.3 KB
./beta wide-event-emitter.js initial 3.2 KB
./beta client.js initial 595 B
./beta index.js initial 20 B
./beta supervisor-api.js lazy 192 B
./beta databricks.js lazy 177 B
./beta index.js lazy 115 B
./testing manifest.js initial 29 KB
./testing index.js initial 10 KB
./testing wide-event-emitter.js initial 2.9 KB
./testing index.js lazy 28 KB
./testing remote-tunnel-manager.js lazy 2.5 KB
./testing utils.js lazy 1.8 KB
./tsdown index.js initial 520 B
./type-generator index.js initial 23 KB

@databricks/appkit-ui

npm tarball (packed): 350 KB (+4 B) — gzipped download (dist + bin; excludes release-only docs/NOTICE).

dist raw gzip
JS (runtime) 395 KB 132 KB
Type declarations 229 KB 84 KB (+1 B)
Source maps 766 KB 253 KB
CSS 16 KB 3.2 KB
Total 1.4 MB 472 KB (+1 B)
Per-entry composition (consumer bundle — deps bundled, peerDeps external)
Entry Initial (gz) Lazy (gz) Total (gz) node_modules (min) Own code (min)
./js 5.3 KB 49 KB 55 KB 208 KB 14 KB
./js/beta 20 B 0 B 20 B 0 B 0 B
./react 432 KB 49 KB 481 KB 1.3 MB 177 KB
./react/beta 1.0 KB 0 B 1.0 KB 0 B 1.9 KB

Chunks:

Entry Chunk Load Size (gz)
./js index.js initial 5.2 KB
./js chunk initial 120 B
./js apache-arrow lazy 49 KB
./js/beta beta.js initial 20 B
./react index.js initial 430 KB
./react tslib initial 2.1 KB
./react apache-arrow lazy 49 KB
./react/beta beta.js initial 1.0 KB

@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

🤖 AppKit PR bot

🔬 Run evals

Start an eval for this PR from the evals-monitor app: Go to Evals Monitor →

📦 Try this PR's app template

Scaffolds a new app from this PR's SDK build. Run it in any folder (requires the GitHub CLI — gh auth login — and the Databricks CLI):

gh run download 37631701673 -R databricks/appkit -n appkit-template-0.84.0-pr.6dae54c-feat-database-startup-diagnostics-607 -D appkit-pr-607 \
  && unzip -o "appkit-pr-607/appkit-template-0.84.0-pr.6dae54c-feat-database-startup-diagnostics-607.zip" -d "appkit-pr-607" \
  && databricks apps init --template "appkit-pr-607"

The template pins @databricks/appkit and @databricks/appkit-ui to tarballs built from this branch, so the scaffolded app runs against this PR's code.

@ditadi
ditadi requested review from MarioCadenas and atilafassina and removed request for calvarjorge September 27, 2026 16:14
@ditadi
ditadi requested a balanced review from Copilot October 7, 2026 13:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

ditadi and others added 4 commits October 7, 2026 15:47
A driver failure reached the server log as a bare SQLSTATE, so a missing
column logged only "SQLSTATE 42703" with no object named. Log the
driver's own message, detail, and hint for SQLSTATE classes whose text
names connections, credentials, or schema objects (08, 28, 3D, 3F, 42,
53, 57), and the text of Node system errors such as ENOTFOUND.

Value-bearing classes (22 data, 23 constraints, P0 PL/pgSQL) still log
only their code, the Drizzle wrapper's SQL and params are never read,
and the thrown error and client message are unchanged.

Co-authored-by: Isaac <no-reply@databricks.com>
Signed-off-by: ditadi <victordperd@gmail.com>
The plugin never migrates, so a database that did not match the declared
schema passed setup and then failed every request with an opaque
"Database operation failed". Setup now reads pg_catalog after the
connectivity check and fails with the missing names, for example
"table public.notes is missing columns board_id, author_email, body".
pg_catalog is used because information_schema hides objects the role
cannot access, which would report a privilege gap as a missing column.

A setup failure now keeps its own reason when it propagates; other
setup errors are still replaced so connector details stay out of it.
The mvp integration test stubs the catalog check.

Co-authored-by: Isaac <no-reply@databricks.com>
Signed-off-by: ditadi <victordperd@gmail.com>
Credentials are issued for LAKEBASE_ENDPOINT but the pool connects to
PGHOST, so a host left over from another branch silently served that
branch's tables. At startup, initializeLakebasePool and the lakebase
plugin look the endpoint up and warn with both hosts when PGHOST is not
among them, or when the endpoint no longer exists.

The lookup runs alongside the identity lookup, is shared per endpoint
and host, gives up after 3 seconds, and never fails startup.

Co-authored-by: Isaac <no-reply@databricks.com>
Signed-off-by: ditadi <victordperd@gmail.com>
Signed-off-by: ditadi <victordperd@gmail.com>
@ditadi
ditadi force-pushed the feat/database-startup-diagnostics branch from fe66137 to ca2a2d0 Compare October 7, 2026 13:50
@ditadi
ditadi merged commit b8c89e9 into main Oct 7, 2026
12 checks passed
@ditadi
ditadi deleted the feat/database-startup-diagnostics branch October 7, 2026 14:38
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.

3 participants