top of page

What Is Data Lineage — And Why It's Now a Legal Requirement

Writer: Vexdata
Vexdata
5 minutes ago
14 min read


For most of the past decade, data lineage was a governance aspiration — something that went on data strategy roadmaps, got demonstrated in vendor demos, and was perpetually deprioritized in favor of building the pipeline. The argument against investing in it was always the same: it's complex, it's expensive, and there's no immediate operational return.


That argument expired in 2025.


The EU AI Act's Article 10 requirements for high-risk AI systems took effect in August 2025, creating the first binding legal mandate for documented data provenance in AI development pipelines. GDPR's right-to-erasure requirements — which require organizations to identify every system a subject's data has touched — are now being enforced at scale by EU data protection authorities. SOX's data integrity requirements, always technically applicable to data pipelines feeding financial reports, are being interpreted more strictly in the post-AI audit environment. HIPAA's minimum necessary standard now gets applied to ML training datasets in healthcare, not just point-of-care access.


Data lineage — the documented record of where data comes from, how it flows through systems, and what transformations it undergoes — sits at the center of compliance with all of these frameworks.


This guide covers what lineage actually means in practice (not the marketing version), what each regulatory framework specifically requires, and how to implement functional lineage even if you don't have a dedicated lineage tool.


Who this is for: Data engineers and architects at regulated enterprises — financial services, healthcare, public sector, or any organization deploying AI systems that make decisions affecting people. If your data pipeline feeds a model that touches hiring, lending, benefits, healthcare, or law enforcement, lineage is not optional for you anymore.


What you'll come away with:

  • A precise definition of data lineage at three levels (table, column, system)

  • A breakdown of what EU AI Act Article 10, GDPR, SOX, and HIPAA specifically require

  • SQL and Python for implementing lightweight lineage without a dedicated platform

  • A framework for column-level lineage that satisfies GDPR right-to-erasure audits

  • The OpenLineage standard and when it matters


What Data Lineage Actually Means in Practice

Data lineage vendors have overcomplicated the concept. At its core, lineage answers three questions:

  1. Where did this data come from? (source systems, upstream tables, ingestion jobs)

  2. What happened to it? (transformations, filters, aggregations, joins)

  3. Where did it go? (downstream tables, reports, models, APIs)


There are three levels at which you can answer those questions, and each level serves different purposes.


Three Levels of Data Lineage


Table-level lineage tracks data flow at the table or dataset level. You know that gold.customer_revenue is built from conformed.orders and conformed.customers. This is the level most data catalogs and visualization tools operate at. It's sufficient for understanding pipeline architecture, but insufficient for regulatory compliance that involves individual data elements.


Column-level lineage tracks which source column produced which target column, through which transformations. You know that gold.customer_revenue.ltv is derived from SUM(conformed.orders.order_total) grouped by conformed.customers.customer_id. This level is required for GDPR right-to-erasure audits — you can't erase a subject's data from derived columns if you don't know which derived columns contain it.


System-level lineage tracks data flow across system boundaries: which API calls moved data from your CRM to your data lake, which message queue carried events from your application to your warehouse. This is required for EU AI Act compliance, where you need to prove that training data for a high-risk AI system came from a specific, validated source.

-- Simple table-level lineage tracking (without a dedicated tool)
CREATE TABLE data_catalog.lineage_map (
  target_table VARCHAR(255) NOT NULL,
  source_table VARCHAR(255) NOT NULL,
  transformation_type VARCHAR(50), -- 'direct', 'aggregation', 'join', 'filter'
  transformation_logic TEXT,       -- SQL or description
  pipeline_job_name VARCHAR(255),
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  data_owner VARCHAR(255),
  compliance_tags ARRAY<VARCHAR>   -- ['gdpr', 'sox', 'hipaa', 'eu_ai_act']
);

