Skip to documentation

Module references

Lineage module

Lineage is the provenance-and-impact slice. It ships as one SDK entry — @eli-ai/react/documents over @eli-ai/client/documents — and one API area, /api/v1/documents. The API operation catalog files both under a single capability it labels Documents and lineage. This page is the adoption reference: what you import, what props exist, what the endpoints actually return, and which tables move. For the conceptual tour of the trail itself, read Data lineage.

What it does

Lineage answers one question about a document in a single read: where did it come from, what knowledge did it produce, and what breaks if it changes. The server assembles that from pure SQL aggregates — upstream origin and governance, an immutable revision summary, the document.* audit trail, the chunks and concepts the document derived, and the answers, cache entries, and live concepts that now depend on it. No model call, no network, no file I/O; deterministic ordering and bounded caps, so the same document yields the same payload every time.

The module also owns the document catalog itself — list, read, create, update, soft-delete — because that write path is what produces the trail: every save appends a revision row and an audit row inside the same transaction as the document update.

Use it standalone

  1. Mint a key with kb:read

    The lineage read and both catalog reads accept kb:read. The lineage read additionally accepts the legacy runs:read grant (it is an any-of guard). Add kb:write only if you also want to create, update, or delete documents. A workspace key (eli_sk_) carries its workspace, so no X-Workspace-Id header is needed; a user key (eli_uk_) that spans several workspaces must send one.
  2. Install two packages, import one module

    @eli-ai/client plus @eli-ai/contracts is enough for the headless path. Add @eli-ai/react only if you want the rendered surfaces. Contracts are imported by capability subpath, so a Lineage integration never pulls in agent, graph, or data types.
  3. Read a document, then read its trail

    createDocumentsClient(transport) exposes six methods: list, get, create, update, remove, and lineage.
minimum install (headless)bash
npm install @eli-ai/client @eli-ai/contracts
server-side: list, then tracets
import { createEliTransport } from "@eli-ai/client/core";
import { createDocumentsClient } from "@eli-ai/client/documents";
import type { DocumentLineage } from "@eli-ai/contracts/documents";

const documents = createDocumentsClient(
  createEliTransport({
    baseUrl: "https://eli.ai",
    apiKey: () => process.env.ELI_API_KEY!,
    // Only needed for eli_uk_ keys that can reach more than one workspace.
    workspaceId: process.env.ELI_WORKSPACE_ID,
  }),
);

const page = await documents.list({ limit: 20 });          // { items, total, limit, offset }
const first = page.items[0];
if (!first) throw new Error("no documents in this workspace");

const trail: DocumentLineage = await documents.lineage(first.id);

console.log(trail.origin.kind);                 // "connector" | "vault" | "upload"
console.log(trail.governance.verification);     // "unverified" | "verified" | "certified" | "deprecated"
console.log(trail.derived.chunkCount, trail.derived.relationCount);
console.log(trail.downstream.citedByAnswers, trail.downstream.impactedEntities);

What adopting Lineage does not require

No knowledge graph, no live-data connectors, no agents, no evals — none of those packages, scopes, or tables are touched by the lineage read. It also needs no AI provider or model slot configured: assembling a DocumentLineage makes zero model calls. Creating or updating a document does kick off embedding and extraction warm-up, but that is fire-and-forget — the write commits and responds whether or not enrichment succeeds.

The React package imports no Next.js router, link, image, font, or route-handler module, and EliProvider deliberately has no API-key prop — you inject a transport. Sections of the payload that belong to other modules do not fail when those modules are unused; they come back as zeros and empty arrays — see How it composes at the end of this page.

React components

@eli-ai/react/documents exports exactly two components — EliDocuments and EliDocumentLineage — plus the types EliDocumentsProps, EliDocumentLineageProps, and EliDocumentsLabels. Both are client components and both read the transport from EliProvider. Neither is re-exported from the package root, so @eli-ai/react/documents is the import path.

EliDocuments

The catalog surface. It renders a titled section containing: an optional create form (path, title, content) behind a toggle button; a filter input; a table of one row per document — title over path as a selectable button, then version, then a localized updated timestamp; and, once a row is selected, the inline lineage panel for that document. It calls GET /api/v1/documents once (query key includes limit), POST /api/v1/documents on submit, and GET /api/v1/documents/{id}/lineage for the selected row.

EliDocuments props

