Skip to content

feat: CI-resilient --wait typegen with committed-types fallback - #510

Merged
atilafassina merged 14 commits into
mainfrom
fix-shared-subpath-bundling
Aug 4, 2026
Merged

feat: CI-resilient --wait typegen with committed-types fallback#510
atilafassina merged 14 commits into
mainfrom
fix-shared-subpath-bundling

Conversation

@atilafassina

@atilafassina atilafassina commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

What & why

Makes the blocking (--wait) type-generation path resilient for CI/CD. On a fresh CI checkout, the committed .d.ts files become the fallback of record: --wait never overwrites good committed types with degraded (result: unknown) ones, and a two-bucket failure taxonomy decides crash-vs-fall-back. The non-blocking dev/install path is untouched.

Behavior

Blocking (--wait) mode:

  • Deterministic failures always crash, regardless of committed types: SQL syntax errors, HTTP 404 (bad warehouse id), HTTP 400 (malformed request).
  • Environmental failures gate on committed types: auth (401/403), connectivity, DELETED/DELETING, wait-for-RUNNING timeout, and any unrecognized failure.
    • Committed types present → skip the write, emit one loud greppable stderr warning naming the coarse cause (auth blocked / warehouse unreachable / warehouse unavailable) + warehouse id, and exit 0.
    • No committed types → crash with a generic "run generate-types --wait locally and commit the .d.ts" remedy.
  • Serving types (serving.d.ts) are excluded from the gate (gitignored, degrade independently).

Non-blocking mode: unchanged — still writes best-effort/degraded types immediately and refreshes via the detached worker.

Commits (one per phase)

  1. fix(appkit) — anti-clobber: --wait never overwrites committed types on any degrade
  2. feat(appkit)classifyBlockingFailure two-bucket taxonomy (pure, in errors.ts)
  3. feat(appkit) — has-types gate + loud warning + CLI terminal wiring (the join)
  4. docs — document the CI-resilient --wait behavior
  5. chore — remove implementation-phase narration and slop

Testing

  • pnpm test — 3763 passed / 1 skipped / 0 failed (type-generator suite: 537 across 19 files, incl. new errors.test.ts + gate-matrix coverage: environmental+present→warn, environmental+absent→crash, deterministic→crash, serving-exclusion, partial-presence, CI-safe warning).
  • pnpm -r typecheck, pnpm check (0 errors), pnpm build, pnpm docs:build — all green.
  • Runtime acceptance bar: dev-playground boots under --conditions=development with no ERR_PACKAGE_PATH_NOT_EXPORTED; no surviving shared/<subpath> bare imports.

Notes

  • Known v1 simplification: a metric-views-only app (no config/queries/) writes an empty analytics.d.ts on a fresh run, which satisfies the has-types gate — so an environmental failure falls back + warns rather than crashing even on a true first build. Documented in the type-generation docs.
  • Built on top of the revert: relocate typegen cache to committed .appkit/ dir #509 revert (no committed cache; committed .d.ts output is the fallback).

This pull request and its description were written by Isaac.

typegen

Phase 1 of typegen-ci-resilient-describe. In blocking (`--wait`) mode the
type generator now suppresses the `.d.ts` write on ANY degraded result
(query `result: unknown` or degraded metric), leaving the committed types
untouched as the CI fallback of record, then throws as before. The prior
path wrote degraded (`unknown`) declarations first and threw after, which
clobbered good committed types on a fresh CI checkout — including via the
auth/timeout/bad-id/DELETED fatal-degrade path.

Non-blocking mode is unchanged (still writes degraded types for the
detached worker to refresh). Tests inverted to assert no-write-on-degrade
while preserving throw + behavioral assertions; adds coverage for the
query-side fatal-degrade clobber-prevention case.

Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
Phase 2 of typegen-ci-resilient-describe. Adds a pure
classifyBlockingFailure(error) → "deterministic" | "environmental" to
type-generator/errors.ts, building on the existing getErrorStatus and
isConnectivityError helpers.

Deterministic (build must crash regardless of committed types): HTTP 404
(bad warehouse id) and 400 (malformed request), checked first and walked
through cause/AggregateError chains. Environmental (has-types gate applies
later): 401/403 auth, connectivity, DELETED/DELETING, wait-timeout, and any
unrecognized failure (the default). The auth status set is a one-line
change point for the auth-owning team. No behavior change to
isConnectivityError. Adds tests/errors.test.ts.

Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
…wait

