Deep technique

The system underneath the decision.

Every request — read or write — passes through the same pipeline, in the same order. This page is the full trace: why static ACLs leak, how authority is resolved, and what happens at each of the thirteen stages.


The problem this exists to solve

Permission drift: the gap where legacy RAG leaks.

Traditional multi-tenant RAG stamps access-control lists onto vector embeddings at ingestion time. When access is revoked, the vector store doesn't know yet — and won't, until a background re-index catches up.

Legacy: static denormalized ACLs
// 1. Ingestion: ACL stamped into vector metadata
INSERT INTO vector_chunks (embedding, metadata_acl)
VALUES ([...], '{"allowed_users":["u123","u456"]}');

// 2. Revocation happens in the core database
// u123 is offboarded — the vector store isn't told

// 3. Drift window: still queryable
SELECT chunk FROM vector_chunks
WHERE metadata_acl @> '{"allowed_users":"u123"}'; -- leak

The window can run minutes to hours depending on re-index cost. During it, revoked access is still fluently synthesized into answers.

TenantSage: dynamic join-chain
-- Executed BEFORE vector search, every request
SELECT c.id FROM tenants t
JOIN families f ON f.tenant_id = t.id
  AND f.quarantine_status = FALSE
JOIN children c ON c.family_id = f.id
JOIN user_assignments ua ON ua.child_id = c.id
WHERE t.id = :tenant_id
  AND ua.user_id = :user_id
  AND NOW() BETWEEN ua.effective_from
                AND ua.effective_to;

Revocation, suspension, or quarantine take effect in under a millisecond — there's no index to catch up, because permission was never stamped onto the vector.

LEGACY DRIFT WINDOW

Minutes to hours

Background re-indexing before revocation actually applies.

DAR LATENCY

< 1 ms

Authority is a live relational join, evaluated at query time.

VECTOR INDEX ROLE

Semantic only

The vector store carries meaning and distance, zero permission metadata.


Authority model

A four-layer hierarchy, resolved on every query.

Authority isn't a flag on a row. It's a chain: Tenant → Family → Child (document) → User Assignment. Break any link and the candidate set collapses to zero — by definition, not by exception handling.

Tenant → Family (quarantine-aware) → Child document → User Assignment (time-boxed)

A quarantined family — say, a legal-hold container — doesn't get filtered out after retrieval. It structurally disappears from the join chain, so it was never a candidate the AI could have seen in the first place. That's the difference between a blind spot and a filter.


The full trace

Thirteen stages. S0–S7 and S11–S12 run always. S8–S10 only run for actions.

Every request — whether it only asks a question or also wants to change something — runs the same read path. Only requests that produce a mutating action continue through the approval-bound block before reaching the ledger.

pipeline_definition.json
S0 · Request Ingress
validates identity, OIDC token, activeScopeId, payload
FAIL_UNAUTHENTICATED
S1 · Authority Resolution
ReBAC join-chain across Tenant/Family/Child/Assignment
FAIL_NO_AUTHORITY
S2 · Governance Decision
legal hold, retention, classification, quarantine
FAIL_POLICY_BLOCKED
S3 · Sealed EEB
computes and hashes the admissible evidence boundary
FAIL_EMPTY_EEB
S4 · Governed Retrieval
vector/hybrid/graph search constrained to the sealed set
FAIL_NO_EVIDENCE
S5 · Governed Context
binds evidence + metadata into a protected prompt context
FAIL_CONTEXT_EXCEEDED
S6 · Generation
LLM produces a response or a proposed action
FAIL_GENERATION_ERROR
S7 · Validation
grounding, hallucination, disclosure, content safety
FAIL_VALIDATION
S8 · Validation Approval — action only
approver identity + authorization for the mutation
FAIL_APPROVAL_DENIED
S9 · Execution Intent — action only
immutable intent payload + idempotency key
FAIL_INTENT_REJECTED
S10 · Exact Execution — action only
verifies executedMaterialHash == approvedMaterialHash
FAIL_EXECUTION_MISMATCH
S11 · Durable Receipt / Replay
builds completion proofs and the cycle hash hCycle
FAIL_RECEIPT_BUILD
S12 · Released
atomic commit to the append-only ledger, output released
FAIL_LEDGER_COMMIT
Why this matters: a read-only request ("summarize this week's incident reports") never touches S8–S10 — it's fully governed and logged by S7, then goes straight to the receipt. Only a request that resolves into a candidate action — sending, changing, deleting, approving — has to clear the additional approval-bound block before S11.

Works with what you already run

Five retrieval architectures, one governance layer.

TenantSage doesn't replace your retrieval stack — it constrains what any of these architectures are allowed to see before they run.

01

Hybrid RAG

Sparse lexical + dense vector search, both constrained to the sealed EEB candidate set.

02

GraphRAG

Subgraphs and community reports — quarantined nodes structurally excluded from traversal.

03

Agentic RAG

Tool-calling loops where every tool invocation re-enters the pipeline at S0.

04

Corrective RAG

Quality-evaluator retries stay inside the same sealed boundary, never a wider one.

05

Multimodal RAG

Visual and page-level vectors (e.g. ColPali-style) inherit authority from their source document, same as text.

+

Your own stack

If it can accept a pre-computed candidate ID set, it can be governed this way.


S11 / S12 in detail

An append-only ledger, not a log line.

Every governed request writes a chained, hashed entry — authority snapshot, policy decision, EEB hash, retrieval receipt, generation and validation results, and (for actions) the execution receipt. Each entry's hash depends on the one before it, so a tampered entry breaks the chain visibly rather than silently.

No durable proof → no allow decision. If the ledger write fails, the request fails with it — the system doesn't release an answer it can't later account for.
Next

See it decide, or read how to integrate.