labelsPartial<EliDocumentsLabels>Spread over the built-in defaults, so any subset of the sixteen keys works. See the defaults below.
limitnumberSent as the limit query parameter of GET /api/v1/documents and part of the query key, so changing it refetches. Default 50.
allowCreatebooleanRenders the create toggle and inline create form. Default true. When false, the component is read-only and never needs kb:write.
initialDocumentIdstringDocument id selected on first render; its lineage panel loads immediately without a click.
onDocumentSelect(document: DocumentMeta) => voidFired when a row button is pressed, with the full DocumentMeta for that row.
onDocumentCreated(result: DocumentSaveResult) => voidFired after a successful create, with { docId, path, contentHash, title }. The component also clears the form, selects the new document, and refetches the list.
onLineageLoaded(lineage: DocumentLineage) => voidFired whenever the embedded lineage panel finishes loading, with the full payload.
classNamestringAppended after the eli-root class on the root section element.
titlestringOverrides labels.title in the section header.
descriptionstringOverrides labels.description in the section header.
loadingLabelstringShown while the document list loads. Default "Loading documents…".
errorLabelstringShown with a retry button when the document list fails. Default "Documents could not be loaded.".
onError(error: unknown) => voidCalled on list-fetch and create failures. Every prop on this component is optional.
EliDocumentsLabels — the shipped defaultsts
// packages/react/src/documents/index.tsx — DEFAULT_LABELS.
// Pass `labels` with any subset; it is spread over these.
const DEFAULT_LABELS: EliDocumentsLabels = {
  title: "Documents",
  description: "Browse source material and inspect its provenance from intake to downstream use.",
  filter: "Filter documents",
  create: "New document",
  closeCreate: "Cancel",
  path: "Path",
  documentTitle: "Title",      // create-form field AND the first table column header
  content: "Content",
  save: "Create document",
  empty: "No documents yet",
  emptyDescription: "Create a document or sync a content connector to begin.",
  lineage: "Lineage",          // default header of <EliDocumentLineage>
  origin: "Origin",            // the four KPI tile labels
  revisions: "Revisions",
  derived: "Derived knowledge",
  downstream: "Downstream impact",
};

Three behaviours worth knowing before you wire it

The filter is client-side. It matches the loaded page only, against title and path, lowercased. It is not a server search and does not page past limit.

Two column headers are hard-coded. The first header uses labels.documentTitle; the version and updated headers are literal strings and are not label-driven.

The embedded panel has its own strings. Inside EliDocuments, the lineage panel uses fixed loading and error text rather than the loadingLabel and errorLabel you pass; those two apply to the document list. Use EliDocumentLineage directly if you need to control them.

provider wiring + catalogtsx
import { createEliTransport } from "@eli-ai/client/core";
import { EliProvider } from "@eli-ai/react/provider";
import { EliDocuments } from "@eli-ai/react/documents";
import "@eli-ai/react/styles.css";

// The injected fetch is the trust boundary: point it at a same-origin route
// that attaches the Bearer key server-side. Never ship an eli_sk_ key to a browser.
const transport = createEliTransport({ fetch: sameOriginProxyFetch });

export function DocumentCatalog({ workspaceId }: { workspaceId: string }) {
  return (
    <EliProvider transport={transport} workspaceId={workspaceId}>
      <EliDocuments
        limit={100}
        allowCreate={false}
        labels={{ title: "Source material", downstream: "Blast radius" }}
        onLineageLoaded={(lineage) => {
          console.log(lineage.origin.kind, lineage.downstream.impactedEntities);
        }}
      />
    </EliProvider>
  );
}

EliDocumentLineage

The lineage panel for one document id, in its own titled section. It renders a badge row (the verification tier, tinted as a warning when governance.reviewOverdue is true; the origin kind; the document version), four KPI tiles, and up to the first six revision items as version · change kind with a timestamp. The tiles are: Origin (origin.sourceType falling back to origin.kind, plus origin.lastSyncedAt), Revisions (revisions.total), Derived knowledge (the number of derived.entities, with chunk and relation counts underneath), and Downstream impact (downstream.citedByAnswers).

EliDocumentLineage props