Phase 3 of typegen-ci-resilient-describe (join point). Wires the
classifyBlockingFailure taxonomy into the reordered blocking write path so
`--wait` degrades gracefully on environmental failures instead of always
crashing:

- Deterministic failures (SQL syntax, HTTP 404/400) still crash the build.
- Environmental failures (401/403 auth, connectivity, DELETED/DELETING,
  wait-timeout, unrecognized) now flow through a has-types gate: if committed
  analytics/metric-views .d.ts exist, skip the (already-suppressed) write,
  emit one loud greppable stderr warning naming the coarse cause
  (auth blocked / warehouse unreachable / warehouse unavailable) + warehouse
  id, and exit 0 using the committed types as the fallback of record. If no
  committed types exist, crash with a generic 'run generate-types --wait
  locally' remedy.

Serving types are excluded from the gate (gitignored, degrade independently).
Non-blocking mode is unchanged. Threads deterministic-vs-environmental and a
coarse cause label out of the query + metric preflights. Adds gate-matrix
coverage: environmental+present (per cause) → warning+exit0, environmental+
absent → crash, deterministic (404/400/syntax) → crash regardless of types,
partial presence, serving-exclusion, and CI-safe (ANSI-free) warning output.

Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
Phase 4 of typegen-ci-resilient-describe. Updates the type-generation docs
to describe the committed-types fallback and two-bucket failure taxonomy for
blocking (`--wait`) builds: committed .d.ts as the fallback of record,
--wait never overwriting good types with degraded ones, deterministic
failures (SQL syntax / 404 / 400) crashing vs. environmental failures
(auth / connectivity / deleted / timeout) gating on committed-type presence,
the loud stderr warning, and the run-locally remedy for a first build with no
committed types. Notes the metric-views-only edge case (empty analytics.d.ts
satisfies the gate). Refreshes the metric-view section to reference the same
taxonomy instead of the old always-fail framing.

Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
…anges

Wash of the typegen-ci-resilient-describe branch: strips loop-process
"Phase N" labels from comments, test names, and describe titles (keeping
the semantic text), removes an unnecessary comment / empty else-block /
useless default parameter, rewrites two stale+duplicated write-suppression
comments to match the actual behavior, and converts errors.test.ts's
`(error as any)` casts to the sibling `Object.assign(new Error(...), { ... })`
idiom. Comments, names, and test-setup style only — no logic or assertion
changes (537 tests still pass).

Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
@atilafassina atilafassina changed the title fix(appkit): CI-resilient --wait typegen with committed-types fallback feat: CI-resilient --wait typegen with committed-types fallback Aug 3, 2026
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle size report

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

@databricks/appkit

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

dist raw gzip
JS (runtime) 832 KB (+7.8 KB) 290 KB (+1.8 KB)
Type declarations 306 KB 105 KB
Source maps 1.6 MB (+19 KB) 544 KB (+4.7 KB)
Other 11 KB 3.7 KB
Total 2.7 MB (+26 KB) 943 KB (+6.5 KB)
Per-entry composition (own code — deps external (as shipped))
Entry Initial (gz) Lazy (gz) Total (gz) node_modules (min) Own code (min)
. 87 KB (+652 B) 2.5 KB 89 KB (+652 B) external 284 KB (+2.3 KB)
./beta 45 KB 458 B 45 KB external 129 KB
./type-generator 20 KB (+673 B) 0 B 20 KB (+673 B) external 57 KB (+2.3 KB)

Chunks:

Entry Chunk Load Size (gz)
. index.js initial 83 KB
. utils.js initial 4.0 KB
. remote-tunnel-manager.js lazy 2.5 KB
./beta beta.js initial 29 KB
./beta stream-manager.js initial 5.8 KB
./beta wide-event-emitter.js initial 3.2 KB
./beta databricks.js initial 3.0 KB
./beta configuration.js initial 2.1 KB
./beta service-context.js initial 1.3 KB
./beta client.js initial 431 B
./beta client-options.js initial 220 B
./beta supervisor-api.js lazy 193 B
./beta databricks.js lazy 142 B
./beta index.js lazy 123 B
./type-generator index.js initial 20 KB