-- Insert lineage record
INSERT INTO data_catalog.lineage_map VALUES (
  'gold.customer_revenue',
  'conformed.orders',
  'aggregation',
  'SUM(order_total) grouped by customer_id, with 12-month lookback window',
  'gold_customer_revenue_daily',
  CURRENT_TIMESTAMP,
  CURRENT_TIMESTAMP,
  'data-platform@company.com',
  ARRAY['gdpr', 'sox']
);

-- Query: what feeds into this table? (one-level upstream)
SELECT source_table, transformation_type, transformation_logic, data_owner
FROM data_catalog.lineage_map
WHERE target_table = 'gold.customer_revenue'
ORDER BY source_table;

The Lineage Gap Most Teams Have

Most data teams have implicit lineage — the engineers who built the pipeline know where data comes from. The problem is:

  • It's not documented. When those engineers leave, the lineage goes with them.

  • It's not queryable. You can't run a compliance report against someone's memory.

  • It's not automated. Every schema change, new pipeline, or new source creates a new lineage gap.


Regulators don't accept "we know where the data came from" as an audit response. They need documented, queryable, timestamped records.


EU AI Act Article 10: What It Actually Requires

The EU AI Act became enforceable in stages. For high-risk AI systems — those used in employment, credit scoring, benefits administration, healthcare, education, law enforcement, and critical infrastructure — the data governance requirements under Article 10 took full effect in August 2025.

Article 10 is specifically about the training, validation, and testing datasets used in high-risk AI systems. It requires that these datasets meet documented standards for quality and provenance.


Article 10 Requirements That Directly Mandate Lineage

Article 10(2)(b): Relevant design choices Organizations must document the design choices made about data collection and preprocessing. This means you need documented lineage showing not just what data was used, but what filtering, transformation, and sampling choices were made — and why.

Article 10(2)(d): Data collection practices You must document where training data was collected from. Not just "we used our CRM data" — the specific tables, the date ranges, the collection methodology. System-level lineage satisfies this requirement.

Article 10(2)(f): Examination of possible biases You must be able to identify whether training data represents protected groups appropriately. This requires column-level lineage: you need to know which columns in your training set potentially encode protected characteristics.

Article 10(3): Dataset sufficiency Datasets must be "relevant, representative, free of errors and complete." Documenting lineage from source to training dataset is how you demonstrate this during an audit.


Related: Article 12 (Record keeping) and Article 17 (Quality management) Article 12 requires automatic logging of events throughout the AI system lifecycle. Article 17 requires a quality management system that includes data management practices. Both reference the lineage documentation required by Article 10.


What an EU AI Act Lineage Audit Record Looks Like

-- EU AI Act Article 10 training data lineage record
CREATE TABLE ai_governance.training_dataset_lineage (
  model_id VARCHAR(255) NOT NULL,
  model_version VARCHAR(50) NOT NULL,
  dataset_name VARCHAR(255) NOT NULL,
  source_system VARCHAR(255),          -- 'CRM_salesforce', 'EHR_epic', etc.
  source_table VARCHAR(255),
  source_query TEXT,                   -- Exact SQL or API call used
  date_range_start DATE,
  date_range_end DATE,
  row_count_at_collection BIGINT,
  filter_criteria TEXT,                -- Why some records were excluded
  transformation_applied TEXT,         -- Normalization, encoding, etc.
  bias_assessment_id VARCHAR(255),     -- FK to bias review record
  data_owner VARCHAR(255),
  collection_date TIMESTAMP,
  eu_ai_act_risk_category VARCHAR(50), -- 'high_risk', 'limited_risk', etc.
  compliance_reviewed_by VARCHAR(255),
  compliance_review_date DATE
);

-- Audit query: full lineage for a model version (Article 10 audit response)
SELECT 
  l.*,
  b.bias_review_result,
  b.protected_attributes_examined,
  b.bias_mitigation_applied
