Module references
Module reference / Trace it
Lineage
- Intake
Anchor every trail in an exact source revision.
- Lens
Record cited claims, retrieval context, and policy verdicts.
- Ports
Trace model calls, tool calls, approvals, and cost.
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
Mint a key with kb:read
The lineage read and both catalog reads acceptkb:read. The lineage read additionally accepts the legacyruns:readgrant (it is an any-of guard). Addkb:writeonly if you also want to create, update, or delete documents. A workspace key (eli_sk_) carries its workspace, so noX-Workspace-Idheader is needed; a user key (eli_uk_) that spans several workspaces must send one.Install two packages, import one module
@eli-ai/clientplus@eli-ai/contractsis enough for the headless path. Add@eli-ai/reactonly if you want the rendered surfaces. Contracts are imported by capability subpath, so a Lineage integration never pulls in agent, graph, or data types.Read a document, then read its trail
createDocumentsClient(transport)exposes six methods:list,get,create,update,remove, andlineage.
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
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.
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
Prefer your own markup?
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).
/api/v1/documents/{id}/lineageBearer · kb:read or runs:readThe 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.
/api/v1/documentsBearer · kb:readLists 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
/api/v1/documents/{id}Bearer · kb:readOne 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.
/api/v1/documentsBearer · kb:writeCreates 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
/api/v1/documents/{id}Bearer · kb:writeRewrites 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
/api/v1/documents/{id}Bearer · kb:writeSoft-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.
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.
/api/v1/documents/{id}/verifyBearer · kb:write · governance:write capabilityWarrant'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
One envelope inconsistency to code against
{ 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.
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.
Feature map
Five feature areas make up the module — two on the write path that produce the trail, three on the read path that assemble and project it. Each names the concrete modules and tables that deliver it, and the same names reappear in the code map and internals below.
System architecture
The module splits cleanly into a write path that produces the trail and a read path that assembles it. Rectangles are API routes and services, cylinders are tables and the job queue; there is no model node because assembling a trail makes zero model calls. The dotted edge is the fire-and-forget enrichment handoff.
Lineage — system architecture
Every save flows through the vault, which writes the document, its body, its chunks, its revision, and its audit row together. The lineage route gates on the document ACL, then the assembler joins the owned stores with the intake, graph, and answer tables it only reads.
Downloads
Concepts
- Lineage architecture
- REST surface — src/app/api/v1/documents
- Write path — the trail producer
- Read path — the trail assembler
- Modules
- Lineage
Keywords
- Lineage route (API)
- Catalog routes — list + create (API)
- Item routes — get, update, delete (API)
- content-acl.ts — document ACL gate (service)
- store: queue
- store: table
- store: tables
- embed + extract jobs
- saveDocument + softDeleteDocument (service)
- path normalization jail (service)
- recordDocumentRevision (service)
- synchronous chunking (service)
- fire-and-forget warm-up (service)
- documentLineage (service)
- store: append-only table
- vault/index.ts
- vault/paths.ts
- vault/revisions.ts
- ingest/chunking.ts
- ingest/enrichment.ts
- lineage/index.ts
Source and generation provenance
Status: current
Generated at: 2026-08-17T18:35:03.816Z
Source hash: bdfd5ed9c898bae491854039be1fb760cb8e189cff771e2924301322ee0441b1
Metadata payload hash: b4ed0c487b1b8992a8af224a579c8a273d9b8db9c95b16bc473d357373fadaba
Canonical appearance
src/app/(docs)/docs/modules/lineage/page.tsx:132 route /docs/modules/lineage
All appearances
canonical—src/app/(docs)/docs/modules/lineage/page.tsx:132route/docs/modules/lineage
No mirrored appearances.
Generation versions
App: eli-ai 0.1.0
Mermaid: 11.16.0 · Mermaid CLI: 11.16.0
Node: v26.3.1 · Yarn: 4.17.1
Renderer config hash: 68c10966fe84406ee626034d58bfabd555df9f65f691204b7c46db24038da101
Renderer theme hash: c80287a78d80ad63d27bd5ca348b2ef9a7e2f44da289e436be6484ea28a1b033
Adapter versions: diagramGenerator=2, drawioFlowchart=1, drawioGantt=1, drawioSequence=1, drawioState=1
Full sidecar JSON: lineage-system-architecture-bdfd5ed9.json
How to read the Lineage system architecture
- Start at the write path: Catalog and item routes call the vault, which normalizes the path, writes the body, chunks synchronously, and appends the revision and audit rows in the same transaction.
- Note what is deferred: Embedding and extraction warm-up is enqueued after the save commits and never blocks or fails the response.
- Cross to the read path: The lineage route checks the document ACL first, then the assembler runs its sequential queries inside one workspace transaction.
- Distinguish owned from joined stores: documents, document_contents, document_revisions, audit_log, and chunks are written here; connector, graph, and answer tables are read-only joins that degrade to zeros when their modules are unused.
- Trust boundary
- kb:read (or the legacy runs:read grant) plus the documents:read capability gate the read, and the content ACL is applied before assembly, so restricted documents 404 rather than leak a partial trail.
- Durable state
- documents with provenance and verification columns, bodies in the vault store, append-only document_revisions and audit_log, and chunks.
Failure paths
- A tombstoned document has no live trail — the read returns 404, not an empty shell
- A vault body missing on disk yields missing: true on the document read, never an error
- Plugin lineage sections that fail or overrun are omitted with partial: true
- Enrichment queue outages delay embeddings but never block saves
Signals
- Lineage read latency (sequential query budget)
- reviewOverdue share across the catalog
- Revision totals versus the 50-item cap
- partial: true frequency (misbehaving plugins)
Where the logic lives
The code map is the module boundary in file terms. Every path is real; table names are stated in the notes because tables are defined in the schema module, not in files of their own.
Primary runtime flow
The highest-value flow is the namesake read. Message labels use the operation names from the HTTP API section above.
Lineage — primary runtime flow
getDocumentLineage gates on the document ACL first, then assembles origin, revisions, audit, derived knowledge, and downstream impact in one workspace transaction with deterministic ordering and fixed caps.
Downloads
Concepts
- Lineage runtime flow
- Modules
- Lineage
Keywords
- GET :id/lineage (API)
- API consumer (SDK or curl)
- documents + connector_sources (store)
- document_revisions + audit_log (store)
- newest 50 document.* audit events
- vault readDocument (service)
- chunks + mentions + relations (store)
- messages + semantic cache (store)
- getDocumentLineage for one document id
- documentLineage in one workspace transaction
- the assembled DocumentLineage
- document row left-joined to its connector source
- newest 50 revision summaries with the uncapped total
- citedByAnswers count and newest 50 dependent cache entries
- lineage/index.ts
- /api/v1/documents
- service
- newest 10 sync_runs when the origin is a connector
- the document is live and visible to this key
- ACL-checked read — unknown, deleted, restricted are the same 404
- chunk count, top 50 canonical concepts, grounded relation count
Source and generation provenance
Status: current
Generated at: 2026-08-17T18:34:59.423Z
Source hash: dca4f3d8c20f01ed3cdcc565ef9afe5a2b689d3e98b5484ff5534f213e8eb216
Metadata payload hash: 140400113ca35e5bba76046ac49060da97aba7b15dbb5123732bb368f7ff7b1d
Canonical appearance
src/app/(docs)/docs/modules/lineage/page.tsx:189 route /docs/modules/lineage
All appearances
canonical—src/app/(docs)/docs/modules/lineage/page.tsx:189route/docs/modules/lineage
No mirrored appearances.
Generation versions
App: eli-ai 0.1.0
Mermaid: 11.16.0 · Mermaid CLI: 11.16.0
Node: v26.3.1 · Yarn: 4.17.1
Renderer config hash: 68c10966fe84406ee626034d58bfabd555df9f65f691204b7c46db24038da101
Renderer theme hash: c80287a78d80ad63d27bd5ca348b2ef9a7e2f44da289e436be6484ea28a1b033
Adapter versions: diagramGenerator=2, drawioFlowchart=1, drawioGantt=1, drawioSequence=1, drawioState=1
Full sidecar JSON: lineage-primary-runtime-flow-dca4f3d8.json
How to read the Lineage primary runtime flow
- The ACL gate is first and absolute: An unknown id, a cross-workspace id, a soft-deleted id, and a restricted id the key cannot see are all the same 404 before any assembly runs.
- One transaction, sequential queries: All queries run in order on the single transaction connection — a pg connection is not safe for concurrent statements, and determinism is part of the contract.
- Caps keep the payload an API response: Fifty revision summaries with the true total alongside, fifty audit events, fifty concepts, ten sync runs, fifty cached answers.
- Foreign sections degrade to zeros: With no connector the origin is vault; with no graph the derived counts are zero; with no answers the downstream block is empty — never an error.
- Trust boundary
- Scope kb:read or the legacy runs:read grant, the documents:read capability for delegated keys, and the content ACL applied through the vault read before assembly.
- Durable state
- Nothing is written — the flow is a pure read over the owned tables and the joined intake, graph, and answer tables.
Failure paths
- Deleted documents return 404 — a tombstone has no live trail
- A dangling source_id (source deleted) degrades origin to a best-effort connector kind
- Plugin sections that fail or overrun are dropped with partial: true
- Cross-workspace reads are impossible under forced RLS
Signals
- Lineage read latency and its slowest sub-query
- 404 rate (stale ids in callers)
- citedByAnswers growth per document
- invalidated share among dependent cache entries
Internals and invariants
Document catalog and write path
Every write lands through the vault: the path is normalized and jailed, the id is spliced into frontmatter, the title is re-extracted from the saved content, the body is hashed, and chunks are written synchronously in the same operation. Deletes are soft for the document and hard for its chunks and graph contribution.
- Invariant — Vault-relative paths cannot escape the vault: normalization and the path jail reject traversal at the boundary, enforced in
src/server/vault/paths.ts. - Invariant — Chunks are written in the synchronous save path, never a deferred job, so chunkCount is accurate the moment a save returns, enforced through
src/server/vault/index.tsandsrc/server/ingest/chunking.ts.
Revision and audit capture
The revision recorder runs inside the caller's already-open save transaction: a metadata snapshot, a field-level diff against the previous revision, an optional content snapshot for diffing, and the actor and activity channel. The matching audit event is appended in the same transaction.
- Invariant — A revision is written atomically with the document mutation it records — never a partial history — enforced by
recordDocumentRevisioninsrc/server/vault/revisions.ts. - Invariant — Revision content snapshots are bounded: bodies over 256 KB store metadata only, and oversized diffs collapse to a coarse summary, enforced in
src/server/vault/revisions.ts.
Trail assembly
The assembler is deliberately boring: one workspace transaction, each sub-query filtered by workspace id on top of row-level security, deterministic ordering everywhere, and fixed caps. Plugin-contributed sections run after the core projection with a strict budget and can only append.
- Invariant — The lineage read performs no model, network, or file I/O and returns the same payload for the same document state, enforced in
src/server/lineage/index.ts. - Invariant — Contributed sections are purely additive under the
src/server/plugins/limits.tsbudget (three-second default, eight-second cap); a failing or overrunning plugin costs its section and sets partial, never the read, enforced insrc/server/lineage/index.ts.
Origin and governance projection
Origin classification is a left join: a null source id means vault, a markdown_upload source means upload, anything else means connector, with the newest sync runs attached. Governance reads the verification column group and computes reviewOverdue from the stored due date.
- Invariant — The ACL is checked before assembly, so unknown, cross-workspace, deleted, and restricted ids are indistinguishable 404s, enforced in
src/app/api/v1/documents/[id]/lineageviasrc/server/authz/content-acl.ts.
Derived and downstream projection
Derived knowledge rolls document mentions up to canonical heads with authority and lifecycle labels and counts live relations whose evidence cites the document. Downstream impact counts persisted assistant messages whose source registry contains the document and lists dependent semantic-cache entries with their invalidation state.
- Invariant — Derived concepts exclude deleted entities and resolve merges to canonical heads before counting, enforced by the rollup queries in
src/server/lineage/index.ts.
For a self-hosting team the safe extension points are the seams the module already exposes: append a titled section to the trail with a lineageSections plugin contribution (budgeted, additive, never load-bearing), swap the vault body store in src/server/vault/storebetween filesystem and Postgres, and tune the enrichment queue's consumers. The transactional revision capture, the ACL-before-assembly rule, and the payload caps are load-bearing and not configurable.
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 —
originbecomes real provenance:kindofconnectororupload, the source type, the external id, the source version, the last sync time, and the recent sync runs. Without it,kindisvault, every other origin field is null, andrecentSyncRunsis empty. This one genuinely needs Intake — there is no other writer of those columns. - With Warrant —
governancecarries a real tier plusreviewOverdue, and the badge inEliDocumentLineageturns amber when a review is past due. Without it, verification staysunverifiedandreviewOverdueis 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, anddownstream.impactedEntitiesbecome the real knowledge the document produced and the concepts a deprecation would touch. Without it,entitiesis empty and both counts are zero —chunkCountis still accurate, because chunking happens in the synchronous save path, not in extraction. - With Lens —
downstream.citedByAnswerscounts persisted messages whose[Sn]source registry includes this document, andcachedAnswerslists 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
DocumentLineageis populated by live-data connectors or by evals. They compose at the product level, not in this payload.
Where to go next
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
lineageSections returns a label/value table that lands in DocumentLineage.contributed, alongside (never replacing) the core projection. It runs under documents:read with a three-second default 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.