@databricks/appkit-ui

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

dist raw gzip
JS (runtime) 360 KB 119 KB
Type declarations 205 KB 74 KB
Source maps 686 KB 224 KB
CSS 16 KB 3.3 KB
Total 1.2 MB 422 KB
Per-entry composition (consumer bundle — deps bundled, peerDeps external)
Entry Initial (gz) Lazy (gz) Total (gz) node_modules (min) Own code (min)
./js 4.3 KB 49 KB 54 KB 208 KB 12 KB
./js/beta 20 B 0 B 20 B 0 B 0 B
./react 429 KB 49 KB 478 KB 1.3 MB 168 KB
./react/beta 20 B 0 B 20 B 0 B 0 B

Chunks:

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

@github-actions

github-actions Bot commented Aug 3, 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 30919671743 -R databricks/appkit -n appkit-template-0.52.0-pr.9b3eb86-fix-shared-subpath-bundling-510 -D appkit-pr-510 \
  && unzip -o "appkit-pr-510/appkit-template-0.52.0-pr.9b3eb86-fix-shared-subpath-bundling-510.zip" -d "appkit-pr-510" \
  && databricks apps init --template "appkit-pr-510"

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.

@atilafassina
atilafassina marked this pull request as ready for review August 3, 2026 16:58
@atilafassina
atilafassina requested a review from a team as a code owner August 3, 2026 16:58

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.

Pull request overview

This PR hardens AppKit’s type-generator blocking (--wait) path for CI/CD by preventing degraded output from overwriting committed .d.ts artifacts, and by introducing a deterministic-vs-environmental failure taxonomy so CI can fall back to committed types (with a loud warning) on transient/environmental issues.

Changes:

  • Add anti-clobber write suppression in blocking mode to preserve committed analytics.d.ts / metric-views.d.ts when generation degrades.
  • Introduce classifyBlockingFailure and plumb “environmental failure” signals into the entrypoint so it can decide warn+fallback vs crash.
  • Expand/adjust test coverage for the new gating and warning behavior, and update docs to describe CI-resilient --wait.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/appkit/src/type-generator/types.ts Extends result types to carry environmental-failure signals used by the blocking-mode gate.
packages/appkit/src/type-generator/index.ts Implements blocking-mode anti-clobber, has-types gating, and warning-message generation.
packages/appkit/src/type-generator/query-registry.ts Updates query preflight/failure handling to separate deterministic fatals from environmental degradation and track it for the gate.
packages/appkit/src/type-generator/errors.ts Adds classifyBlockingFailure to bucket errors into deterministic vs environmental.
packages/appkit/src/type-generator/tests/index.test.ts Adds extensive integration-style tests for write suppression, warning output, and the has-types gate.
packages/appkit/src/type-generator/tests/generate-queries.test.ts Updates query-registry tests to align with environmental degradation + gate signaling.
packages/appkit/src/type-generator/tests/errors.test.ts Adds unit tests for classifyBlockingFailure.
docs/docs/development/type-generation.md Documents CI-resilient --wait semantics, including warning behavior and the two-bucket taxonomy.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/appkit/src/type-generator/index.ts Outdated
Comment thread packages/appkit/src/type-generator/index.ts Outdated
Comment thread packages/appkit/src/type-generator/tests/index.test.ts Outdated
Comment thread packages/appkit/src/type-generator/query-registry.ts
Comment thread packages/appkit/src/type-generator/query-registry.ts Outdated
…use labels

`classifyEnvironmentalCause` read only `err.status`/`err.statusCode` on the
top-level error, while `errors.ts` already resolved `response.status` and
walked `cause`/`AggregateError` chains. A 403 reported under `response` or
wrapped in a cause chain was therefore labeled "warehouse unavailable"
instead of "auth blocked", pointing CI at the wrong remedy.

Move the helper next to `classifyBlockingFailure` in errors.ts so both
classifiers share one status-extraction path, and reuse the existing
chain walk.

Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
…typegen