FROM ai_governance.training_dataset_lineage l
LEFT JOIN ai_governance.bias_assessments b USING (bias_assessment_id)
WHERE l.model_id = 'credit_scoring_v3'
  AND l.model_version = '2025.08'
ORDER BY l.dataset_name, l.source_table;

The Penalty Exposure


For high-risk AI systems, failure to comply with Article 10 data governance requirements can result in fines of up to €15 million or 3% of global annual turnover, whichever is higher. For organizations with significant AI deployments, this is not an edge-case risk.


The EU's AI Office has indicated that enforcement will focus first on high-risk systems in employment and credit contexts — exactly the systems where lineage is hardest to document because training data often comes from legacy sources with poor metadata.


SOX, HIPAA, and GDPR: What Each Framework Actually Requires


Sarbanes-Oxley (SOX) — Financial Reporting Lineage

SOX Section 302 and 404 require that financial reports can be traced back to authoritative source systems. Every number in a financial statement must have a documented, auditable data path from the originating transaction system to the reported figure.

For data teams, this means:

  • Every ETL job that feeds a financial report must have documented lineage

  • Transformations that affect financial figures (currency conversion, allocation logic, consolidation rules) must be version-controlled and auditable

  • Any change to a pipeline that feeds financial reporting must go through a change control process

-- SOX lineage audit query: trace a reported metric back to source
WITH RECURSIVE lineage_chain AS (
  -- Start from the reported metric
  SELECT 
    target_table,
    source_table,
    transformation_logic,
    pipeline_job_name,
    1 AS depth
  FROM data_catalog.lineage_map
  WHERE target_table = 'gold.financial_summary'
    AND 'sox' = ANY(compliance_tags)
  
  UNION ALL
  
  -- Recursively trace upstream
  SELECT 
    m.target_table,
    m.source_table,
    m.transformation_logic,
    m.pipeline_job_name,
    lc.depth + 1
  FROM data_catalog.lineage_map m
  JOIN lineage_chain lc ON m.target_table = lc.source_table
  WHERE lc.depth < 10  -- prevent infinite recursion
)
SELECT 
  depth,
  source_table AS data_source,
  target_table AS flows_into,
  transformation_logic,
  pipeline_job_name
FROM lineage_chain
ORDER BY depth;

HIPAA — Minimum Necessary and Access Lineage

HIPAA's minimum necessary standard (45 CFR §164.502(b)) requires that only the data necessary for a specific purpose is accessed or disclosed. Applied to ML pipelines in healthcare, this means:

  • Training datasets for clinical ML models must document why each data element was included

  • Access to PHI in training pipelines must be logged at the column level

  • De-identification transformations must be documented in lineage records


The HIPAA lineage failure mode: A data scientist pulls a dataset for model training using a broad query that includes columns not needed for the model. Without column-level lineage tracking, this over-access is invisible. With it, it's auditable.


GDPR — Right to Erasure and Lineage

GDPR Article 17 (right to erasure) is the framework where the absence of lineage most visibly creates compliance failures. When a data subject requests deletion, organizations must erase their data from every system where it exists — including derived systems.


What lineage enables: When you receive a GDPR deletion request for customer ID 12345, column-level lineage tells you exactly which derived tables, aggregations, ML features, and reports contain data derived from that customer's records.


What happens without it: You delete the record from the source system, believe the erasure is complete, and remain in violation of GDPR because the customer's data is still embedded in 14 downstream derived tables you didn't know about.

GDPR Article

Lineage Requirement

Without Lineage

Art. 5(1)(c) — Data minimisation

Can't prove data collected was necessary

Cannot demonstrate minimization in derived datasets

Art. 5(1)(d) — Accuracy

Can trace stale data to its source

Cannot identify which downstream tables hold stale data

Art. 17 — Erasure

Column-level lineage required

Cannot locate derived data for deletion

Art. 25 — Privacy by design

Lineage enables data flow documentation

Cannot demonstrate proactive data governance

Art. 30 — Records of processing

Lineage map IS the processing record

