Skip to main content

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.

PyPI CI Python Tests Agents License: MIT Microsoft Fabric Claude


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

โšก 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 -r requirements.txt
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                 # 248 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
        โ”‚                            (15 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 โ€” the FabricBackend sees 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 ๐Ÿ™‹ contributor wanted โ€”
Azure SQL / SQL Server source ๐Ÿ™‹ contributor wanted โ€”
PostgreSQL (RDS/Aurora) source ๐Ÿ™‹ contributor wanted โ€”
AWS Redshift source ๐Ÿ™‹ contributor wanted โ€”
MySQL / Aurora MySQL source ๐Ÿ™‹ contributor wanted โ€”
Azure Cosmos DB source ๐Ÿ™‹ wanted (advanced โ€” schemaless) โ€”

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:

python main.py --agent lineage_qa --task "what breaks if silver.customers.email is renamed?"
python main.py --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

Fifteen 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

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, column default changes โ€” are the tracked roadmap; the engine's typed, lineage-aware design extends to them without touching consumers.

๐Ÿš€ Usage

python main.py --mode simulate --baseline        # capture baseline snapshots
python main.py --mode simulate --once            # one detection cycle
python main.py --mode simulate --once --dry-run  # render all payloads, send nothing
python main.py --mode live --once --open-pr      # real Fabric + real PR
python main.py --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 3 and 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:

python main.py --list-agents
python main.py --agent lineage_qa --task "what breaks if silver.sales_orders.freight is dropped?"
python main.py --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                    # 248 tests: differ (15 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)
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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fabric_schema_drift_detective-0.1.0.tar.gz (108.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fabric_schema_drift_detective-0.1.0-py3-none-any.whl (96.5 kB view details)

Uploaded Python 3

File details

Details for the file fabric_schema_drift_detective-0.1.0.tar.gz.

File metadata

File hashes

Hashes for fabric_schema_drift_detective-0.1.0.tar.gz
Algorithm Hash digest
SHA256 f47651ef89a074916cf75770d2906124d5cc51d5998515d77df2f5ba1893d5f0
MD5 6fbfa145990ace844868f4a5851031ee
BLAKE2b-256 056af9bc9d780aee3d0752b9f9414619e37d7c6dd3d3cdd941db1dac85d7892f

See more details on using hashes here.

Provenance

The following attestation bundles were made for fabric_schema_drift_detective-0.1.0.tar.gz:

Publisher: workflow.yml on naveenjujaray/microsoft-fabric-schema-drift-detection

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fabric_schema_drift_detective-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for fabric_schema_drift_detective-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9d54d6673c5a5b8fb4c5c46c64f23e018070764d7d56f963abed6b019c840add
MD5 161dc7cce2527fb8baf2a724c0fad507
BLAKE2b-256 8ea2b9e08ebc123c248aadaee4fa92b85cb47d431206c257ba846f1b75349944

See more details on using hashes here.

Provenance

The following attestation bundles were made for fabric_schema_drift_detective-0.1.0-py3-none-any.whl:

Publisher: workflow.yml on naveenjujaray/microsoft-fabric-schema-drift-detection

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page