documentIdrequiredstringThe document whose trail is fetched. Also the query key, so changing it refetches.
labelsPartial<EliDocumentsLabels>Shares the EliDocumentsLabels type. This component reads labels.lineage (the default header) and the four KPI labels: origin, revisions, derived, downstream.
onLoaded(lineage: DocumentLineage) => voidFired once the trail resolves, with the full payload.
classNamestringAppended after the eli-root class on the root section element.
titlestringSection header. Defaults to labels.lineage, that is "Lineage".
descriptionstringSection subheading. Defaults to the fixed string "Trace origin, governance, revisions, and downstream use.".
loadingLabelstringShown while the trail loads. Default "Loading lineage…".
errorLabelstringShown with a retry button when the trail fails. Default "Lineage could not be loaded.".
onError(error: unknown) => voidCalled when the trail request fails.
emptyLabelstringAccepted by the shared surface prop type but not read by this component — it renders no empty state. Listed for completeness only.
standalone lineage paneltsx
import { EliProvider } from "@eli-ai/react/provider";
import { EliDocumentLineage } from "@eli-ai/react/documents";

export function ProvenancePanel({ docId }: { docId: string }) {
  return (
    <EliProvider transport={transport}>
      <EliDocumentLineage
        documentId={docId}
        title="Provenance"
        errorLabel="We could not load this document's trail."
        onLoaded={(lineage) => {
          if (lineage.governance.reviewOverdue) flagForReview(lineage.document.id);
        }}
      />
    </EliProvider>
  );
}

Prefer your own markup?

Both components are thin wrappers over the generic hooks. Call useEliQuery from @eli-ai/react/hooks with createDocumentsClient(context.transport).lineage(id, …) and render whatever you like — the scoped eli-* classes and the CSS export are optional.

HTTP API

Six operations, all under /api/v1/documents. The workspace is resolved from the bearer key and never appears in the URL or the body. Every failure uses the shared envelope { error, message } — plus issues on body-validation failures — so a consumer can branch on error. Codes you will actually see here: unauthorized (401), insufficient_scope and insufficient_capability (403), not_found (404), invalid_pagination, invalid_json, invalid_body, and invalid_path (400).

GET/api/v1/documents/{id}/lineageBearer · kb:read or runs:read

The module's namesake read: origin provenance, governance, immutable revision summaries, the document audit trail, derived knowledge, and downstream impact — assembled in one workspace transaction. The document ACL is checked before assembly, so an unknown id, a cross-workspace id, a soft-deleted id, and a restricted id you cannot see are all the same 404.

Example requestbash
curl -s https://eli.ai/api/v1/documents/01KY10AAAAAAAAAAAAAAAAAAAA/lineage \
  -H "Authorization: Bearer $ELI_KEY" \
  | jq '{origin: .origin.kind, revisions: .revisions.total, citedBy: .downstream.citedByAnswers}'
Responsejson
{
  "document": {
    "id": "01KY10AAAAAAAAAAAAAAAAAAAA",
    "path": "ops/order-platform-runbook.md",
    "title": "Order Platform incident runbook",
    "version": 4,
    "contentHash": "9f2b1c…",
    "createdAt": "2026-06-02T14:20:00.000Z",
    "updatedAt": "2026-07-18T09:05:00.000Z",
    "createdBy": "usr_01KY0Z9M2E5Q4RS8T0V1WX2Y3Z",
    "updatedBy": "usr_01KY0Z9M2E5Q4RS8T0V1WX2Y3Z"
  },
  "governance": {
    "verification": "certified",
    "verifiedBy": "usr_01KY0Z9M2E5Q4RS8T0V1WX2Y3Z",
    "verifiedAt": "2026-07-01T00:00:00.000Z",
    "nextVerifyAt": "2026-12-28T00:00:00.000Z",
    "reviewOverdue": false
  },
  "origin": {
    "kind": "connector",
    "sourceId": "01KY0V000CONNECTOR00000001",
    "sourceType": "confluence",
    "externalId": "CONF-88213",
    "sourceVersion": "17",
    "lastSyncedAt": "2026-07-18T09:00:00.000Z",
    "recentSyncRuns": [
      { "id": "01KY18…", "kind": "incremental", "startedAt": "2026-07-18T09:00:00.000Z", "finishedAt": "2026-07-18T09:00:12.000Z", "error": null }
    ]
  },
  "revisions": {
    "total": 4,
    "items": [
      {
        "id": "01KY17…", "version": 4, "changeKind": "update",
        "changedFields": ["contentHash", "title"],
        "hasContent": true, "activity": "api",
        "changedBy": "usr_01KY0Z9M…", "note": null,
        "createdAt": "2026-07-18T09:05:00.000Z"
      }
    ]
  },
  "audit": [
    { "event": "document.verify", "actor": "usr_01KY0Z9M…", "at": "2026-07-01T00:00:00.000Z", "meta": { "from": "verified", "to": "certified" } },
    { "event": "document.update", "actor": "usr_01KY0Z9M…", "at": "2026-07-18T09:05:00.000Z", "meta": { "path": "ops/order-platform-runbook.md" } }
  ],
  "derived": {
    "chunkCount": 12,
    "entities": [
      { "id": "01KY0ZW3…", "name": "Order Platform", "type": "system", "authority": "certified", "lifecycleStatus": "published", "mentionCount": 9 }
    ],
    "relationCount": 5
  },
  "downstream": {
    "citedByAnswers": 14,
    "cachedAnswers": [
      { "entryId": "01KY19…", "question": "how do we handle stuck orders?", "invalidated": false }
    ],
    "impactedEntities": 7
  }
}
GET/api/v1/documentsBearer · kb:read

