Skip to content

chore: upgrade Databricks CLI to 1.9.0, DQX to 0.15.0, and track uv.lock - #51

Merged
andre-salvati merged 5 commits into
mainfrom
chore/dependency-updates
Jul 23, 2026
Merged

chore: upgrade Databricks CLI to 1.9.0, DQX to 0.15.0, and track uv.lock#51
andre-salvati merged 5 commits into
mainfrom
chore/dependency-updates

Conversation

@andre-salvati

@andre-salvati andre-salvati commented Jul 23, 2026

Copy link
Copy Markdown
Owner

What?

Version bumps across the deploy toolchain and dev dependencies, plus uv.lock is now tracked.

  • Databricks CLI / bundles 0.298.01.9.0 (databricks.yml, setup-cli action, databricks-bundles)
  • DQX 0.12.00.15.0
  • Tooling: ruff hook v0.15.22, setup-uv@v9 + uv 0.11.31, pytest 9.1.1, pytest-cov 7.1.0, coverage 7.15.2, pre-commit 4.6.1, pydantic 2.13.4
  • Unchanged on purpose: pyspark 4.1.0, pandas 2.2.3, numpy 2.1.3, pyarrow 21.0.0

Why?

The CLI was ~10 months behind and databricks-bundles tracks 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.toml now records this so the pins aren't "helpfully" bumped later.

How?

uv.lock is now tracked. The blanket *.lock ignore dated to the 2024 pipenv migration and predates uv; it silently excluded the lockfile, so CI resolved dependencies from scratch on every run. databricks-sdk isn't declared in pyproject.toml at 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 existing cache-dependency-glob: "uv.lock" in CI meaningful; previously it hashed a file absent from every fresh checkout.

A uv lock --check CI step now runs before make sync, failing fast if a PR edits pyproject.toml without re-locking (otherwise make sync re-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.json to the OS keyring. Profiles with auth_type = databricks-cli (dev, account) need a one-time databricks auth login --profile <name>. Service-principal profiles (DEFAULT, staging, prod) authenticate via client_id/client_secret and are unaffected — CI needs no change.

Validation?

Run on dev: make deploy env=dev (Deployment complete!) and make run env=dev (TERMINATED SUCCESS). The nested job1 run confirmed extract_source2 — the DQX task — SUCCESS, alongside run_sdp (SDP pipeline, full refresh).

  • 16 unit tests pass on pytest 9.1.1
  • ruff lint + format pass
  • bundle validate OK on staging and prod, including the mode: production deployer-identity gate
  • Job generator produces jobs.yml for all three targets on databricks-bundles 1.9.0
  • uv sync --locked exit 0 — lock exactly satisfies pyproject.toml

Impact in prod

  • No table schema/data change — no production impact.
  • Schema change — classify and declare the remediation:

DQX 0.15 adds three fields to the _errors / _warnings structs on raw.order_quarantine: rule_fingerprint: string, rule_set_fingerprint: string, skipped: boolean.

struct fields
old (DQX 0.12) name, message, columns, filter, function, run_time, run_id, user_metadata
new (DQX 0.15) above + rule_fingerprint, rule_set_fingerprint, skipped

Because every medallion write uses overwriteSchema=false, the widened struct is rejected against a table created under 0.12 → job1 extract_source2 fails. Remediation is 🟠 make drop env=<env> yes=--yes before 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=stagingmake run env=staging all passed, and CI on this branch is green. The recreated staging.raw.order_quarantine now carries the 11-field struct. This is what validates the migration end-to-end; the dev run could not, because dev_….raw.order_quarantine was created fresh mid-run (create path, not the overwriteSchema=false drift path).

Prod — pending, one manual step after merge. prod.raw.order_quarantine still 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) from external_source and re-freezes product_name against current values, so historical frozen names do not survive — schedule it deliberately.

andre-salvati and others added 5 commits July 23, 2026 13:46
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>
@andre-salvati
andre-salvati merged commit 2212ee4 into main Jul 23, 2026
1 check passed
@andre-salvati
andre-salvati deleted the chore/dependency-updates branch July 23, 2026 19:27
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.
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