> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kaireonai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Flow Lineage

> Walk back from any target row to the IR source node + run that produced it. Reads the _kaireon_lineage JSONB column written by every target executor.

Every Flow target executor writes a `_kaireon_lineage` JSONB column on
every row it loads. The Lineage tab in the [Flow Editor
UI](/data/pipelines/flow-editor-ui) reads that column to show you which run +
source node produced any row in the table.

## What's stored

For every loaded row, the column carries:

```json theme={null}
{
  "runId": "uuid-of-pipeline-run",
  "pipelineId": "uuid-of-pipeline",
  "sourceNodeId": "src",
  "tenantId": "uuid-of-tenant"
}
```

The `tenantId` field keeps every loaded row traceable to its owning
tenant for cross-pipeline audit queries.

The column is added idempotently via `ALTER TABLE … ADD COLUMN IF NOT
EXISTS _kaireon_lineage JSONB` the first time a target writes to that
table. Pre-Phase-6 tables won't have the column until you re-run the
pipeline.

## API

```http theme={null}
GET /api/v1/lineage?schema=public&table=ds_customer&limit=100
```

**Tenant scoping:** the (`schema`, `table`) pair must match a
`data_schemas` row owned by the current tenant. `ds_*` tables only —
public-but-unmanaged tables are not exposed.

**Response shape:**

```json theme={null}
{
  "schema": { "id": "...", "name": "customer", "displayName": "Customer" },
  "table": "public.ds_customer",
  "lineageColumn": "_kaireon_lineage",
  "rows": [
    {
      "row": { "id": 1, "name": "Alice", ... },
      "lineage": { "runId": "...", "pipelineId": "...", "sourceNodeId": "src", "tenantId": "..." }
    }
  ],
  "count": 50
}
```

When the target table has no `_kaireon_lineage` column (a table that
hasn't re-loaded since lineage was added), or the `ds_*` table hasn't
been materialized yet, the response is **`200` with `rows: []`,
`count: 0`, and a `note` field** explaining the remediation (re-run the
pipeline / add fields first) — never silently empty, and never a hard
error for an empty-but-valid table.

## Walk-back path

Click a row in the Lineage tab → a panel expands showing the IR DAG
path from the source node forward to the target, broken into pills:

```
[ src ] → [ t ] → [ v ] → [ tgt ]
```

This reads the pipeline IR currently loaded in the editor. Rows written
by an older run whose source node no longer exists in that IR surface as
"Source node X not found in current IR — likely an older run from a
previous IR version".

## Limits

* Default limit: 100 rows (clamp \[1, 1000])
* Ordering: `ORDER BY ctid DESC` (most-recent insert first)
* Multi-input joins: the walk follows the first input only — full
  multi-parent visualization is a Wave 4 polish plan