Lists live, ACL-visible documents ordered by path ascending. total is the full count for the workspace, not the page length, so it is the value to paginate against.

Query parameters

limitinteger >= 1Page size. Default 50, capped at 200. A non-integer or out-of-range value returns 400 invalid_pagination.
offsetinteger >= 0Rows to skip. Default 0.
Example requestbash
curl -s "https://eli.ai/api/v1/documents?limit=2&offset=0" \
  -H "Authorization: Bearer $ELI_KEY"
Responsejson
{
  "items": [
    {
      "id": "01KY10AAAAAAAAAAAAAAAAAAAA",
      "path": "ops/order-platform-runbook.md",
      "title": "Order Platform incident runbook",
      "contentHash": "9f2b1c…",
      "version": 4,
      "updatedAt": "2026-07-18T09:05:00.000Z"
    }
  ],
  "total": 137,
  "limit": 2,
  "offset": 0
}
GET/api/v1/documents/{id}Bearer · kb:read

One document with its raw markdown. missing is true when the metadata row exists but the stored body is gone or unservable — content is then an empty string rather than an error.

Example requestbash
curl -s https://eli.ai/api/v1/documents/01KY10AAAAAAAAAAAAAAAAAAAA \
  -H "Authorization: Bearer $ELI_KEY"
Responsejson
{
  "id": "01KY10AAAAAAAAAAAAAAAAAAAA",
  "path": "ops/order-platform-runbook.md",
  "title": "Order Platform incident runbook",
  "contentHash": "9f2b1c…",
  "version": 4,
  "updatedAt": "2026-07-18T09:05:00.000Z",
  "content": "---\nid: 01KY10AAAAAAAAAAAAAAAAAAAA\n---\n\n# Order Platform incident runbook\n…",
  "missing": false
}
POST/api/v1/documentsBearer · kb:write

Creates a document through the vault: the body is stored, the id is spliced into frontmatter, the content is hashed and chunked, and a create revision plus a document.create audit row are written in the same transaction. Responds 201. Embedding and extraction are queued afterwards and never block the response. An unusable path returns 400 invalid_path.

Request body

pathstring (min 1)Vault-relative path. A missing extension gets .md appended. Omit it and the server derives a unique slug from the title it parses out of the content.
contentstringMarkdown body. When omitted, the server writes "# " followed by the title, or "# New note" when there is no title either.
titlestring (min 1)Only used to build the default content when content is omitted. The stored title is always re-extracted from the saved content, so it can differ from what you send.
Example requestbash
curl -L -X POST https://eli.ai/api/v1/documents \
  -H "Authorization: Bearer $ELI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"path":"ops/order-platform-runbook.md","title":"Order Platform incident runbook","content":"# Order Platform incident runbook\n\nRequeue stuck orders after 24h."}'
Responsejson
{
  "docId": "01KY10AAAAAAAAAAAAAAAAAAAA",
  "path": "ops/order-platform-runbook.md",
  "contentHash": "9f2b1c…",
  "title": "Order Platform incident runbook"
}
PATCH/api/v1/documents/{id}Bearer · kb:write

Rewrites the content in place: the version is bumped, the document is re-chunked, an update revision and a document.update audit row are appended, and dependent semantic-cache entries are invalidated. The stored path never changes here — a rename is a separate operation. Returns the same save result as create.

Request body

