Skip to content

fix: unblock job1_prod (DQX quarantine schema) + rollback playbook and CI secret hardening - #52

Merged
andre-salvati merged 4 commits into
mainfrom
docs/ci-cd-hardening
Jul 24, 2026
Merged

fix: unblock job1_prod (DQX quarantine schema) + rollback playbook and CI secret hardening#52
andre-salvati merged 4 commits into
mainfrom
docs/ci-cd-hardening

Conversation

@andre-salvati

Copy link
Copy Markdown
Owner

What?

Three related pieces of CI/CD and production hardening:

  1. Fixes a live prod outagejob1_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

  • Schema changeraw.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.

andre-salvati and others added 4 commits July 24, 2026 14:50
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 to
raw.order_quarantine used overwriteSchema=false, so the first prod run after
the upgrade hard-failed with DELTA_METADATA_MISMATCH and skipped everything
downstream.

The shape of those structs 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 rather than the drift signal the guard exists to
raise. Switch that one write to overwriteSchema=true, joining ops._health as
the second intentional exception.

The guard is not lost: the business columns reaching quarantine come from the
same DataFrame written to raw.order a few lines later, which keeps
overwriteSchema=false, so genuine source drift still hard-fails the same task.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Merging to main deploys to prod with no approval gate, but nothing documented
how to get back. Both the CI/CD article review and the official Databricks
best-practices doc flag the missing rollback procedure as a real gap.

The section is organised around the rule that matters: a redeploy reverts
code, never data. Code rollback is a checkout of the last good sha plus
make deploy env=prod, which 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. Data rollback is per-table
Delta RESTORE, with the caveat that curated.order_enriched cannot be rebuilt
by re-running: it freezes product_name at sale time, so re-deriving stamps
today's names onto historical orders and is silently wrong. Schema rollback
via make drop is last resort since it destroys history.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two mitigations for the long-lived DATABRICKS_CLIENT_SECRET, plus the context
for why it is still there at all.

The secret moves from the workflow-level env block, where it was visible to
every step including dependency installation, to the three steps that actually
talk to Databricks. Dependency install and unit tests are exactly where
third-party code executes, and they never needed it. The host and client id
stay at workflow level — they are identifiers, not credentials.

Every third-party action is now pinned to a full commit SHA rather than a tag,
since tags are mutable and whoever controls an action's repo can repoint one at
code that then runs on the runner with that environment. databricks/setup-cli
resolves the CLI version from a VERSION file at the pinned ref, so the SHA pin
keeps the CLI on 1.9.0 exactly as the tag did — verified, not assumed.

The architecture spec now records why OIDC / workload identity federation is
the mechanism Databricks recommends instead, and why this repo cannot use it:
the trust anchor is a service principal federation policy, an account-level
API, and Free Edition has no account-level API access. The exact policy command
and workflow delta are documented for anyone forking onto a paid account.
Neither mitigation shortens the credential's lifetime; only OIDC does that.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@andre-salvati
andre-salvati merged commit 3e79621 into main Jul 24, 2026
andre-salvati added a commit that referenced this pull request Jul 25, 2026
workflow.md said the schema-drift guard had a single exception (ops._health).
There are two: raw.order_quarantine also writes with overwriteSchema=true,
added in #52 and already documented in CLAUDE.md — the spec was not updated in
the same commit, which is what its own "keep docs in sync" section exists to
prevent. Also corrects the Co-Authored-By trailer, which named Opus 4.8.

require-changelog-entry.sh printed guidance the spec had superseded: "at most
3 sentences" (dropped at #49 because it constrained nothing) and a header
template built from the branch name. Both now match specs/workflow.md — a
~1000-character single paragraph and the PR-URL header — and the message links
to the section so the two cannot drift silently again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
EOF --subject 'Commit the agent tooling: workflow hooks, three project skills, and a fully ignored reports/' --body-file /tmp/pr-body-c1_otrlp.md
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