The blocking-mode preflight set `decision = "degradeAll"` for connectivity
failures but left `isEnvironmental` false, so `hadEnvironmentalFailure`
never flipped and the has-types gate never ran for an unreachable
warehouse. On a fresh CI checkout that meant: queries degrade to
`result: unknown`, write suppression skips `analytics.d.ts` entirely, no
fatal errors are recorded, and the run exits 0 having written no types —
the build then fails later somewhere less legible.

Flag connectivity failures (preflight and per-query DESCRIBE) as
environmental so the gate decides: warn and fall back when committed types
exist, crash with the run-locally remedy when they don't. This cannot turn
a passing build red — with committed types the outcome is unchanged apart
from the warning now being emitted.

Also return `environmentalCause` from `generateQueriesFromDescribe`, which
`QueryGenerationResult` already declared and the metric path already set.
Without it every query-path environmental failure fell back to the default
"warehouse unavailable" label, and the "warehouse unreachable" label was
unreachable in practice.

Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
…utation

Two comments claimed blocking mode suppresses writes only for pure
degradation and that degraded artifacts are still written before a throw.
`suppressDegradedWrite: mode === "blocking"` is unconditional, so any
degradation suppresses the write including on runs that then throw. The
behavior is what the PR intends; the comments described the old shape.

The ANSI-free warning test set `process.env.CI` and deleted it in `finally`,
clobbering a pre-existing value for later tests. Nothing under
`src/type-generator` reads `CI`, so the assignment never affected the
assertion — it only risked perturbing third-party color detection, which is
exactly what this test checks. Drop it rather than stub it.

Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
…uery path

The gate tests in index.test.ts mock `generateQueriesFromDescribe`, so they
supply the `hadEnvironmentalFailure: true` they assert on. They pass whether
or not the query path actually reports it — which is how the connectivity
bug survived review: the preflight returned false and no test joined the
two halves.

Mock only the SDK boundary so the real query path classifies the failure and
the real gate decides. Covers the fresh-checkout crash (previously exit 0
with no types written), the committed-types warn-and-preserve path with the
"warehouse unreachable" label, and non-blocking staying silent.

Verified these fail when the isEnvironmental assignment is reverted.

Co-authored-by: Isaac
…rehouse gate

The new gate test replaced `@databricks/sdk-experimental` with a bare factory
mock. After #475 the type-generator reaches the SDK through
`../../workspace-client`, whose `legacy.ts` destructures `ConfigError`,
`Context` and `TimeUnits` off that module at import time — so the factory-only
mock starved module init and the suite failed to collect.

Mock the wrapper instead, spreading `importOriginal` so the re-exported SDK
values survive. This matches the sibling type-generator tests and keeps the
test's intent: the wrapper is now the client boundary, so the real query path
still classifies the failure and the real gate still decides. Verified by
re-injecting the original bug (`isEnvironmental = false` on the connectivity
branch) and confirming both gate tests fail.

Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
Signed-off-by: Atila Fassina <atila@fassina.eu>

@calvarjorge calvarjorge 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.

If I add new analytics query but don't commit the types, can this cause the build to succeed when the SQL warehouse is not available? Even if no types are there? In this scenario, we might want to fail, given that types are clearly missing

@MarioCadenas

Copy link
Copy Markdown
Collaborator

If I add new analytics query but don't commit the types, can this cause the build to succeed when the SQL warehouse is not available? Even if no types are there? In this scenario, we might want to fail, given that types are clearly missing

either the build will fail or the app will just normally run, but this would be a strange scenario because this would be caught on deploy time

Signed-off-by: Atila Fassina <atila@fassina.eu>
Six comment-only removals in the has-types gate: an orphaned section header
that summarised the two decision comments immediately below it, two docblocks
restating single-expression predicates (`isQueryDegraded`,
`hasAnyDegradedMetrics`), two trailing comments restating their own
assignment, and a redundant `@param warehouseId` plus a sentence duplicating
the `@param cause` list.

No behaviour change. The load-bearing comments stay: the two-bucket taxonomy
contracts in errors.ts, the write-suppression invariant note, and the gate
test's account of how the original bug evaded weaker coverage.

Co-authored-by: Isaac
Signed-off-by: Atila Fassina <atila@fassina.eu>
@atilafassina
atilafassina merged commit f90fa9b into main Aug 4, 2026
10 checks passed
@atilafassina
atilafassina deleted the fix-shared-subpath-bundling branch August 4, 2026 14:57
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.

4 participants