contentrequiredstringThe full new markdown body. This is the only accepted field.
Example requestbash
curl -L -X PATCH https://eli.ai/api/v1/documents/01KY10AAAAAAAAAAAAAAAAAAAA \
  -H "Authorization: Bearer $ELI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content":"# Order Platform incident runbook\n\nRequeue stuck orders after 12h."}'
Responsejson
{
  "docId": "01KY10AAAAAAAAAAAAAAAAAAAA",
  "path": "ops/order-platform-runbook.md",
  "contentHash": "4c81ae…",
  "title": "Order Platform incident runbook"
}
DELETE/api/v1/documents/{id}Bearer · kb:write

Soft-delete. The body moves to recoverable trash and the row is tombstoned, but chunks are hard-deleted and the document's graph contribution is purged, so deleted content is never retrievable. A terminal delete revision and a document.delete audit row are appended. Afterwards the lineage read returns 404 for this id — a tombstoned document has no live trail.

Example requestbash
curl -L -X DELETE https://eli.ai/api/v1/documents/01KY10AAAAAAAAAAAAAAAAAAAA \
  -H "Authorization: Bearer $ELI_KEY"
Responsejson
{ "deleted": true, "id": "01KY10AAAAAAAAAAAAAAAAAAAA" }

One more route sits under this path prefix but belongs to a different module. The operation catalog files verifyDocument under the governance capability, and its handler requires the governance:write capability rather than the document one. It is documented here because it is the only writer of the governance block that Lineage reads back.

POST/api/v1/documents/{id}/verifyBearer · kb:write · governance:write capability

Warrant's document verification stamp. verified and certified set verified_by, verified_at, and a re-verify due date derived from the workspace review interval; unverified and deprecated clear all three. Audited as document.verify with meta { from, to }.

Request body

verificationrequired"unverified" | "verified" | "certified" | "deprecated"The new tier. Any other value fails validation.
Example requestbash
curl -L -X POST https://eli.ai/api/v1/documents/01KY10AAAAAAAAAAAAAAAAAAAA/verify \
  -H "Authorization: Bearer $ELI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"verification":"certified"}'
Responsejson
{
  "docId": "01KY10AAAAAAAAAAAAAAAAAAAA",
  "verification": "certified",
  "verifiedBy": "usr_01KY0Z9M2E5Q4RS8T0V1WX2Y3Z",
  "verifiedAt": "2026-07-01T00:00:00.000Z",
  "nextVerifyAt": "2026-12-28T00:00:00.000Z"
}

One envelope inconsistency to code against

The verify route returns { error: "Document not found" } on 404 — a human sentence in the error field, with no message. Every other endpoint on this page returns the machine code not_found with a separate message. If you branch on error, special-case this one route.

Data elements

Every table below is a tenant table: it carries workspace_id as the leading index column and exactly one row-level-security policy comparing it to the workspace GUC, with FORCE ROW LEVEL SECURITY applied by companion migrations. The lineage assembler runs every one of its queries inside a workspace transaction and filters workspace_id explicitly in the SQL. There is no non-tenant table in this module.