No queryable processing record exists


Implementing Lineage Without a Dedicated Tool

Dedicated lineage platforms (Alation, Atlan, DataHub, Marquez) are excellent. They're also expensive, require months to implement, and need ongoing maintenance. If you're not there yet — or if you need to demonstrate lineage now for a compliance audit — here's how to build functional lineage with what you already have.


The Lightweight Lineage Stack

You need three things: a lineage registry (a table in your warehouse), a population mechanism (code that writes to it when pipelines run), and a query interface (SQL queries that answer lineage questions).

-- Lineage registry schema (create once in your data warehouse)
CREATE TABLE data_catalog.lineage_registry (
  lineage_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  
  -- What was produced
  target_system VARCHAR(100),    -- 'snowflake', 'bigquery', 's3', etc.
  target_database VARCHAR(255),
  target_schema VARCHAR(255),
  target_table VARCHAR(255),
  target_column VARCHAR(255),    -- NULL if table-level, populated for column-level
  
  -- Where it came from
  source_system VARCHAR(100),
  source_database VARCHAR(255),
  source_schema VARCHAR(255),
  source_table VARCHAR(255),
  source_column VARCHAR(255),    -- NULL if table-level
  
  -- How it got there
  pipeline_name VARCHAR(255),
  pipeline_version VARCHAR(50),
  transformation_type VARCHAR(50), -- 'select', 'filter', 'aggregate', 'join', 'union'
  transformation_sql TEXT,
  
  -- Compliance metadata
  data_classification VARCHAR(50), -- 'pii', 'phi', 'financial', 'internal', 'public'
  compliance_frameworks ARRAY<VARCHAR>, -- ['gdpr', 'sox', 'hipaa', 'eu_ai_act']
  data_owner_email VARCHAR(255),
  
  -- When
  pipeline_run_timestamp TIMESTAMP,
  record_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Populating Lineage from dbt

If you use dbt, you already have the richest possible source for lineage: the dbt manifest. The manifest.json generated by every dbt compile contains the full dependency graph of your models, down to which columns were selected from which models.

import json
from pathlib import Path
import snowflake.connector

def extract_dbt_lineage(manifest_path: str, conn) -> list:
    """Extract table-level lineage from dbt manifest.json and write to registry."""
    
    with open(manifest_path) as f:
        manifest = json.load(f)
    
    lineage_records = []
    
    for node_id, node in manifest['nodes'].items():
        if node['resource_type'] != 'model':
            continue
            
        target_table = f"{node['database']}.{node['schema']}.{node['name']}"
        
        # Get upstream dependencies
        for dep_id in node.get('depends_on', {}).get('nodes', []):
            if dep_id not in manifest['nodes'] and dep_id not in manifest['sources']:
                continue
                
            # Resolve the source
            source_node = manifest['nodes'].get(dep_id) or manifest['sources'].get(dep_id)
            if not source_node:
                continue
                
            source_table = f"{source_node['database']}.{source_node['schema']}.{source_node['name']}"
            
            lineage_records.append({
                'target_system': 'snowflake',
                'target_table': target_table,
                'source_system': 'snowflake',
                'source_table': source_table,
                'pipeline_name': f"dbt:{node['name']}",
                'pipeline_version': manifest['metadata']['dbt_version'],
                'transformation_type': 'dbt_model',
                'transformation_sql': node.get('compiled_sql', node.get('raw_sql', '')[:2000]),
                'data_owner_email': node.get('meta', {}).get('owner', 'unknown')
            })
    
    return lineage_records

Querying Lineage for Compliance Audits

-- GDPR erasure audit: find all tables containing data derived from a source table
SELECT DISTINCT
  target_database || '.' || target_schema || '.' || target_table AS affected_table,
  target_column,
  data_classification,
  compliance_frameworks,
  data_owner_email,
  pipeline_name
FROM data_catalog.lineage_registry
WHERE source_table = 'raw.customers'  -- The table holding the subject's PII
  AND 'gdpr' = ANY(compliance_frameworks)
ORDER BY affected_table, target_column;

-- EU AI Act audit: full data provenance for a training dataset
SELECT 
  pipeline_name,
  source_system || '.' || source_table AS data_source,
  target_table AS feeds_into,
  transformation_type,
  data_classification,
  pipeline_run_timestamp
FROM data_catalog.lineage_registry
WHERE target_table LIKE 'ml_features.%'
  AND 'eu_ai_act' = ANY(compliance_frameworks)
ORDER BY pipeline_run_timestamp DESC;

Column-Level Lineage: The Standard That Actually Satisfies Regulators

Table-level lineage tells you that gold.customer_features is built from conformed.customers. Column-level lineage tells you that gold.customer_features.email_domain was derived from SPLIT_PART(conformed.customers.email, '@', 2). For GDPR erasure and EU AI Act documentation, column-level is what regulators want.



Why Column-Level Matters for GDPR Right-to-Erasure


Consider a customer record with an email column. When that customer requests erasure:

  • You delete the row from raw.customers — obviously

  • You need to delete their record from conformed.customers — clear

  • But gold.customer_segments has a column email_domain derived from their email — does that count as personal data? Under GDPR, potentially yes, if it can be re-linked to the individual

  • And ml_features.customer_features has an email_provider_category column derived from that domain — same question


Without column-level lineage, you can't answer these questions systematically for 100,000 customers. With it, you can run a single query that returns every derived column containing data derived from the customers.email field.


Building Column-Level Lineage from SQL Parsing

Parsing column-level lineage from SQL is non-trivial, but feasible for the most critical pipelines. For simpler cases — SELECT with no derived columns — you can detect it automatically.

import sqlglot
from sqlglot.lineage import lineage as sqlglot_lineage

def extract_column_lineage(sql: str, target_table: str, dialect: str = 'snowflake') -> list:
    """
    Extract column-level lineage using sqlglot.
    Returns list of {source_column, target_column, transformation} dicts.
    """
    try:
        results = []
        parsed = sqlglot.parse_one(sql, dialect=dialect)
        
        # Walk SELECT expressions
        for col_node in parsed.find_all(sqlglot.exp.Select):
            for expr in col_node.expressions:
                target_col = expr.alias or str(expr)
                
                # Find source columns this expression depends on
                source_cols = [
                    f"{ref.table}.{ref.name}" 
                    for ref in expr.find_all(sqlglot.exp.Column)
                ]
                
                transformation = 'direct' if isinstance(expr, sqlglot.exp.Column) else 'derived'
                
                results.append({
                    'target_table': target_table,
                    'target_column': target_col,
                    'source_columns': source_cols,
                    'transformation': transformation,
                    'expression': str(expr)
                })
        
        return results
        
    except Exception as e:
        # Fall back to manual documentation for complex cases
        return [{'error': str(e), 'sql_snippet': sql[:200]}]

# Example usage:
sql = """
  SELECT 
    c.customer_id,
    SPLIT_PART(c.email, '@', 2) AS email_domain,
    c.first_name || ' ' || c.last_name AS full_name,
    SUM(o.order_total) AS lifetime_value
  FROM conformed.customers c
  JOIN conformed.orders o ON c.customer_id = o.customer_id
  GROUP BY 1, 2, 3
"""

lineage = extract_column_lineage(sql, 'gold.customer_features')
for entry in lineage:
    print(f"{entry['target_column']} <- {entry['source_columns']} ({entry['transformation']})")
# Output:
# customer_id <- ['c.customer_id'] (direct)
# email_domain <- ['c.email'] (derived)
# full_name <- ['c.first_name', 'c.last_name'] (derived)
# lifetime_value <- ['o.order_total'] (derived)

The OpenLineage Standard

OpenLineage (openlineage.io) is the emerging open standard for lineage event emission. It defines a JSON schema for lineage events that can be emitted by data tools and collected in a central backend. Marquez is the open-source reference backend.


If you're running Airflow, Spark, or dbt, OpenLineage facets are available today:

# Emit an OpenLineage event from an Airflow task
from openlineage.airflow import OpenLineageListener
from openlineage.client.run import RunEvent, RunState, Run, Job, Dataset
from openlineage.client.facet import (
    SchemaDatasetFacet, SchemaField,
    DataQualityMetricsInputDatasetFacet
)

# This wires Airflow to emit lineage events automatically
# Each task creates a lineage event with inputs and outputs
# Events are sent to your configured backend (Marquez or compatible)

For teams evaluating dedicated lineage platforms, OpenLineage compatibility is the key criterion: tools that emit OpenLineage events can be swapped and the lineage data remains portable.



ML Pipeline Lineage: The Gap Most Teams Haven't Closed

Data warehouse lineage is relatively straightforward — SQL is parseable, tables have schemas, transformations are discrete. ML pipeline lineage is harder. The "transformation" from a training dataset to a model output is a non-deterministic function with millions of parameters. But that complexity is exactly why regulators require it: if you can't document what data produced a model, you can't explain why the model made a specific decision.


What ML Lineage Must Document

For EU AI Act Article 10 compliance, ML pipeline lineage needs to cover:

  1. Training data lineage — which tables, which date ranges, which filters (see earlier SQL example)

  2. Feature engineering lineage — which source columns produced which features, and what transformations were applied

  3. Model version lineage — which training dataset version produced which model version

  4. Prediction lineage — which model version produced a specific prediction, on which input data

import mlflow
from datetime import datetime, timezone
from typing import Any

def log_training_lineage(
    model_name: str,
    training_table: str,
    feature_columns: list[str],
    filter_criteria: dict,
    row_count: int,
    date_range: tuple[str, str],
    framework_tags: list[str] = None
) -> str:
    """Log ML training lineage using MLflow tracking."""
    
    with mlflow.start_run() as run:
        # Log training data provenance
        mlflow.log_params({
            'training_table': training_table,
            'date_range_start': date_range[0],
            'date_range_end': date_range[1],
            'training_row_count': row_count,
            'feature_columns': ','.join(feature_columns),
            'filter_criteria': str(filter_criteria)
        })
        
        # Log compliance tags
        if framework_tags:
            mlflow.set_tags({
                f'compliance.{tag}': 'true' for tag in framework_tags
            })
        
        mlflow.set_tag('lineage.documented_at', 
                       datetime.now(timezone.utc).isoformat())
        mlflow.set_tag('lineage.model_name', model_name)
        
        run_id = run.info.run_id
    
    return run_id

# Usage:
run_id = log_training_lineage(
    model_name='credit_risk_v4',
    training_table='ml_features.credit_applicants_features',
    feature_columns=['age_bucket', 'income_decile', 'months_employed', 
                     'num_credit_accounts', 'payment_history_score'],
    filter_criteria={'application_date': '2023-01-01 to 2024-12-31', 
                     'status': 'approved_or_denied',
                     'excluded': 'incomplete_applications'},
    row_count=847293,
    date_range=('2023-01-01', '2024-12-31'),
    framework_tags=['eu_ai_act', 'gdpr']
)

Prediction Lineage: Tracing a Decision to Its Source

When a model makes a decision affecting an individual — a loan denial, a benefit eligibility determination, a clinical flag — EU AI Act Article 13 requires that the decision be explainable. Prediction lineage is what makes that possible.

-- Create prediction audit log (required for high-risk AI systems)
CREATE TABLE ai_governance.prediction_audit_log (
  prediction_id VARCHAR(255) PRIMARY KEY,
  model_id VARCHAR(255) NOT NULL,
  model_version VARCHAR(50) NOT NULL,
  mlflow_run_id VARCHAR(255),           -- Links back to training lineage
  subject_id VARCHAR(255),              -- The individual affected
  prediction_timestamp TIMESTAMP,
  input_features VARIANT,               -- JSON snapshot of input data
  prediction_output VARIANT,            -- The decision/score
  feature_importance VARIANT,           -- Explainability output
  data_sources_used ARRAY<VARCHAR>,     -- Which tables contributed
  compliance_frameworks ARRAY<VARCHAR>
);

-- Query: full lineage for a specific prediction (for an audit response)
SELECT 
  p.prediction_id,
  p.subject_id,
  p.prediction_output,
  p.prediction_timestamp,
  p.model_version,
  ml.training_table,
  ml.date_range_start,
  ml.date_range_end,
  ml.training_row_count,
  p.feature_importance
FROM ai_governance.prediction_audit_log p
JOIN ai_governance.model_training_log ml 
  ON p.model_id = ml.model_id AND p.model_version = ml.model_version
WHERE p.prediction_id = 'pred_1847293'
   OR p.subject_id = 'customer_12345';  -- Find all predictions for a subject

Automated Lineage vs. Manual Documentation: What Actually Works at Scale

Every data team that's tried to maintain lineage manually eventually hits the same wall: the documentation falls behind the pipelines. Engineers add a new source table, forget to update the lineage registry, and the compliance record diverges from reality. By the time an audit happens, the documented lineage reflects an architecture from 18 months ago.


The Manual Lineage Maintenance Trap

Approach

Coverage

Maintenance Burden

Audit Reliability

Wiki/Confluence docs

Low (team knowledge)

High — humans must update

Low — always stale

Spreadsheet lineage map

Medium (designed pipelines)

High — breaks on schema change

Medium — often incomplete

Code comments in ETL

Medium (what someone remembered)

Low effort, low quality

Low — not queryable

Lineage registry table (this guide)

High (if enforced in CI)

Medium — automated population

High — queryable and timestamped

dbt lineage (manifest.json)

High (dbt models only)

Low — generated automatically

High for dbt scope

OpenLineage + Marquez

Very high (all instrumented tools)

Low — automated emission

Very high — full audit trail


What You Need for a Compliance-Grade Lineage Program


You don't need a dedicated lineage platform on day one. You need:


  1. A lineage registry table in your warehouse (SQL schema in this guide) — populated by automated pipeline runs, not by hand

  2. A dbt manifest parser that extracts table and column lineage after every dbt run and loads it into the registry

  3. A PII tagging convention — columns containing personal data tagged in the registry so erasure queries work

  4. A compliance tag taxonomy — each table tagged with which frameworks apply (gdpr, sox, hipaa, eu_ai_act)

  5. A lineage query library — pre-written SQL that answers the questions auditors ask


This stack can be built in two to three weeks. It covers 80% of what a dedicated lineage platform covers, at a fraction of the cost, and with no vendor dependency.

The remaining 20% — visual lineage graphs, impact analysis on schema changes, cross-system lineage across real-time and batch — is where dedicated platforms earn their license fees. But start with the 80%.


Vexdata Integrates Data Quality Testing with Lineage

Data lineage without data quality testing is an incomplete compliance story. You can document where data came from, but if you can't demonstrate that the data met quality standards at each step of the pipeline, lineage alone won't satisfy a rigorous audit.


Vexdata closes this gap by pairing automated data quality testing with lineage metadata:

  • Test results are tagged by compliance framework — every quality check is linked to the data elements and frameworks it validates

  • Test failures create lineage-aware alerts — you know not just that a check failed, but which downstream systems depend on that table

  • Quality gate history is audit-exportable — generate a report showing every quality check result for a given table, over a given date range, for an audit response


For public sector and healthcare organizations specifically, Vexdata's testing platform is built around the compliance requirements that govern how public data can be used — the same requirements that Massachusetts DPH and Houston Health Department rely on.


 
 
 

Comments


bottom of page