Shift-left data governance · Apache 2.0
A developer drops a column. Every application test passes — it is valid SQL, and nothing in that repository mentions data. Twelve hours later a dbt model returns NULLs, a dashboard shows the wrong revenue, and a model trains on empty values. Nothing errored.
The lineage that would have caught it already exists in DataHub. It is just on the wrong side of the merge button. ContextCI moves it.
The run
The Action triggers on pull_request. No developer has to open DataHub, know dbt, or wait for a data-team review.
Parses the patch — not whole files — for raw SQL DDL, Alembic op.* migrations, dbt schema.yml removals and dbt select lists. Deleting an old migration is not a schema change; commented-out DDL is ignored.
Resolves each table to a dataset URN, then pulls column-level lineage, ownership, glossary terms, the dataset profile, and the SQL people actually run against it.
An LLM weighs the evidence and writes the backward-compatible migration. Deterministic rules set a floor it cannot go below, and a compliance gate can override both.
One sticky PR comment, an optional fix commit, and tags written back into DataHub. Exit 1 on a block verdict stops the merge.
Two-way integration
Reading metadata is table stakes. The graph is richer after a ContextCI run than it was before.
| Capability | How ContextCI uses it |
|---|---|
| Column-level lineage | searchAcrossLineage finds downstream assets; each one's fineGrainedLineages is checked for the changed column's schemaField URN — so “reads this table” and “reads this column” are never conflated. |
| Ownership | The owners of the assets that actually break are named on the pull request, so the conversation starts with the right people. |
| Glossary & tags | PII, GDPR, PHI and Tier-1 markers drive a compliance gate that can force a block regardless of blast radius. |
| Dataset profile | Row count, size and the column's null fraction size the migration — a backfill across four million rows is not the same as one on a staging stub. |
| Query history | topSqlQueries and per-column fieldCounts ground the generated SQL in joins and filters people really use. |
| Tag mutations | Writes Schema-Change-Pending and PR-Under-Review on the dataset, Blast-Risk-{level} downstream, and a tag on the column itself via editableSchemaMetadata. |
| Institutional memory | A pending-change note keyed by the PR URL, so an analyst browsing the catalog learns why the dataset is flagged. |
Non-negotiable
Both came from real failures found by re-running against live infrastructure, not from design meetings.
A table missing from the catalog, or a downstream asset whose column-level lineage cannot be confirmed, produces a warning — never a pass. The comment labels every asset confirmed or table-level only, so nobody mistakes silence for safety.
Hosted inference is not reproducible: the same input returned high / block twice and medium / warn on the third run, and temperature=0 does not fix it. The deterministic analysis is a floor. The model writes the narrative and the migration; the rules own the decision.
Verified, not claimed
A DataHub v1.7.0 instance with authentication enabled, and a live pull request on this repository.
$ make demo phase 1: 1 schema change detected phase 2: SampleHiveDataset → 13 downstream phase 3: verdict from groq · risk=critical action=block phase 4b: 17/17 DataHub mutations applied phase 4a: comment → github.com/LSUDOKO/ContextCI/pull/1 ContextCI verdict: block (risk: critical) Blocking merge: this change breaks downstream assets.
Try it
make demo
It boots DataHub, ingests sample metadata with real column-level lineage, runs the
gate against a breaking DROP COLUMN, and prints the forwarded DataHub UI
URL. Roughly 10–15 minutes cold, almost all of it pulling images.
.github/workflows/contextci-gate.ymlDATAHUB_MCP_URL, DATAHUB_GMS_TOKEN, and either GROQ_API_KEY or ANTHROPIC_API_KEYWith no LLM key the deterministic analyzer runs instead, and the gate still works. If DataHub is unreachable the run degrades to diff-only with a banner rather than failing your build — only a real block verdict exits non-zero.