TableOne row isWhat Lineage does with itOwner
documentsOne document. Identity and content fingerprint (path, title, content_hash, version); the Warrant columns (verification, verified_by, verified_at, next_verify_at); the Intake provenance columns (source_id, external_id, source_version); actors (created_by, updated_by); the ACL columns (acl_visibility, acl_principals); and the deleted_at tombstone.Read for document, governance, and most of origin. Written by create, update, delete, and verify. A row with deleted_at set is invisible to every read here.Lineage (writes) · Warrant and Intake (columns)
document_contentsThe markdown body, keyed by (workspace_id, path) rather than by document id.Written and read only when the deployment stores bodies in Postgres instead of on disk. Not read by the lineage assembler.Lineage (vault store)
document_revisionsOne immutable revision: version, change_kind (create, update, delete), a metadata snapshot, a field-level diff, an optional full content snapshot, the PROV activity channel, the actor, and a note.Read for revisions — the uncapped total plus the newest 50 summaries. changedFields is the sorted key set of diff; hasContent reports whether a snapshot exists. Content blobs never enter the payload. Appended on every save and on delete.Lineage
audit_logOne append-only event: event name, actor, target, meta, timestamp.Read filtered to events beginning with document. and targeting this document id, newest first, capped at 50. Written as document.create, document.update, and document.delete with meta { path }, and document.verify with meta { from, to }.Lineage (writes) · Warrant (verify)
chunksOne retrievable slice of a document.Read as a count only, for derived.chunkCount. Written synchronously by the save path and hard-deleted on soft-delete.Intake
connector_sourcesOne governed ingestion source (Confluence, SharePoint, Slack, markdown upload).Left-joined on documents.source_id to fill origin.sourceType and origin.lastSyncedAt. A null source_id means origin.kind is vault; a markdown_upload type means upload; anything else means connector.Intake
sync_runsOne sync attempt for a source: kind (full, incremental, prune, permissions), start, finish, error.Read for origin.recentSyncRuns — the newest 10 for that source. Empty for vault documents.Intake
mentions + entitiesA mention links a document (and usually a chunk) to an entity; an entity is a concept with an authority tier and a lifecycle status.Mentions for this document are rolled up to canonical heads to produce derived.entities (top 50 by mention count, each with authority and lifecycle) and, as a distinct count, downstream.impactedEntities. Deleted entities are excluded.Atlas
relations + relation_evidenceA relation is a typed edge between two entities; evidence is the quote and document that grounds it.Read as one count: distinct live relations whose evidence cites this document, exposed as derived.relationCount.Atlas
messagesOne persisted conversation message; assistant messages carry a jsonb sources registry.Counted with a jsonb containment match on the document id to produce downstream.citedByAnswers.Lens
semantic_cache_entries + semantic_cache_dependenciesA cached grounded answer, plus one dependency row per document it was grounded in.Entries with a dependency on this document, newest 50, become downstream.cachedAnswers — entry id, question, and whether the entry has been tombstoned.Lens

Bounded by construction

The payload is capped so it stays a safe API response: 50 revision summaries (with the true total alongside), 50 audit events, 50 derived concepts, 10 recent sync runs, and 50 cached answers. Ordering is deterministic in every case, and the queries run sequentially on one connection.

How it composes

Lineage is the module that reads everyone else's trail. Adopted alone it works and is useful — you get identity, revisions, audit, and chunk counts for every document you write — but four of its sections are genuinely filled in by other modules, and this is what they look like without them.

  • With Intake origin becomes real provenance: kind of connector or upload, the source type, the external id, the source version, the last sync time, and the recent sync runs. Without it, kind is vault, every other origin field is null, and recentSyncRuns is empty. This one genuinely needs Intake — there is no other writer of those columns.
  • With Warrant governance carries a real tier plus reviewOverdue, and the badge in EliDocumentLineage turns amber when a review is past due. Without it, verification stays unverified and reviewOverdue is always false. Note the package-level relationship too: the documents capability declares a dependency on governance, but that is a type dependency only — the verification, authority, and lifecycle unions are imported from @eli-ai/contracts/governance. The wire read needs no governance scope.
  • With Atlas derived.entities, derived.relationCount, and downstream.impactedEntities become the real knowledge the document produced and the concepts a deprecation would touch. Without it, entities is empty and both counts are zero — chunkCount is still accurate, because chunking happens in the synchronous save path, not in extraction.
  • With Lens downstream.citedByAnswers counts persisted messages whose [Sn] source registry includes this document, and cachedAnswers lists the semantic-cache entries that depend on it. Without it, both are zero and empty: nothing has cited the document yet. This is the section that most rewards adopting Lens alongside Lineage — impact analysis is only interesting once answers exist.
  • With Ports — the identical trail is exposed as the MCP tool kb_document_lineage, so an agent can trace provenance mid-run without your application brokering the call. Without it, the REST endpoint and the SDK are the surfaces.
  • Conduit and Crucible — honestly, nothing. No field of DocumentLineage is populated by live-data connectors or by evals. They compose at the product level, not in this payload.

Where to go next

The conceptual walkthrough of the trail, with the full DocumentLineage TypeScript shape and the upstream/downstream diagram, lives at Data lineage. For the package architecture behind these imports — transports, workspace resolution, and error classes — see the JavaScript and React SDK, and confirm scopes in Authentication.

Extending Lineage

A plugin can append a titled section to this trail — lineageSections returns a label/value table that lands in DocumentLineage.contributed, alongside (never replacing) the core projection. It runs under documents:read with a one-second budget; an overrun or a throw omits that section and sets partial: true rather than failing the read, so a misbehaving plugin costs you a section and never the lineage page. See the extension model for the contract and the trust boundary, and Build a plugin for a worked example.