chore: upgrade Databricks CLI to 1.9.0, DQX to 0.15.0, and track uv.lock - #51
Merged
Conversation
Bumps the deploy toolchain (CLI + databricks-bundles 0.298.0 -> 1.9.0), DQX 0.12.0 -> 0.15.0, and the dev/test tooling. Serverless-mirror pins (pyspark 4.1.0, pandas, numpy, pyarrow) are deliberately unchanged and now carry a comment explaining why. uv.lock is now tracked: the blanket *.lock ignore was a leftover from the 2024 pipenv migration and silently excluded it, leaving databricks-sdk and other transitive deps to float on every CI run. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds `uv lock --check` ahead of `make sync`. Now that uv.lock is tracked, a PR that edits pyproject.toml without re-locking would otherwise pass CI — `make sync` (uv sync, no --locked) silently re-resolves in the throwaway checkout, discarding the update and leaving the committed lock stale. The read-only check fails fast instead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Trims the sql-diagram entry in the Features list to a single sentence that points at the example diagram, matching the sibling bullets. Removes the inline #TODO. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
setup-uv stopped publishing moving major tags after v7, so `@v9` failed to
resolve ("unable to find version v9"). Pin the exact release tag, matching
how setup-cli and the uv version are already pinned.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Merged
1 task
andre-salvati
added a commit
that referenced
this pull request
Jul 24, 2026
…d CI secret hardening (#52) ## What? Three related pieces of CI/CD and production hardening: 1. **Fixes a live prod outage** — `job1_prod` has failed since 2026-07-24 09:05 UTC. `raw.order_quarantine` is no longer schema-pinned. 2. **Adds a rollback playbook** to `specs/workflow.md`. 3. **Narrows the CI secret's blast radius** and pins every GitHub action by commit SHA. ## Why? **The outage:** DQX 0.15.0 (#51) added `rule_fingerprint`, `rule_set_fingerprint` and `skipped` to the `_errors`/`_warnings` structs it appends to quarantined rows. The write used `overwriteSchema=false`, so the first prod run after that merge hard-failed with `DELTA_METADATA_MISMATCH` and skipped every downstream task. **The rollback gap:** merging to `main` deploys to prod with no approval gate, and nothing documented how to get back. Flagged independently by a CI/CD article review and by the official Databricks best-practices doc. **The secret:** `DATABRICKS_CLIENT_SECRET` sat in the workflow-level `env` block, visible to every step — including dependency installation, which is exactly where third-party code runs. ## How? The quarantine struct shape is owned by the **DQX library version**, not by the source data contract, so pinning it turns every dependency bump into a prod outage — a false positive, not the drift signal the guard exists to raise. The guard isn't lost: the business columns reaching quarantine come from the same DataFrame written to `raw.order` a few lines later, which keeps `overwriteSchema=false`. The rollback section is organised around one rule — **a redeploy reverts code, never data**. Code rollback works because nothing deployed is stored apart from the checkout (`jobs.yml`, the SDP pipeline and the dashboard are all regenerated), so git history *is* the artifact store. Actions are pinned by SHA because tags are mutable. `databricks/setup-cli` resolves the CLI version from a `VERSION` file at the pinned ref — verified it still installs 1.9.0, not assumed. The architecture spec records why OIDC would be the real fix and why this repo can't use it: the trust anchor is an account-level API, and Free Edition has none. Neither mitigation shortens the credential's lifetime; only OIDC does. ## Validation? Unit tests pass (16); ruff lint + format clean. The staging integration test in this PR's CI exercises `extract_source2` end-to-end, which is the actual regression path. The `onpush.yml` changes are validated by this very run — an auth or SHA mistake shows up as a red CI here rather than on `main`. ## Impact in prod - [x] **Schema change** — `raw.order_quarantine` gains three nested fields inside `_errors`/`_warnings` (DQX framework metadata; no business column changes). | Change | Risk | Remediation | |---|---|---| | Add field (nested, framework metadata) | 🟢 The write now carries `overwriteSchema=true`, so it self-heals instead of failing | None required | No consumer reads `raw.order_quarantine` — not the dashboard, not the SDP pipeline. Business columns are untouched. **Chosen strategy:** leave as-is — the next `job1_prod` run rewrites the table schema and recovers on its own. This PR *ends* an outage rather than risking one.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What?
Version bumps across the deploy toolchain and dev dependencies, plus
uv.lockis now tracked.0.298.0→1.9.0(databricks.yml,setup-cliaction,databricks-bundles)0.12.0→0.15.0v0.15.22,setup-uv@v9+ uv0.11.31, pytest9.1.1, pytest-cov7.1.0, coverage7.15.2, pre-commit4.6.1, pydantic2.13.44.1.0, pandas2.2.3, numpy2.1.3, pyarrow21.0.0Why?
The CLI was ~10 months behind and
databricks-bundlestracks it in lockstep. DQX was three minor versions behind.The serverless-mirror pins are not upgraded despite newer PyPI releases (pyspark 4.2.0, pandas 3.0.5, numpy 2.5.1, pyarrow 25.0.0). Serverless environment version 5 — the runtime the wheel executes on — ships Spark 4.1.0 with exactly pandas 2.2.3 / numpy 2.1.3 / pyarrow 21.0.0. Bumping them would put unit tests ahead of production. Environment version 5 is the newest available; there is no Spark 4.2 serverless environment yet. A comment in
pyproject.tomlnow records this so the pins aren't "helpfully" bumped later.How?
uv.lockis now tracked. The blanket*.lockignore dated to the 2024 pipenv migration and predates uv; it silently excluded the lockfile, so CI resolved dependencies from scratch on every run.databricks-sdkisn't declared inpyproject.tomlat all — it arrives transitively via DQX under~=0.73, a 27-minor-version window — so CI could install a different SDK on two runs of the same commit. Tracking the lock also makes the existingcache-dependency-glob: "uv.lock"in CI meaningful; previously it hashed a file absent from every fresh checkout.A
uv lock --checkCI step now runs beforemake sync, failing fast if a PR editspyproject.tomlwithout re-locking (otherwisemake syncre-resolves silently in CI's checkout and the committed lock goes stale).Auth migration (operator action). CLI v1.0.0 moved OAuth tokens from
~/.databricks/token-cache.jsonto the OS keyring. Profiles withauth_type = databricks-cli(dev,account) need a one-timedatabricks auth login --profile <name>. Service-principal profiles (DEFAULT,staging,prod) authenticate viaclient_id/client_secretand are unaffected — CI needs no change.Validation?
Run on
dev:make deploy env=dev(Deployment complete!) andmake run env=dev(TERMINATED SUCCESS). The nested job1 run confirmedextract_source2— the DQX task — SUCCESS, alongsiderun_sdp(SDP pipeline, full refresh).bundle validateOK on staging and prod, including themode: productiondeployer-identity gatejobs.ymlfor all three targets ondatabricks-bundles1.9.0uv sync --lockedexit 0 — lock exactly satisfiespyproject.tomlImpact in prod
DQX 0.15 adds three fields to the
_errors/_warningsstructs onraw.order_quarantine:rule_fingerprint: string,rule_set_fingerprint: string,skipped: boolean.name, message, columns, filter, function, run_time, run_id, user_metadatarule_fingerprint,rule_set_fingerprint,skippedBecause every medallion write uses
overwriteSchema=false, the widened struct is rejected against a table created under 0.12 → job1extract_source2fails. Remediation is 🟠make drop env=<env> yes=--yesbefore the first run on that env (quarantine is a rebuildable DQX output — no source-of-truth data is lost).Chosen strategy: drop + recreate.
Staging — done.
make drop env=staging(17 objects) →make deploy env=staging→make run env=stagingall passed, and CI on this branch is green. The recreatedstaging.raw.order_quarantinenow carries the 11-field struct. This is what validates the migration end-to-end; the dev run could not, becausedev_….raw.order_quarantinewas created fresh mid-run (create path, not theoverwriteSchema=falsedrift path).Prod — pending, one manual step after merge.
prod.raw.order_quarantinestill holds the old 8-field struct (10,000 rows). Before the next prod job1 run:make drop env=prod yes=--yes. Note this rebuilds the full prod medallion (~44M rows) fromexternal_sourceand re-freezesproduct_nameagainst current values, so historical frozen names do not survive — schedule it deliberately.