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
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.
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 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.