# InvariantDB — Full LLM Corpus Version: 2026-07-08 Last updated: 2026-07-08 Canonical URL: https://invariantdb.com/llms-full.txt > InvariantDB is a Cypher-native, bitemporal graph engine built for AI agents that must prove what they knew, when they knew it, and why they changed their mind. Valid time and recorded time are first-class query verbs. Every belief carries lineage. Erasure produces a cryptographic receipt. The public API surface is Cypher over HTTP plus a native Model Context Protocol endpoint that exposes a 14-tool catalog to any MCP-compatible client (Claude Desktop, Claude Code, Cursor, custom agents). This file is the single-document corpus intended for retrieval-augmented agents, evaluation harnesses, and one-shot ingestion. Every procedure signature, MCP tool schema, and REST endpoint documented here is verified against the running binary — no fabricated columns, no phantom endpoints. If you are a human, prefer the individual pages linked from `/llms.txt`. Contents: 1. What InvariantDB is (capability level) 2. Cypher surface (public clauses + bitemporal verbs) 3. Procedure catalog (every `CALL` procedure registered in this binary) 4. MCP tool catalog (all 14 tools with input schemas + examples) 5. Public REST endpoints 6. Bitemporal query surface (`AT VALID` + `AT RECORDED` from the query author's perspective) 7. Belief revision flow (as a query pattern) 8. Crypto-shred flow (receipt structure) 9. Authentication (Bearer / API key / Cognito JWT) 10. What not to do (common Cypher mistakes) --- ## 1. What InvariantDB is InvariantDB answers three questions about any AI decision: 1. **What did it know?** Every property, every edge, every belief is versioned. You can reconstruct exactly what the system saw at any past instant. 2. **When did it know it?** Two clocks. `AT VALID` asks what was true in the world at time T. `AT RECORDED` asks what the system believed at time T. They are independent — a fact can be valid from Monday but only recorded on Wednesday. 3. **Why did it change its mind?** Beliefs are first-class nodes with revision chains and evidence lineage. `db.beliefLineage` walks the chain; `db.propertyProvenance` returns the actor, source, reason, and confidence for every change. Capability shorthand: - **Bitemporal**: two clocks (valid time + recorded time), both queryable, both first-class in Cypher. - **Belief revision**: `db.recordBelief` / `db.reviseBelief` / `db.beliefLineage` with evidence lineage on every row. - **Crypto-shred erasure**: GDPR Article 17 erasure that yields a cryptographic receipt anchored to the audit chain. - **MCP-native**: JSON-RPC 2.0 (`protocolVersion 2024-11-05`) at `POST /graphs/{name}/mcp` and `POST /mcp`, 14-tool catalog, no local binary required. - **Cypher-native**: the query language is Cypher plus bitemporal verbs. No proprietary DSL. - **Hybrid recall**: BM25 fulltext + vector search + graph traversal in one query, via `gds.fulltextSearch`, `gds.vectorSearch`, and `gds.semanticMatchExpand`. The rest of this document is the machine-readable surface. Every procedure signature below is the source of truth returned by `CALL db.listProcedures()` against this binary — no hand-written drift. --- ## 2. Cypher surface InvariantDB implements a Cypher dialect with the standard read/write clauses plus two bitemporal extensions. ### Read clauses - `MATCH (n:Label)` — pattern match a node by label - `MATCH (n)-[e:TYPE]->(m)` — pattern match a relationship - `WHERE expr` — filter (standard operators: `=`, `<>`, `<`, `<=`, `>`, `>=`, `IN`, `IS NULL`, `IS NOT NULL`, `AND`, `OR`, `NOT`, `STARTS WITH`, `ENDS WITH`, `CONTAINS`) - `RETURN expr [AS alias]` — project columns - `WITH expr [AS alias]` — chain query stages - `ORDER BY expr [ASC|DESC]` — sort - `SKIP n` / `LIMIT n` — pagination - `UNWIND list AS x` — expand a list into rows - `OPTIONAL MATCH` — left-outer match - `UNION` / `UNION ALL` — combine query results ### Write clauses - `CREATE (n:Label {prop: value})` — create a node - `CREATE (a)-[:TYPE {prop: value}]->(b)` — create a relationship - `MERGE (n:Label {key: value})` — get-or-create with match-first semantics - `SET n.prop = value` — set property - `SET n:NewLabel` — add label - `REMOVE n.prop` — remove property - `REMOVE n:Label` — remove label - `DELETE n` — delete node (must be disconnected) - `DETACH DELETE n` — delete node and all connected edges ### Bitemporal clauses (InvariantDB-specific) - `MATCH (n:Label) AT VALID '2026-01-15T00:00:00Z'` — read the graph as of a valid-time instant. - `MATCH (n:Label) AT RECORDED '2026-01-15T00:00:00Z'` — read the graph as of a recorded-time instant (what the system believed at that moment). - `MATCH (n:Label) AT VALID $vt AT RECORDED $rt` — both clocks in one query. - Bitemporal clauses attach to the `MATCH` pattern, not to `WITH`. This is a common mistake — see §10. ### Procedure calls - `CALL db.procName(arg1, arg2) YIELD col1, col2 RETURN col1, col2` - `CALL gds.procName(...) YIELD ...` Every procedure is enumerated in §3. Every `YIELD` column list in this document matches the running binary — if you `YIELD` a column not in the signature, the engine returns `UNKNOWN_YIELD_COLUMN`. ### Aggregations `count()`, `sum()`, `avg()`, `min()`, `max()`, `collect()` are supported in `RETURN` and `WITH`. ### Parameters `$name` in the query body is substituted from the `parameters` object supplied with the request (Cypher HTTP body or MCP `cypher_query` tool argument). --- ## 3. Procedure catalog Every procedure below is registered in this binary. The `Signature` line is the exact form from the engine's self-describing table (`CALL db.listProcedures()`). If you use a YIELD column not listed here, the query fails with `UNKNOWN_YIELD_COLUMN`. ### gds.shortestPath - Signature: `(source, target) :: (path, length)` - Purpose: BFS shortest path between two nodes. Arguments may be a string id or `id(n)` numeric id. - Example: ```cypher CALL gds.shortestPath('user-42', 'user-77') YIELD path, length RETURN length, path ``` - Common errors: `PROCEDURE_NOT_FOUND` if you misspell; `NODE_NOT_FOUND` if either id doesn't exist. ### gds.pageRank - Signature: `(damping = 0.85) :: (nodeId, score)` - Purpose: PageRank over the currently-visible graph. Damping factor optional; defaults to 0.85. - Example: ```cypher CALL gds.pageRank(0.85) YIELD nodeId, score RETURN nodeId, score ORDER BY score DESC LIMIT 20 ``` ### gds.connectedComponents - Signature: `() :: (nodeId, componentId)` - Purpose: Weakly-connected components. One row per node. - Example: ```cypher CALL gds.connectedComponents() YIELD nodeId, componentId RETURN componentId, count(nodeId) AS size ORDER BY size DESC ``` ### gds.degreeCentrality - Signature: `() :: (nodeId, inDegree, outDegree)` - Purpose: Per-node in-degree and out-degree. - Example: ```cypher CALL gds.degreeCentrality() YIELD nodeId, inDegree, outDegree RETURN nodeId, inDegree + outDegree AS totalDegree ORDER BY totalDegree DESC LIMIT 10 ``` ### gds.fulltextSearch - Signature: `(label, prop, query [, k]) :: (nodeId, score, node, strId)` - Purpose: BM25 fulltext search over a (label, property) pair. Returns top-k hits with the score, the full node record, and its string id. - Example: ```cypher CALL gds.fulltextSearch('Document', 'body', 'quarterly earnings', 20) YIELD nodeId, score, node, strId RETURN strId, score, node.title ORDER BY score DESC ``` - Common errors: `INDEX_NOT_FOUND` if no fulltext index exists for that (label, property). Fulltext indexes are auto-built on first query and cached; subsequent calls are fast. ### gds.vectorSearch - Signature: `(label, prop, query [, k]) :: (nodeId, score)` - Purpose: Vector similarity search over a (label, property) embedding. Supply the query vector as an array of floats. - Example: ```cypher CALL gds.vectorSearch('Memory', 'embedding', $queryVector, 10) YIELD nodeId, score MATCH (m:Memory) WHERE id(m) = nodeId RETURN m.text, score ORDER BY score DESC ``` - Common errors: `DIM_MISMATCH` if the query vector's dimension differs from the indexed dim; `INDEX_NOT_FOUND` if no vector index exists — call `db.buildVectorIndex(label, prop)` first. ### gds.semanticMatchExpand - Signature: `(label, prop, query, hops [, k]) :: (nodeId, hops, score)` - Purpose: Vector-search seed nodes, then BFS expand by up to `hops` hops. Combines semantic recall with graph traversal in one call. - Example: ```cypher CALL gds.semanticMatchExpand('Memory', 'embedding', $queryVector, 2, 5) YIELD nodeId, hops, score MATCH (n) WHERE id(n) = nodeId RETURN n, hops, score ORDER BY score DESC ``` ### db.embeddingModelDrift - Signature: `() :: (label, prop, lockedModel, currentDefault, driftKind, builtAt, vectorCount)` - Purpose: Per-(label, property) vector-index drift check versus the graph's current default embedding model. Detects when an index was built with a model that has since changed. - Example: ```cypher CALL db.embeddingModelDrift() YIELD label, prop, lockedModel, currentDefault, driftKind WHERE driftKind <> 'in_sync' RETURN label, prop, lockedModel, currentDefault, driftKind ``` ### db.recordEpisode - Signature: `(sessionId, kind, text [, payload]) :: (eventId, ts)` - Purpose: Append an Event to a Session (episodic memory). `kind` is a free-form string; conventional values are `observation`, `thought`, `toolCall`, `decision`. - Example: ```cypher CALL db.recordEpisode( 'session-2026-07-08-agent-42', 'observation', 'User asked about their loan status.', {source: 'chat', ts: timestamp()} ) YIELD eventId, ts RETURN eventId, ts ``` ### db.replayEpisodes - Signature: `(sessionId [, limit]) :: (eventId, kind, ts, text, payload)` - Purpose: Replay a session's events in time order. Optional limit; default returns the full session. - Example: ```cypher CALL db.replayEpisodes('session-2026-07-08-agent-42', 100) YIELD eventId, kind, ts, text, payload RETURN kind, ts, text ORDER BY ts ``` ### db.consolidateSession - Signature: `(sessionId, summary [, eventIds]) :: (summaryId, eventsCovered)` - Purpose: Create a Summary node plus `SUMMARIZES` edges to the covered events. Events are **not deleted** — the summary is additive. - Example: ```cypher CALL db.consolidateSession( 'session-2026-07-08-agent-42', 'Discussed loan approval workflow; user provided ID.' ) YIELD summaryId, eventsCovered RETURN summaryId, eventsCovered ``` ### db.sessionSummaries - Signature: `(sessionId) :: (summaryId, summary, eventsCovered, createdAt)` - Purpose: List every summary produced for a session. - Example: ```cypher CALL db.sessionSummaries('session-2026-07-08-agent-42') YIELD summaryId, summary, eventsCovered, createdAt RETURN summary, eventsCovered ORDER BY createdAt ``` ### db.applySalienceDecay - Signature: `(label, halfLifeSeconds [, floor]) :: (label, decayed)` - Purpose: Halve the `salience` property every `halfLifeSeconds` since `last_accessed_at`. Optional `floor` clamps decay so memories never fully fade. - Example: ```cypher CALL db.applySalienceDecay('Memory', 86400, 0.05) YIELD label, decayed RETURN label, decayed ``` ### db.reinforceSalience - Signature: `(strId [, amount]) :: (strId, salience)` - Purpose: Bump a node's salience and refresh `last_accessed_at`. Use when an agent revisits a memory. - Example: ```cypher CALL db.reinforceSalience('mem-9f3b', 0.1) YIELD strId, salience RETURN strId, salience ``` ### db.recordBelief - Signature: `(beliefId, claim, confidence [, evidenceIds]) :: (beliefId)` - Purpose: Record an initial belief with supporting evidence. `evidenceIds` is an optional list of node string ids that support the belief. - Example: ```cypher CALL db.recordBelief( 'belief-agent-42-loan-c77-risk', 'customer:c-77 risk = LOW', 0.82, ['doc-credit-report-1', 'doc-tax-return-2'] ) YIELD beliefId RETURN beliefId ``` ### db.reviseBelief - Signature: `(beliefId, newClaim, newConfidence, reason) :: (oldBeliefId, newBeliefId, revisedAt)` - Purpose: Create a successor Belief with a `REVISES` edge back to the original. Returns the pre- and post-revision ids plus the revision timestamp. The original belief is preserved — nothing is overwritten. - Example: ```cypher CALL db.reviseBelief( 'belief-agent-42-loan-c77-risk', 'customer:c-77 risk = HIGH', 0.91, 'New evidence: fraud flag on tax return' ) YIELD oldBeliefId, newBeliefId, revisedAt RETURN oldBeliefId, newBeliefId, revisedAt ``` - Common errors: If you `YIELD successorBeliefId` (an older, drifted column name), you get `UNKNOWN_YIELD_COLUMN`. Use `newBeliefId`. ### db.beliefLineage - Signature: `(beliefId [, hops]) :: (beliefId, claim, confidence, createdAt, supersededAt, depth, evidence)` - Purpose: Walk the belief-revision chain **backward** from `beliefId` (which can be either the head or any historical version). Optional `hops` in the range 1..=3 enriches each row with a `DERIVED_FROM` evidence list capped at 50 entries per row. When capped, the row carries an `EVIDENCE_TRUNCATED` warning. - Example: ```cypher CALL db.beliefLineage('belief-agent-42-loan-c77-risk', 2) YIELD beliefId, claim, confidence, createdAt, supersededAt, depth, evidence RETURN beliefId, claim, confidence, depth, evidence ORDER BY depth ``` - Common errors: Older SDKs may attempt `YIELD version, content, derivedFrom, validFrom, validTo` — that shape drifted and now raises `UNKNOWN_YIELD_COLUMN`. Use the columns above. ### db.linkDerivedFrom - Signature: `(nodeStrId, sourceStrIds [, kind]) :: (edgesAdded)` - Purpose: Stamp `DERIVED_FROM` edges from a node to its supporting sources. `kind` labels the derivation ("summary_of", "translated_from", "inferred_from", etc.). - Example: ```cypher CALL db.linkDerivedFrom( 'summary-2026-07-08-a', ['doc-1', 'doc-2', 'doc-3'], 'summary_of' ) YIELD edgesAdded RETURN edgesAdded ``` ### db.derivedFrom - Signature: `(nodeStrId [, maxDepth]) :: (strId, depth, kind)` - Purpose: Walk `DERIVED_FROM` edges forward to enumerate the evidence chain. Use for RAG citation trails, inference trees, and document lineage. - Example: ```cypher CALL db.derivedFrom('summary-2026-07-08-a', 5) YIELD strId, depth, kind RETURN strId, depth, kind ORDER BY depth ``` ### db.destroySubjectKey - Signature: `(subjectId) :: (subjectId, destroyedAtNanos)` - Purpose: Crypto-shred a subject. Destroys the KeyProvider entry for the subject; every ciphertext encrypted with that key becomes permanently unrecoverable. This is the GDPR Article 17 primitive. - Example: ```cypher CALL db.destroySubjectKey('subject:u-4429') YIELD subjectId, destroyedAtNanos RETURN subjectId, destroyedAtNanos ``` - Common errors: `SUBJECT_NOT_FOUND` if the subject was never registered as a key holder. ### db.proveErasure - Signature: `(subjectId) :: (receiptId, destroyedAt, anchorHistorySeq, anchorHistoryHash, ownChainHash, chainStatus)` - Purpose: Replay every erasure receipt for a subject and verify the audit chain. `chainStatus` is one of `verified`, `broken`, or `missing`. - Example: ```cypher CALL db.proveErasure('subject:u-4429') YIELD receiptId, destroyedAt, anchorHistorySeq, anchorHistoryHash, ownChainHash, chainStatus RETURN receiptId, destroyedAt, chainStatus ``` ### db.verifyAclChain - Signature: `() :: (status, entries, brokenAtEventId, reason)` - Purpose: Tamper-evidence check on the ACL audit log. Returns one row: `status` is `ok` or `broken`; `entries` is the count checked; if broken, `brokenAtEventId` and `reason` locate the tamper. - Example: ```cypher CALL db.verifyAclChain() YIELD status, entries, brokenAtEventId, reason RETURN status, entries, brokenAtEventId, reason ``` ### db.subjectExport - Signature: `(subjectStrId) :: (type, id, kind, payload)` - Purpose: GDPR Article 20 per-subject data export. Returns every node, every edge, and the full property history touching the subject. `type` is `node`, `edge`, or `history`. - Example: ```cypher CALL db.subjectExport('subject:u-4429') YIELD type, id, kind, payload RETURN type, id, kind, payload ``` ### db.aclEvents - Signature: `([sinceId [, principalFilter [, limit]]]) :: (eventId, ts, principal, label, property, action, denied, reason)` - Purpose: Replay ACL audit events — who tried to read what, when, and whether the engine denied it. - Example: ```cypher CALL db.aclEvents(0, 'agent-42', 100) YIELD eventId, ts, principal, label, property, action, denied, reason WHERE denied = true RETURN ts, label, property, reason ORDER BY ts DESC ``` ### db.changes - Signature: `(from_ts_nanos, to_ts_nanos) :: (kind, op, entity_id, label, txn_at, applied_seq)` - Purpose: Every mutation in a bitemporal window. `kind` is `node` or `edge`; `op` is `insert`, `update`, or `delete`. - Example: ```cypher CALL db.changes(1728345600000000000, 1728432000000000000) YIELD kind, op, entity_id, label, txn_at, applied_seq RETURN kind, op, label, count(*) AS n ORDER BY n DESC ``` ### db.changesWithStrId - Signature: `(from_ts_nanos, to_ts_nanos) :: (kind, op, entity_id, str_id, label, txn_at, applied_seq)` - Purpose: `db.changes` plus the resolved string id for each entity. - Example: ```cypher CALL db.changesWithStrId(1728345600000000000, 1728432000000000000) YIELD kind, op, str_id, label, txn_at RETURN kind, op, str_id, label, txn_at ORDER BY txn_at ``` ### db.delta - Signature: `(from_ts_nanos, to_ts_nanos) :: (kind, entity_id, first_op, last_op)` - Purpose: Per-entity summary of changes in a window — one row per entity showing the first and last operation seen. - Example: ```cypher CALL db.delta(1728345600000000000, 1728432000000000000) YIELD kind, entity_id, first_op, last_op WHERE first_op = 'insert' AND last_op = 'delete' RETURN kind, entity_id ``` ### db.changeRate - Signature: `(window_seconds) :: (mutationsPerSecond)` - Purpose: Mutations per second over the last `window_seconds`. - Example: ```cypher CALL db.changeRate(60) YIELD mutationsPerSecond RETURN mutationsPerSecond ``` ### db.propertyHistory - Signature: `(nodeId, propName) :: (value, validFrom, validTo, version, txnAt)` - Purpose: Per-property version history for a node. Every value the property has ever held, plus its valid-time window and the transaction that recorded it. - Example: ```cypher MATCH (u:User {strId: 'u-4429'}) WITH id(u) AS nodeId CALL db.propertyHistory(nodeId, 'risk') YIELD value, validFrom, validTo, version, txnAt RETURN value, validFrom, validTo, version, txnAt ORDER BY version ``` ### db.history - Signature: `(nodeId) :: (version, timestamp, properties, labels, validFrom, validTo)` - Purpose: Full per-version record dump for one node. - Example: ```cypher MATCH (u:User {strId: 'u-4429'}) WITH id(u) AS nodeId CALL db.history(nodeId) YIELD version, timestamp, properties, labels, validFrom, validTo RETURN version, timestamp, properties ORDER BY version ``` ### db.propertyProvenance - Signature: `(nodeId, propName) :: (value, validFrom, validTo, version, txnAt, actor, source, reason, requestId, confidence)` - Purpose: `db.propertyHistory` plus provenance for every change. Answers "who set this value, from what source, why, in which request, at what confidence?" - Example: ```cypher MATCH (u:User {strId: 'u-4429'}) WITH id(u) AS nodeId CALL db.propertyProvenance(nodeId, 'risk') YIELD value, validFrom, validTo, actor, source, reason, confidence RETURN value, actor, source, reason, confidence ORDER BY validFrom ``` ### db.buildVectorIndex - Signature: `(label, prop [, metric]) :: (vectorsIndexed, dim, backend)` - Purpose: Build and cache the vector searcher for a (label, property). `metric` defaults to `cosine`; other values: `euclidean`, `dot`. - Example: ```cypher CALL db.buildVectorIndex('Memory', 'embedding', 'cosine') YIELD vectorsIndexed, dim, backend RETURN vectorsIndexed, dim, backend ``` ### db.dropVectorIndex - Signature: `(label, prop [, metric]) :: (label, property, searchersDropped, status)` - Purpose: Drop the cached vector searcher for a (label, property) across every metric and every snapshot. - Example: ```cypher CALL db.dropVectorIndex('Memory', 'embedding') YIELD label, property, searchersDropped, status RETURN label, property, searchersDropped, status ``` ### db.listVectorIndexes - Signature: `() :: (label, prop, model, dim, kind, builtAt, vectorCount)` - Purpose: Enumerate every vector index built on this graph. `vectorCount` is live when the searcher is warm; otherwise it reflects the count stamped at first build. Declared-but-not-yet-built indexes are not listed — the index only becomes discoverable on first successful embedding. - Example: ```cypher CALL db.listVectorIndexes() YIELD label, prop, model, dim, kind, builtAt, vectorCount RETURN label, prop, model, dim, vectorCount ``` ### db.buildBitemporalLabelIndex - Signature: `(label) :: (label, entries, bytesWritten)` - Purpose: Build the bitemporal label index so `AT VALID` queries against that label become O(log H + matches) instead of scanning history. - Example: ```cypher CALL db.buildBitemporalLabelIndex('Customer') YIELD label, entries, bytesWritten RETURN label, entries, bytesWritten ``` ### db.createCoveringIndex - Signature: `(label, key, coveredProps) :: (label, key, status)` - Purpose: Declare a covering index. Materialised on the next background maintenance pass. - Example: ```cypher CALL db.createCoveringIndex('Order', 'customerId', ['status', 'amount', 'placedAt']) YIELD label, key, status RETURN label, key, status ``` ### db.dropCoveringIndex - Signature: `(label, key) :: (label, key, status)` - Purpose: Drop a covering index. - Example: ```cypher CALL db.dropCoveringIndex('Order', 'customerId') YIELD label, key, status RETURN label, key, status ``` ### db.createSpatialIndex - Signature: `(label, x_prop, y_prop, kind) :: (label, x_prop, y_prop, kind, status)` - Purpose: Declare a 2D spatial index. `kind` is `geographic` (haversine distance) or `cartesian` (Euclidean). - Example: ```cypher CALL db.createSpatialIndex('Place', 'lng', 'lat', 'geographic') YIELD label, x_prop, y_prop, kind, status RETURN status ``` ### db.dropSpatialIndex - Signature: `(label, x_prop, y_prop) :: (label, x_prop, y_prop, status)` - Purpose: Drop a spatial index. - Example: ```cypher CALL db.dropSpatialIndex('Place', 'lng', 'lat') YIELD status RETURN status ``` ### db.spatialIndexes - Signature: `() :: (label, x_prop, y_prop, kind)` - Purpose: List declared spatial-index catalog entries. - Example: ```cypher CALL db.spatialIndexes() YIELD label, x_prop, y_prop, kind RETURN label, x_prop, y_prop, kind ``` ### db.buildSpatialIndex - Signature: `(label, x_prop, y_prop) :: (label, x_prop, y_prop, bytes_written)` - Purpose: Unconditional rebuild of a single spatial index. Use when source data has changed faster than the automatic rebuild can keep up. - Example: ```cypher CALL db.buildSpatialIndex('Place', 'lng', 'lat') YIELD bytes_written RETURN bytes_written ``` ### db.spatialWithinBbox - Signature: `(label, x_prop, y_prop, minX, minY, maxX, maxY) :: (nodeId, strId, x, y)` - Purpose: Spatial bounding-box window query. - Example: ```cypher CALL db.spatialWithinBbox('Place', 'lng', 'lat', -122.5, 37.7, -122.3, 37.9) YIELD nodeId, strId, x, y RETURN strId, x, y LIMIT 100 ``` - Common errors: `INDEX_NOT_FOUND` if no spatial index is declared for that (label, x_prop, y_prop) — call `db.createSpatialIndex` first. ### db.spatialWithinDistance - Signature: `(label, x_prop, y_prop, centerX, centerY, distance) :: (nodeId, strId, x, y, distance)` - Purpose: Spatial radius query. Distance is haversine metres for `geographic` indexes, Euclidean units for `cartesian` indexes. - Example: ```cypher CALL db.spatialWithinDistance('Place', 'lng', 'lat', -122.4194, 37.7749, 5000) YIELD strId, x, y, distance RETURN strId, distance ORDER BY distance ``` ### db.dpBudget - Signature: `([principal]) :: (principal, epsilonSpent, asOfNanos)` - Purpose: Differential-privacy per-principal consumed-epsilon snapshots. Argumentless form returns every principal; with argument, only the named one. - Example: ```cypher CALL db.dpBudget('analyst-14') YIELD principal, epsilonSpent, asOfNanos RETURN principal, epsilonSpent ``` ### db.dpEvents - Signature: `([principal [, from_ns [, to_ns]]]) :: (eventId, ts, principal, query, epsilon, true_value, noisy_value)` - Purpose: Read the differential-privacy audit log. `true_value` is redacted for non-root principals. - Example: ```cypher CALL db.dpEvents('analyst-14') YIELD eventId, ts, query, epsilon, noisy_value RETURN ts, query, epsilon, noisy_value ORDER BY ts DESC LIMIT 50 ``` ### db.dpVerifyChain - Signature: `() :: (status, entries, brokenAtEventId, reason)` - Purpose: Hash-chain verification of the differential-privacy audit log. Same shape as `db.verifyAclChain`. - Example: ```cypher CALL db.dpVerifyChain() YIELD status, entries, brokenAtEventId, reason RETURN status, entries ``` ### db.compliance.checklist - Signature: `() :: (control, status, evidence)` - Purpose: Per-control compliance posture — audit chain, WORM mode, erasure, ACL chain, DP chain, and every other control the engine tracks. One row per control. - Example: ```cypher CALL db.compliance.checklist() YIELD control, status, evidence RETURN control, status, evidence ``` ### db.recordDecision - Signature: `(actor, decisionLabel, inputsSeen, chosen [, rationale [, metadataJson]]) :: (decisionId, ts, inputsSeenCount)` - Purpose: Record an actor's decision with the inputs they saw and the action they chose. Underpins reproducible decision reconstruction. - Example: ```cypher CALL db.recordDecision( 'agent-42', 'loan_approval', ['customer:c-77', 'credit_report:cr-9', 'belief:belief-agent-42-loan-c77-risk'], 'approve', 'risk = LOW at 0.82 confidence' ) YIELD decisionId, ts, inputsSeenCount RETURN decisionId, ts, inputsSeenCount ``` ### db.exportDecisionTrail - Signature: `(actor [, sinceNs [, untilNs]]) :: (decisionId, ts, decisionLabel, chosen, rationale, inputsSeen)` - Purpose: Export an actor's decision trail in a time window. - Example: ```cypher CALL db.exportDecisionTrail('agent-42') YIELD decisionId, ts, decisionLabel, chosen, rationale RETURN ts, decisionLabel, chosen, rationale ORDER BY ts DESC ``` ### db.materializeTrainingSet - Signature: `(decisionIds) :: (decisionId, recordedAtNs, snapshotSeq, snapshotJson, decisionLabel, chosen, rationale)` - Purpose: For each decision id, reconstruct the database state `AT RECORDED` that decision's timestamp. No future information leaks in — the returned snapshot is exactly what the agent saw. Suitable for offline training-set assembly. - Example: ```cypher CALL db.materializeTrainingSet(['dec-1', 'dec-2', 'dec-3']) YIELD decisionId, recordedAtNs, decisionLabel, chosen, snapshotJson RETURN decisionId, decisionLabel, chosen ``` ### db.registerAgent - Signature: `(name, cypher, action) :: (agentId, name, enabled, createdAt)` - Purpose: Register a declarative agent trigger. `cypher` is the guard query; `action` names the effect procedure to invoke when the guard matches. - Example: ```cypher CALL db.registerAgent( 'high-risk-loan-alerter', "MATCH (b:Belief {claim: 'high_risk'}) WHERE b.confidence > 0.8 RETURN b", 'send_slack_alert' ) YIELD agentId, name, enabled, createdAt RETURN agentId, name, enabled ``` ### db.listAgents - Signature: `() :: (agentId, name, enabled, createdAt)` - Purpose: List every registered agent. - Example: ```cypher CALL db.listAgents() YIELD agentId, name, enabled, createdAt RETURN name, enabled, createdAt ORDER BY createdAt ``` ### db.disableAgent - Signature: `(name) :: (agentId, disabledAt)` - Purpose: Set `enabled=false` on an agent without deleting its history. - Example: ```cypher CALL db.disableAgent('high-risk-loan-alerter') YIELD agentId, disabledAt RETURN agentId, disabledAt ``` ### db.ensureReceiptSchema - Signature: `() :: (nodesCreated, edgesCreated)` - Purpose: Idempotent bootstrap of the receipt schema (12 nodes, 13 edges). Safe to call on any graph; a no-op after the first call. - Example: ```cypher CALL db.ensureReceiptSchema() YIELD nodesCreated, edgesCreated RETURN nodesCreated, edgesCreated ``` ### db.listProcedures - Signature: `() :: (name, signature, description)` - Purpose: Enumerate the procedures registered in this binary. Use for SDK discovery and integration tests. - Example: ```cypher CALL db.listProcedures() YIELD name, signature, description RETURN name, signature, description ORDER BY name ``` ### db.relabel - Signature: `(oldLabel, newLabel [, where]) :: (oldLabel, newLabel, relabeled, skipped)` - Purpose: Move every node with `oldLabel` to `newLabel`, optionally filtered by a property-equality map. Atomic per chunk. - Example: ```cypher CALL db.relabel('Customer', 'Client', {region: 'EU'}) YIELD oldLabel, newLabel, relabeled, skipped RETURN relabeled, skipped ``` ### db.graphCounts - Signature: `() :: (nodes, edges, labels, edgeTypes, snapshotSeq, memtableDirty)` - Purpose: Live count surface. `nodes` and `edges` are **exact** (live totals). `labels` and `edgeTypes` are a **floor** — they reflect published cardinality only; when `memtableDirty=true`, a label or edge type present only in still-pending writes will not appear in the count. Callers wanting exact label/edgeType cardinality should treat the value as a floor while `memtableDirty=true`. - Example: ```cypher CALL db.graphCounts() YIELD nodes, edges, labels, edgeTypes, snapshotSeq, memtableDirty RETURN nodes, edges, labels, edgeTypes, memtableDirty ``` --- ## 4. MCP tool catalog InvariantDB advertises a native Model Context Protocol server at two mount points, both dispatching against the same catalog: - `POST /mcp` — dispatches against the default graph. - `POST /graphs/{name}/mcp` — dispatches against a named graph. Body: JSON-RPC 2.0. Accepted methods: `initialize`, `tools/list`, `tools/call`. `initialize` response advertises `protocolVersion: "2024-11-05"`, `serverInfo.name: "invariantdb-mcp"`, and `capabilities.tools: {}`. No `resources`, no `prompts`, no `sampling` — the surface is intentionally narrow. The 14 tools: ### cypher_query Run a Cypher query against the bound graph. Returns columns and rows in the engine's standard wire shape. Input schema: ```json { "type": "object", "properties": { "query": {"type": "string", "description": "Cypher query text"}, "parameters": {"type": "object", "description": "Optional parameter map; substituted for $name placeholders."} }, "required": ["query"] } ``` Example call: ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "cypher_query", "arguments": { "query": "MATCH (u:User {strId: $id}) RETURN u", "parameters": {"id": "u-4429"} } } } ``` **Warning:** `cypher_query` intentionally rejects an `atTime` argument. Bitemporal queries must use `AS OF` (or `AT VALID` / `AT RECORDED`) inside the Cypher body: ```cypher MATCH (n:Customer) AS OF '2026-06-01T00:00:00Z' RETURN n ``` ### search_fulltext BM25 fulltext search over a (label, property) pair. Returns top-k hits with score. Input schema: ```json { "type": "object", "properties": { "label": {"type": "string"}, "property": {"type": "string"}, "query": {"type": "string"}, "k": {"type": "integer", "default": 10} }, "required": ["label", "property", "query"] } ``` Example call: ```json {"name": "search_fulltext", "arguments": {"label": "Document", "property": "body", "query": "quarterly earnings", "k": 20}} ``` Example response (`structuredContent.hits`): ```json [ {"node_id": 1042, "score": 8.31}, {"node_id": 993, "score": 7.02} ] ``` ### search_vector Vector search over a (label, property) embedding. Returns top-k hits with score. Input schema: ```json { "type": "object", "properties": { "label": {"type": "string"}, "property": {"type": "string"}, "query": {"type": "array", "items": {"type": "number"}}, "k": {"type": "integer", "default": 10}, "metric": {"type": "string", "enum": ["cosine", "euclidean", "dot"], "default": "cosine"} }, "required": ["label", "property", "query"] } ``` Example call: ```json {"name": "search_vector", "arguments": {"label": "Memory", "property": "embedding", "query": [0.12, -0.44, 0.03, "..."], "k": 5}} ``` ### get_node_by_str Look up a node by its string id. Input schema: ```json { "type": "object", "properties": {"str_id": {"type": "string"}}, "required": ["str_id"] } ``` Example call: ```json {"name": "get_node_by_str", "arguments": {"str_id": "u-4429"}} ``` ### get_edge_by_str Look up an edge by its string id. Input schema: ```json { "type": "object", "properties": {"str_id": {"type": "string"}}, "required": ["str_id"] } ``` ### engine_info Return engine metadata for the bound graph. Useful for smoke-testing that the MCP session is connected to the graph you expect. Input schema: ```json {"type": "object", "properties": {}} ``` Example call: ```json {"name": "engine_info", "arguments": {}} ``` ### record_episode Record one event in an agent's session (per-agent sequential log). `kind` is a free-form string — conventional values: `observation`, `thought`, `toolCall`, `decision`. Input schema: ```json { "type": "object", "properties": { "session": {"type": "string"}, "kind": {"type": "string"}, "text": {"type": "string"}, "payload": {"type": "object", "default": {}} }, "required": ["session", "kind", "text"] } ``` Internally re-dispatches through `cypher_query` against `CALL db.recordEpisode(...)`. ### replay_episodes Replay an agent's session — last N events in time order. Input schema: ```json { "type": "object", "properties": { "session": {"type": "string"}, "limit": {"type": "integer", "default": 50} }, "required": ["session"] } ``` ### belief_lineage Walk every version of a belief and its evidence. Returns the full inference-revision history. Input schema: ```json { "type": "object", "properties": {"belief_key": {"type": "string"}}, "required": ["belief_key"] } ``` Re-dispatches through `CALL db.beliefLineage(...)`. ### property_provenance Full bitemporal version history plus provenance (actor, source, reason, confidence) for one node property. Input schema: ```json { "type": "object", "properties": { "node_id": {"type": "integer"}, "property": {"type": "string"} }, "required": ["node_id", "property"] } ``` ### derived_from Walk the `DERIVED_FROM` edges back from a node — for RAG citation chains, inference trees, and document derivation. Input schema: ```json { "type": "object", "properties": { "node_str_id": {"type": "string"}, "max_depth": {"type": "integer", "default": 5} }, "required": ["node_str_id"] } ``` ### acl_events Replay ACL audit events — who tried to read what, when, and whether the engine denied it. Input schema: ```json { "type": "object", "properties": { "since_id": {"type": "integer", "default": 0}, "principal_filter": {"type": "string"}, "limit": {"type": "integer", "default": 100} } } ``` ### subject_export GDPR Article 20 — per-subject data export. Returns every node, every edge, and every property version touching the subject. Input schema: ```json { "type": "object", "properties": {"subject": {"type": "string"}}, "required": ["subject"] } ``` ### destroy_subject_key GDPR Article 17 — crypto-shred. Destroys the subject's encryption key (irreversible). Returns the audit-chain receipt. Input schema: ```json { "type": "object", "properties": {"subject": {"type": "string"}}, "required": ["subject"] } ``` Example call: ```json {"name": "destroy_subject_key", "arguments": {"subject": "subject:u-4429"}} ``` Example structured response: ```json { "columns": ["subjectId", "destroyedAtNanos"], "rows": [["subject:u-4429", 1728432011234567890]] } ``` **Tool response wrapper.** Every tool response comes wrapped in the MCP-standard shape: ```json { "content": [{"type": "text", "text": ""}], "isError": false, "structuredContent": { "...actual tool output..." } } ``` Callers should read `structuredContent` for machine-parseable results; `content[0].text` is a display-friendly rendering. --- ## 5. Public REST endpoints Every public endpoint is authenticated (see §9). All request/response bodies are JSON unless noted. ### `GET /healthz` Liveness check. Returns 200 with `{"status": "ok"}` when the engine is accepting requests. ### `POST /graphs/{name}/cypher` Run a Cypher query against a named graph. Request: ```json { "query": "MATCH (n:User {strId: $id}) RETURN n", "parameters": {"id": "u-4429"} } ``` Response: ```json { "columns": ["n"], "rows": [[{"strId": "u-4429", "email": "..."}]] } ``` ### `POST /mcp` and `POST /graphs/{name}/mcp` JSON-RPC 2.0 MCP transport. See §4 for the tool catalog. `POST /mcp` dispatches against the default graph; `POST /graphs/{name}/mcp` is scoped to a named graph. ### `GET /v1/schemas` List declared schemas for the caller's tenant. Response: ```json { "schemas": [ {"name": "customer_360", "createdAt": "2026-05-01T00:00:00Z"}, {"name": "agent_memory", "createdAt": "2026-06-14T00:00:00Z"} ] } ``` ### Errors Every error response is a JSON envelope: ```json { "error": { "code": "UNKNOWN_YIELD_COLUMN", "message": "Column `successorBeliefId` is not produced by db.reviseBelief. Yields: oldBeliefId, newBeliefId, revisedAt.", "position": {"line": 3, "column": 9}, "details": {"procedure": "db.reviseBelief"} } } ``` Common error codes: - `PROCEDURE_NOT_FOUND` — procedure name misspelled or not registered. - `UNKNOWN_YIELD_COLUMN` — YIELD column not produced by the procedure. Check §3 for the exact column list. - `ARGUMENT_TYPE_MISMATCH` — argument type does not match the signature. - `NODE_NOT_FOUND`, `EDGE_NOT_FOUND` — the referenced entity does not exist. - `INDEX_NOT_FOUND` — no index for the requested (label, prop). Build it with `db.buildVectorIndex` / `db.createSpatialIndex` etc. - `DIM_MISMATCH` — vector query dimension differs from indexed dim. - `SUBJECT_NOT_FOUND` — subject was never registered as a key holder. --- ## 6. Bitemporal query surface InvariantDB has two clocks. Both are queryable, both are first-class Cypher verbs. **Valid time** answers "what was true in the world at time T?" Example: a customer's address on the day they applied for a loan. **Recorded time** answers "what did the system believe at time T?" Example: what did the fraud model see when it approved the transaction, before the retroactive correction landed? The two clocks are independent. A fact can be: - Valid on Monday, recorded on Monday (the common case). - Valid on Monday, recorded on Wednesday (a fact you learned about later). - Valid on Monday, recorded on Monday, then superseded by a Wednesday correction that is *also* recorded on Wednesday but valid retroactively from Monday. The four bitemporal question forms: | Question | Cypher form | |---|---| | What was true then? | `MATCH (n:Customer {id: 'c-77'}) AT VALID '2026-06-01T00:00:00Z' RETURN n` | | What did we know then? | `MATCH (n:Customer {id: 'c-77'}) AT RECORDED '2026-06-01T00:00:00Z' RETURN n` | | What did we know then about how things were then? | `MATCH (n:Customer {id: 'c-77'}) AT VALID '2026-06-01T00:00:00Z' AT RECORDED '2026-06-01T00:00:00Z' RETURN n` | | What do we know now about how things were then? | `MATCH (n:Customer {id: 'c-77'}) AT VALID '2026-06-01T00:00:00Z' RETURN n` (RECORDED defaults to now) | Bitemporal clauses attach to the `MATCH` pattern. They cannot be moved to a `WITH` and cannot be applied to a `RETURN`. See §10. The equivalent shorthand `AS OF '2026-06-01T00:00:00Z'` is accepted; it binds to VALID time by default. To be explicit, use `AT VALID` or `AT RECORDED`. For per-property history without a full match, use `db.propertyHistory(nodeId, propName)` or `db.propertyProvenance(nodeId, propName)`. --- ## 7. Belief revision flow Beliefs in InvariantDB are first-class nodes with revision chains. The end-to-end pattern: **Step 1 — Record.** An agent forms an initial belief with supporting evidence. ```cypher CALL db.recordBelief( 'belief-agent-42-loan-c77-risk', 'customer:c-77 risk = LOW', 0.82, ['doc-credit-report-1', 'doc-tax-return-2'] ) YIELD beliefId RETURN beliefId ``` **Step 2 — Revise.** New evidence contradicts the belief. Revise it. The original is preserved; a successor is created with a `REVISES` edge back. ```cypher CALL db.reviseBelief( 'belief-agent-42-loan-c77-risk', 'customer:c-77 risk = HIGH', 0.91, 'New evidence: fraud flag on tax return' ) YIELD oldBeliefId, newBeliefId, revisedAt RETURN oldBeliefId, newBeliefId, revisedAt ``` **Step 3 — Trace.** An auditor asks "why did the agent change its mind?" Walk the lineage. ```cypher CALL db.beliefLineage('belief-agent-42-loan-c77-risk', 2) YIELD beliefId, claim, confidence, createdAt, supersededAt, depth, evidence RETURN beliefId, claim, confidence, depth, evidence ORDER BY depth ``` **Step 4 — Reproduce.** The auditor asks "what did the agent see at the moment it decided?" Reconstruct the recorded-time snapshot. ```cypher CALL db.materializeTrainingSet(['dec-loan-c77-2026-07-01']) YIELD decisionId, recordedAtNs, snapshotJson, decisionLabel, chosen, rationale RETURN snapshotJson, chosen, rationale ``` Every belief carries a `confidence` in [0, 1]. The revision chain is preserved forever unless explicitly erased via `db.destroySubjectKey` (see §8). --- ## 8. Crypto-shred flow GDPR Article 17 ("right to erasure") is satisfied by destroying the subject's per-tenant encryption key. Every ciphertext encrypted with that key becomes permanently unrecoverable; the surrounding graph structure remains queryable but the subject's payloads are cryptographic noise. **Step 1 — Destroy.** ```cypher CALL db.destroySubjectKey('subject:u-4429') YIELD subjectId, destroyedAtNanos RETURN subjectId, destroyedAtNanos ``` **Step 2 — Prove.** Produce a receipt suitable for regulator handoff. ```cypher CALL db.proveErasure('subject:u-4429') YIELD receiptId, destroyedAt, anchorHistorySeq, anchorHistoryHash, ownChainHash, chainStatus RETURN receiptId, destroyedAt, anchorHistorySeq, anchorHistoryHash, ownChainHash, chainStatus ``` The receipt has six fields: - `receiptId` — unique id for this receipt. - `destroyedAt` — instant of erasure (ISO 8601 with nanosecond precision). - `anchorHistorySeq` — the audit-chain sequence number this receipt anchors to. - `anchorHistoryHash` — the audit-chain hash at that anchor point. - `ownChainHash` — the hash of this receipt within the erasure chain. - `chainStatus` — `verified`, `broken`, or `missing`. Always check this — `verified` is the only acceptable answer for a regulator handoff. **GDPR Article 20 — subject export.** Before destruction, callers usually run: ```cypher CALL db.subjectExport('subject:u-4429') YIELD type, id, kind, payload RETURN type, id, kind, payload ``` Every node, every edge, every property version touching the subject is returned. --- ## 9. Authentication InvariantDB accepts three authentication schemes on the public API. Choose one: ### Bearer token (recommended for agents and services) Include a bearer token in the `Authorization` header: ``` Authorization: Bearer ``` For MCP clients configured via Claude Desktop: ```json { "mcpServers": { "invariantdb": { "command": "npx", "args": ["@invariantdb/mcp-client"], "env": { "INVARIANTDB_ENDPOINT": "https://your-tenant.invariantdb.com/graphs/production/mcp", "INVARIANTDB_TOKEN": "" } } } } ``` ### API key (`gq_` prefix) Long-lived API keys have the prefix `gq_`. Send them as a bearer token: ``` Authorization: Bearer gq_live_abc123... ``` Create and rotate API keys in the tenant admin UI. Keys carry a principal identity, and every request is subject to the same ACL and differential-privacy gates as a Cognito-authenticated user. ### Cognito JWT (recommended for interactive UIs) Interactive users authenticate through the tenant's Cognito Hosted UI. The returned ID token is a JWT; send it as a bearer token: ``` Authorization: Bearer eyJ... ``` The engine validates the JWT signature against the tenant's Cognito user pool and extracts the principal from the token claims. Every authenticated request carries a principal identity. That principal is: - The subject for ACL enforcement (field-level and row-level filters). - The account being charged in the differential-privacy audit log. - The `actor` recorded in `db.propertyProvenance` for every mutation the principal makes. --- ## 10. What not to do Common Cypher mistakes agents make when hitting InvariantDB for the first time. **1. Do not pass `atTime` as an MCP tool argument.** The MCP `cypher_query` tool intentionally rejects `atTime`. Put the temporal filter *inside* the Cypher body: ```cypher // Wrong (MCP will reject with a helpful error): {"name": "cypher_query", "arguments": {"query": "MATCH (n) RETURN n", "atTime": "2026-06-01T00:00:00Z"}} // Right: {"name": "cypher_query", "arguments": {"query": "MATCH (n) AS OF '2026-06-01T00:00:00Z' RETURN n"}} ``` **2. Do not attach `AT VALID` / `AT RECORDED` to `WITH`.** Bitemporal clauses attach to the `MATCH` pattern. ```cypher // Wrong: MATCH (n:Customer) WITH n AT VALID '2026-06-01T00:00:00Z' RETURN n // Right: MATCH (n:Customer) AT VALID '2026-06-01T00:00:00Z' RETURN n ``` **3. Do not `YIELD successorBeliefId` from `db.reviseBelief`.** That column was renamed. The correct columns are `oldBeliefId, newBeliefId, revisedAt`. **4. Do not `YIELD version, content, derivedFrom, validFrom, validTo` from `db.beliefLineage`.** That signature drifted. The correct columns are `beliefId, claim, confidence, createdAt, supersededAt, depth, evidence`. **5. Do not treat `db.graphCounts` `labels` and `edgeTypes` as exact.** They are a floor. If `memtableDirty=true`, a label or edge type present only in still-pending writes will not appear. `nodes` and `edges` are exact regardless. **6. Do not vector-search without an index.** Call `db.buildVectorIndex(label, prop)` first; otherwise `gds.vectorSearch` returns `INDEX_NOT_FOUND`. **7. Do not query with the wrong vector dimension.** The dimension is fixed at index build time; a query vector of a different length returns `DIM_MISMATCH`. **8. Do not assume `DELETE` cascades.** Plain `DELETE n` refuses on nodes with attached edges. Use `DETACH DELETE n` when you want the cascade. **9. Do not skip `chainStatus` on erasure receipts.** `db.proveErasure` can return `chainStatus: 'broken'` or `'missing'`. Only `'verified'` is safe to hand a regulator. **10. Do not fabricate procedure names.** If in doubt, `CALL db.listProcedures() YIELD name, signature, description` returns the exact catalog for this binary. --- Version: 2026-07-08 Last updated: 2026-07-08 Canonical URL: https://invariantdb.com/llms-full.txt Machine index: https://invariantdb.com/llms.txt