Autonomous schema-drift detection for Microsoft Fabric medallion architectures, with Claude-powered impact analysis and cross-workspace lineage
Project description
๐ต๏ธ Microsoft Fabric Schema Drift Detection
Lineage-aware schema-drift detection for Microsoft Fabric medallion architectures โ with Claude-powered impact analysis, auto-fix PRs, and Teams / Outlook / Slack alerts.
A source column got renamed. Tonight your Silver notebook fails, tomorrow three Power BI dashboards silently show blanks, and nobody knows why until the CFO asks.
This project fixes that. An autonomous agent watches every layer of a Fabric medallion architecture, walks a column-level lineage graph to find what a change breaks three layers downstream, asks Claude to judge the business impact and draft fixes, opens a Git PR with the mechanical repairs, and alerts your team on Teams, Outlook, and Slack โ before the dashboards break.
Runs against a real Fabric tenant (live mode) or a local DuckDB replica of AdventureWorksLT (simulate mode) with zero capacity cost โ same code path.
๐ฆ Install
pip install fabric-schema-drift-detective
fabric-drift --help # console script (also: python -m fabric_drift_detective)
Optional extras per integration:
pip install "fabric-schema-drift-detective[live]" # Fabric SQL endpoint (pyodbc)
pip install "fabric-schema-drift-detective[hana]" # SAP HANA direct-connect
pip install "fabric-schema-drift-detective[snowflake]" # Snowflake direct-connect
pip install "fabric-schema-drift-detective[databricks]" # Databricks / Unity Catalog
pip install "fabric-schema-drift-detective[sqlserver]" # Azure SQL / SQL Server
pip install "fabric-schema-drift-detective[postgres]" # PostgreSQL (RDS/Aurora)
pip install "fabric-schema-drift-detective[redshift]" # AWS Redshift
pip install "fabric-schema-drift-detective[mysql]" # MySQL / Aurora MySQL
pip install "fabric-schema-drift-detective[cosmos]" # Azure Cosmos DB
โก Quickstart โ 60 seconds, no Fabric account
The demo needs the repo (sample data isn't shipped in the wheel):
git clone https://github.com/naveenjujaray/microsoft-fabric-schema-drift-detection.git
cd microsoft-fabric-schema-drift-detection
pip install -e . # deps + the `fabric-drift` CLI
bash scripts/run_demo.sh
Run the tests and quality gates any time:
pip install -e .[dev] # pytest, ruff, mypy, bandit + type stubs
pytest -q # 330 tests
Loads AdventureWorksLT โ builds the medallion โ snapshots baselines โ injects six
kinds of drift โ prints the drift report, the PR it would open, and every notification
payload. No API keys required (Claude falls back to a deterministic mock; set
ANTHROPIC_API_KEY for real reasoning).
๐๏ธ Architecture
Bronze (Lakehouse) raw AdventureWorksLT tables, ingested as-is via Dataflow Gen2
โ drift watch: source columns appearing/disappearing/retyped
โผ
Silver (Lakehouse) cleaned, deduped, conformed keys, standardized types
โ drift watch: transformation contract changes, key/type drift
โผ
Gold (Warehouse) star schema โ Dim_Customer, Dim_Product, Dim_Date,
โ Fact_Sales โ consumed by the semantic model
โผ
Semantic Model relationships, DAX measures, RLS
โ drift watch: a Gold column drop/rename that breaks a measure or relationship
โผ
Power BI reports (PBIP in Git) final consumers that break silently today
Detection pipeline:
SchemaBackend (ABC) baselines (.baselines/*.json)
โโ FabricBackend (fab CLI + REST) โ
โโ LocalBackend (DuckDB + JSON) โผ
โ current schemas โโโโโโโโโบ schema_diff โโโบ lineage graph โโโบ Claude
โ (17 drift types) (cross-layer + (severity,
โผ cross-workspace fixes, PR text)
five LayerSchemas breaks) โ
โผ
git_handler (branch+PR) notifications
(console/Slack/Teams/Outlook)
๐ The differentiator: cross-layer lineage
silver.customers.email renamed to email_address? The lineage graph walks:
silver:customers.email
โโโบ gold:Dim_Customer.Email
โโโบ semantic_model:Customer.Email
โโโบ reports:Customer Detail.Customer.Email
โฆand emits a cross_layer_break for each โ so the alert says "the Customer Detail
report will break", not just "a column changed". DAX measures are parsed for
Table[Column] references, so SUM(Sales[Freight]) breaks when
silver.sales_orders.freight is dropped, three layers up.
๐ โฆand cross-WORKSPACE lineage
Real Fabric estates span workspaces: ingestion in one, the enterprise warehouse in another, reporting in a third โ connected by OneLake shortcuts, mirrored databases and semantic-model bindings. Declare that topology in a workspace manifest and the same drift becomes:
Contoso-Ingestion (Workspace A) silver:customers.email โ the rename
โโ OneLake shortcut โโบ Contoso-Enterprise-DW (Workspace B)
gold:Dim_Customer.Email cross_workspace_break
semantic_model:Customer.Email cross_workspace_break
โโโบ Contoso-Reporting (Workspace C)
reports:Customer Detail cross_workspace_break
Impact analysis then reports the workspace name, artifact, workspace path and per-workspace blast radius โ and states plainly: "This schema change impacts assets across multiple Microsoft Fabric workspaces." Tenant-boundary crossings are flagged too. Full guide: docs/CROSS_WORKSPACE.md.
๐ Supported sources
Two integration modes โ pick per source:
- Already mirrored/shortcut into Fabric? Use
mode: liveโ theFabricBackendsees it in the lakehouse. No source backend needed. - Direct-connect upstream (
mode: source) โ read schema straight from the source system and catch drift before it lands in Fabric: the Bronze-boundary contract.
| Source | Mode | Status | Install |
|---|---|---|---|
| Fabric Lakehouse (Bronze/Silver/Gold) | live | โ shipped | โ |
| Fabric Warehouse (Gold) | live | โ shipped | .[live] for SQL endpoint |
| Semantic model (TMDL) + PBIP reports | live | โ shipped | โ |
| Local DuckDB simulation | simulate | โ shipped | โ |
| SAP HANA | source | โ shipped | .[hana] |
| Snowflake | source | โ shipped | .[snowflake] |
| Databricks / Unity Catalog | source | โ shipped | .[databricks] |
| Azure SQL / SQL Server | source | โ shipped | .[sqlserver] |
| PostgreSQL (RDS/Aurora) | source | โ shipped | .[postgres] |
| AWS Redshift | source | โ shipped | .[redshift] |
| MySQL / Aurora MySQL | source | โ shipped | .[mysql] |
| Azure Cosmos DB (schema inferred by document sampling) | source | โ shipped | .[cosmos] |
A new backend is ~100 lines: connection factory + catalog query + type
map on top of the shared SQL-catalog base, with cross-source type
normalization so NVARCHAR vs STRING never reads as false drift.
Per-source setup: docs/BACKENDS.md ยท claim one:
CONTRIBUTING.md.
Lineage is data, not code: declare your own medallion's column mappings
in a lineage manifest
(lineage.manifest in config.yaml); the AdventureWorksLT demo mappings
are just the fallback. Scope the watch with watch.layers /
watch.mode: boundaries (contract-enforced layers: only boundary
breaks surface).
๐ Example story โ the rename that would have reached the CFO
9:02 AM. An upstream engineer renames a column in the CRM export:
email โ email_address. Nobody tells the BI team. The nightly Dataflow succeeds. No error anywhere.
Here is what happens with this project running as a scheduled Fabric pipeline:
1 โ Detect. The 2 AM run snapshots every layer and diffs against the stored
baselines. schema_diff sees email gone and email_address arrived in
silver.customers โ same type, same position, near-identical name โ and
classifies it as a column_rename (not a scarier drop+add):
[CRITICAL] silver:customers.email column_rename ('email' -> 'email_address')
2 โ Walk the lineage. The column-level graph follows the rename downstream
and synthesizes a cross_layer_break at every stop:
silver:customers.email
โโโบ gold:Dim_Customer.Email
โโโบ semantic_model:Customer.Email
โโโบ reports:Customer Detail.Customer.Email โ the dashboard the CFO opens
So the alert reads "the Customer Detail report will break" โ not just "a column changed somewhere."
3 โ Reason (Claude). Claude judges business impact, confirms the rename is mechanically fixable, and drafts the exact TMDL find/replace plus PR prose. (No API key? A deterministic mock does the same shape offline.)
4 โ Fix. git_handler cuts a branch drift-fix/<timestamp>, rewrites
sourceColumn: email โ email_address in the PBIP TMDL, and opens a PR โ
never touching main.
5 โ Alert. One DriftAlert fans out to Teams / Outlook / Slack:
severity counts, the three worst drifts, the blast radius, and the PR link.
6 โ Gate. The pipeline fails the run (exit 1) on critical drift, so
drift is alertable like any other pipeline failure โ and CI blocks the merge
until it's resolved.
9:05 AM. Instead of blank dashboards next week, the BI team has a PR waiting, a Teams card explaining what and why, and a red pipeline that already stopped the bad state from shipping.
Want to interrogate it yourself? Ask an agent:
fabric-drift --agent lineage_qa --task "what breaks if silver.customers.email is renamed?"
fabric-drift --agent triage # rank everything currently drifting, P1โP3
Run the whole story locally in 60 seconds with bash scripts/run_demo.sh โ it
injects this rename (plus a money-truncating precision_scale_change, a dropped
column, a type change, and more) and prints every step above.
๐ฌ Drift types
Seventeen typed drifts, grouped by the level they hit. Severity is judged by
whether a change breaks consumers or just risks them; auto_fixable means
the repair is mechanical (a downstream find/replace), not a business decision.
Column-level
| Type | Severity | Auto-fixable | Enterprise scenario |
|---|---|---|---|
column_drop |
๐ด critical | โ | source system retires a field; everything binding to it errors |
column_add |
๐ต info | โ | new field ingested; safe, but should flow to Silver/Gold |
type_change |
๐ก warning (safe cast) / ๐ด critical | safe casts only | INTEGERโBIGINT widens (safe); DECIMALโVARCHAR corrupts joins/measures |
precision_scale_change |
๐ก warning (widen) / ๐ด critical (narrow) | widen only | DECIMAL(19,4)โDECIMAL(10,2) silently truncates money; VARCHAR(50)โVARCHAR(20) clips values |
column_rename (deterministic stable matching: type + position + name similarity, with a reported confidence score) |
๐ด critical | โ | a refactor renames emailโemail_address; every downstream ref breaks but is mechanically remappable |
column_reorder |
๐ก warning | โ | positions swap; breaks SELECT * inserts and positional CSV/parquet binding |
nullability_change |
๐ก warning | โ | NOT NULLโNULL lets nulls into a column a measure assumes is populated |
default_change |
๐ก warning | โ | DEFAULT 0 โ DEFAULT NULL silently changes what lands in every new row โ data semantics move with no query breaking |
flag_change |
๐ก warning | โ | a column gains/loses identity/computed/auto_increment; inserts break or two systems start generating conflicting values |
Table-level
| Type | Severity | Auto-fixable | Enterprise scenario |
|---|---|---|---|
table_drop |
๐ด critical | โ | a Lakehouse table disappears; all downstream tables/reports orphaned |
table_add |
๐ต info | โ | new entity ingested; informational |
key_change |
๐ด critical | โ | primary/business key gained or lost; relationships and dedup logic break |
Semantic-model-level
| Type | Severity | Auto-fixable | Enterprise scenario |
|---|---|---|---|
measure_drop |
๐ด critical | โ | a DAX measure is deleted; every visual bound to it goes blank |
measure_add |
๐ต info | โ | new measure published; informational |
measure_change |
๐ก warning | โ | measure expression edited โ numbers shift silently under a report the business already trusts |
Cross-layer / cross-workspace
| Type | Severity | Auto-fixable | Enterprise scenario |
|---|---|---|---|
cross_layer_break (synthesized via lineage graph) |
๐ด critical | rename-driven only | a Bronze/Silver change reaches a Gold column, a DAX measure, or a Power BI visual three layers up |
cross_workspace_break (synthesized when the blast radius crosses a workspace boundary) |
๐ด critical | rename-driven only | a Silver change in the ingestion workspace breaks the warehouse, semantic model, or reports in other workspaces via shortcuts / mirrors / model bindings |
Whitespace-only DAX reformatting (Power BI Desktop rewrites TMDL on every save)
is normalized away, so measure_change fires on real logic edits โ not noise.
Real-world drift the model is designed for
The catalog maps directly to how enterprise Fabric estates actually break:
upstream source-system schema evolution (SAP/Salesforce/Dynamics adding,
retyping, renaming fields), Dataflow Gen2 / notebook refactors silently
changing the Silver contract, money-precision narrowing in financial marts,
and semantic-model edits that move numbers without breaking a query. Drifts
that need metadata the backends don't yet capture โ Delta partition-column
changes, collation/case-sensitivity โ are the tracked roadmap; the
engine's typed, lineage-aware design extends to them without touching
consumers. (default_change/flag_change fire wherever a backend captures
defaults/flags โ the simulate backend does; SQL backends opt in by widening
their catalog query.)
๐ Usage
Installed via pip, the CLI is fabric-drift (equivalently
python -m fabric_drift_detective):
fabric-drift --mode simulate --baseline # capture baseline snapshots
fabric-drift --mode simulate --once # one detection cycle
fabric-drift --mode simulate --once --dry-run # render all payloads, send nothing
fabric-drift --mode live --once --open-pr # real Fabric + real PR
fabric-drift --mode snowflake --once # direct-connect upstream source
fabric-drift --provision # show fab provisioning steps
Exit codes: 0 clean ยท 1 critical drift (usable as a CI gate) ยท 2 config
error ยท 3 missing/corrupt baselines.
Baselines are never recreated implicitly. If baseline files vanish or corrupt, the run fails with exit code
3and tells you to re-capture with--baselineโ silent recreation would hide exactly the changes a drift detector exists to catch.
Everything is configured in config.yaml (IDs, model, channels) +
.env (secrets โ see .env.example). No hardcoded IDs anywhere.
๐ค Agents โ ten tool-use specialists
Beyond the scheduled pipeline, ten Claude agents run tool-use loops over the same backends, differ and lineage graph โ for interactive investigation, verified repair and operations:
fabric-drift --list-agents
fabric-drift --agent lineage_qa --task "what breaks if silver.sales_orders.freight is dropped?"
fabric-drift --agent fix_verify --allow-writes # propose โ apply โ re-diff โ retry
| Agent | Superpower |
|---|---|
fix_verify โ๏ธ |
repairs drift, then re-runs the differ to prove the fix worked โ retries until green |
drift_investigator |
rename vs drop+add verdicts backed by data profiles, not just heuristics |
lineage_qa |
interactive "what breaks ifโฆ" / "where does X come from" answers |
root_cause |
traces symptoms upstream; groups 10 breaks under 1 root cause |
triage |
P1/P2/P3 fix queue ranked by blast radius + report criticality |
migration_planner |
step-by-step reversible migration plans (plan only โ no DDL tools) |
pr_responder โ๏ธ |
reads reviewer comments on the auto-PR, adjusts edits, pushes follow-up |
provisioner โ๏ธ |
drives the fab CLI, captures item GUIDs into config.yaml |
historian |
mines archived baselines for drift trends + hotspot tables |
notification_composer |
engineer-Slack vs executive-email framing of the same incident |
Production guard rails: write tools hard-gated behind --allow-writes
(โ๏ธ agents plan-only otherwise) ยท sandboxed file/SQL access (SELECT-only,
row-capped, .env/.git denied) ยท per-run turn + token budgets ยท JSONL
transcript of every run in .agent_runs/ ยท no key? clean offline result, never
a crash. Full guide: docs/AGENTS.md.
๐ญ Run it inside Fabric โ notebook, pipeline, CI/CD
The fabric/ folder ships Fabric-native artifacts (Git-integration format):
| Artifact | What it does |
|---|---|
DriftDetection.Notebook |
Clones the repo, runs a full cycle in-workspace with the notebook identity โ no secrets, baselines persisted to the attached lakehouse |
DriftCheckPipeline.DataPipeline |
Scheduled orchestration; fails the pipeline run (SchemaDriftCritical) when critical drift is found, so drift is alertable like any pipeline failure |
deploy-config.yml + parameter.yml |
fab deploy (fabric-cicd) config with per-environment (DEV/TEST/PROD) parameterization |
FABRIC_WORKSPACE_ID=<guid> bash scripts/deploy_fabric.sh # one-command deploy
Auth adapts automatically โ four methods behind one credential (src/azure_auth.py):
FABRIC_AUTH_METHOD |
Identity | Where |
|---|---|---|
client_secret (default outside Fabric) |
SPN from .env |
laptops, GitHub Actions |
notebookutils (auto-detected in Fabric) |
notebook identity | inside Fabric notebooks |
managed_identity |
system/user-assigned MI | Azure VMs, Container Apps |
default |
DefaultAzureCredential chain |
everything else |
Full guide: docs/FABRIC_NATIVE.md.
๐ฃ Notifications
One DriftAlert object, three formatters โ content logic never duplicated:
| Channel | Format | Transport |
|---|---|---|
| Teams | Adaptive Card (severity counts, top 3 drifts, PR button) | incoming webhook or Graph POST /teams/{id}/channels/{id}/messages |
| Outlook | HTML email (severity table, per-layer breakdown, PR link) | Graph sendMail or SMTP fallback |
| Slack | Block Kit | incoming webhook or chat.postMessage bot |
| Console | rich table | always on |
Each channel independently toggleable; a failing channel never blocks the others;
--dry-run prints every payload. Teams/Outlook Graph calls reuse the same
credential as Fabric โ one app registration, one auth stack (permissions:
ChannelMessage.Send, Mail.Send โ see docs/FABRIC_SETUP.md).
๐ Docs
| Doc | Contents |
|---|---|
| docs/ARCHITECTURE.md | module map, data flow, design decisions |
| docs/CROSS_WORKSPACE.md | cross-workspace lineage: manifest format, link types, tenant boundaries |
| docs/BACKENDS.md | per-source setup: drivers, auth, catalog queries, type normalization |
| CONTRIBUTING.md | add a source backend: the seam, the recipe, the contract-suite test bar |
| docs/AGENTS.md | the ten agents: tools, guard rails, examples, config |
| docs/FABRIC_SETUP.md | verified fab CLI sequence to stand up the workspace + Graph permissions |
| docs/FABRIC_NATIVE.md | notebook / pipeline / fab deploy โ running inside Fabric |
| docs/DEMO.md | the simulate-mode demo, step by step |
โ Tests & quality gates
pytest # 330 tests: differ (17 drift types), deterministic
# rename matching, cross-workspace lineage, baseline
# fail-loud policy, REST retry/backoff, git handler
# security guards, agents, notifications, lineage
# manifest, watch scope, type normalization, and the
# backend contract suite (Local/HANA/Snowflake/
# Databricks/SQL Server/Postgres/Redshift/MySQL/
# Cosmos DB)
ruff check . # lint (incl. bugbear + security rules)
mypy # strict-leaning type check, 0 errors
bandit -c pyproject.toml -r src main.py # security scan
CI (.github/workflows/ci.yml) runs all four on
every push/PR across Python 3.10 and 3.12, plus pip-audit for dependency
vulnerabilities, with a 75% coverage floor.
๐ License
MIT License
Copyright (c) 2026 Naveen Jujaray
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Made with โค๏ธ by Naveen Jujaray
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fabric_schema_drift_detective-0.1.2.tar.gz.
File metadata
- Download URL: fabric_schema_drift_detective-0.1.2.tar.gz
- Upload date:
- Size: 116.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
535ec22c66bb6f3693d3c53eb9cb489d210fb5df3bf8fcf8ebf919dea494a570
|
|
| MD5 |
95580110200f78e570022e8f0866b223
|
|
| BLAKE2b-256 |
e4ce4a829639ef8e84a45d6ad79b83693fddbab8c32c8515a5d38169862c27f1
|
Provenance
The following attestation bundles were made for fabric_schema_drift_detective-0.1.2.tar.gz:
Publisher:
workflow.yml on naveenjujaray/microsoft-fabric-schema-drift-detection
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fabric_schema_drift_detective-0.1.2.tar.gz -
Subject digest:
535ec22c66bb6f3693d3c53eb9cb489d210fb5df3bf8fcf8ebf919dea494a570 - Sigstore transparency entry: 2113005746
- Sigstore integration time:
-
Permalink:
naveenjujaray/microsoft-fabric-schema-drift-detection@344b04d0f601ee298716241950c95a017c604373 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/naveenjujaray
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
workflow.yml@344b04d0f601ee298716241950c95a017c604373 -
Trigger Event:
release
-
Statement type:
File details
Details for the file fabric_schema_drift_detective-0.1.2-py3-none-any.whl.
File metadata
- Download URL: fabric_schema_drift_detective-0.1.2-py3-none-any.whl
- Upload date:
- Size: 110.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9ba24b30fc767d8e0bf7835b018778ad1b56e9fba2b3aeb135c034a0179b9729
|
|
| MD5 |
c89b24b792122e51817e0a5f87ca5eee
|
|
| BLAKE2b-256 |
8c7065fe6ee70b6adc3e8d95da46ad8ac58e502105c5932256d63d465eea43fe
|
Provenance
The following attestation bundles were made for fabric_schema_drift_detective-0.1.2-py3-none-any.whl:
Publisher:
workflow.yml on naveenjujaray/microsoft-fabric-schema-drift-detection
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fabric_schema_drift_detective-0.1.2-py3-none-any.whl -
Subject digest:
9ba24b30fc767d8e0bf7835b018778ad1b56e9fba2b3aeb135c034a0179b9729 - Sigstore transparency entry: 2113005799
- Sigstore integration time:
-
Permalink:
naveenjujaray/microsoft-fabric-schema-drift-detection@344b04d0f601ee298716241950c95a017c604373 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/naveenjujaray
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
workflow.yml@344b04d0f601ee298716241950c95a017c604373 -
Trigger Event:
release
-
Statement type: