The last two lessons secured the individual user’s view of the data, permissions on one side, injection resistance on the other. This lesson secures the organization’s broader obligations around that same data: keeping tenants cleanly apart from each other, honoring data residency and deletion law across jurisdictions, and being able to prove, months after the fact, exactly why the system said what it said to whom. These requirements rarely block a prototype from working, which is precisely why they’re easy to defer, but they routinely block a real launch, so they genuinely belong in the initial design, not in a remediation sprint scrambled together after a customer’s legal team asks a hard question.
Isolation models and their blast radii
Three models are worth understanding, and for each one the honest, useful question to ask is: “what breaks if one single line of application code forgets to do something it was supposed to do?”
Shared collection, tenant filter. Every chunk carries a tenant_id field; every query filters on it. This is the cheapest option by far, the simplest to build initially, and its isolation guarantee is fundamentally conventional rather than structural: one single endpoint somewhere in a large codebase that forgets to apply the filter is a genuine cross-tenant data leak, full stop, with no second line of defense catching the mistake. This model is defensible specifically for a single-company internal deployment, where the effective blast radius of a mistake is one organization that already internally shares most of its own data anyway. It’s a genuinely dangerous choice as a SaaS isolation promise made to paying enterprise customers.
Collection per tenant. Each customer gets their own dedicated collection or namespace within the vector store. Isolation becomes structural rather than conventional: a query scoped against tenant A’s collection cannot physically return tenant B’s rows, regardless of any application-layer bug, because the two tenants’ data literally lives in separate index structures. Deletion becomes simply dropping a collection; residency becomes placing a specific collection in a specific region; noisy-neighbor performance issues from one heavy tenant stay contained locally rather than degrading everyone. The cost is genuine per-collection overhead, since HNSW graphs carry fixed costs regardless of how small the collection is, plus real management surface area once tenant counts climb into the thousands. This is the sensible default for most enterprise SaaS deployments.
Database or cluster per tenant. Maximum available isolation, reserved specifically for the customers who both need and pay for exactly that guarantee: separate encryption keys per tenant, separate backups, a specific dedicated region, all of it contractually provable to an auditor on request. Expensive to run and operate; reserve this tier specifically for the customer segment whose contracts actually fund it.
A pattern that scales well in practice, and avoids the false choice of picking one isolation level for every tenant uniformly, is tiered isolation: pooled shared collections for small self-serve customers where the economics don’t support dedicated infrastructure, dedicated collections for mid-size enterprise customers, and fully dedicated infrastructure for the smaller regulated segment that specifically requires and pays for it, all while one single retrieval interface hides from the rest of your application code which underlying isolation model a given tenant happens to be running on.
Deletion is harder than it looks
Lesson 6 already built delete propagation purely for data correctness reasons. Compliance turns that same mechanism into a genuine legal obligation, and RAG systems as an architecture are unusually good at hiding copies of data in places you might not think to check. Trace through where a single sentence of one employee’s personal data could plausibly have ended up across everything built in this course so far:
- the original chunk rows and their corresponding vectors in the index,
- the LLM-written contextual summary describing that chunk, from Module 3’s enrichment step,
- GraphRAG entity nodes and community summaries that happen to mention them, if Module 5’s graph indexing was adopted,
- semantic cache entries whose cached answer text happens to quote them, covered in Lesson 23,
- evaluation fixtures built from real production traffic, covered in Lesson 22,
- and trace logs storing complete prompts, which by construction contain the retrieved text verbatim, in full.
So maintain a deletion map as a living piece of documentation: for every one of these derived artifact types, know its key path back to the originating source document and the specific data subject. Test this map with an actual rehearsal, pick a real document, delete it through your defined process, then grep every single store listed above for any remaining trace of its content, because the first time this deletion path is genuinely exercised should never be under an actual regulator’s 30-day compliance clock. Note also that this same analysis makes a real compliance argument specifically against GraphRAG’s cost profile from Lesson 15: LLM-written summaries that mention specific people are themselves personal data, sitting in a store that is genuinely annoying to selectively edit or purge on a per-person basis.
Residency requirements present the identical shape of problem, just moved into physical space rather than time: if a customer’s EU document text must contractually stay within the EU, then the EU index, the EU-region cache, the parsing service that processed it, the embedding API it called, and the LLM endpoint used for generation must all genuinely reside in-region, and any cross-region failover mechanism must not quietly relocate that data as a side effect of an unrelated availability decision. Per-tenant, or per-region, collections turn this from a painful rewrite into a straightforward placement decision made once at setup, which is yet another point in favor of that isolation model over a shared pooled collection.
The audit record
An audit trail that actually satisfies a real dispute months later is considerably more specific than most teams’ default application logs. Six months on, “which documents produced this specific answer” must be answerable with what those documents genuinely said at that exact time, not merely with a document ID that might now point at substantially different content:
# One immutable record per answered query. This is also the
# raw material for evaluation (Lesson 22) and cost attribution
# (Lesson 23), so it pays for itself several times.
@dataclass(frozen=True)
class AnswerAudit:
trace_id: str
timestamp: str
tenant_id: str
user_id: str
principals: list[str] # resolved groups AT QUERY TIME:
# proves what access they had then
question_raw: str
question_rewritten: str # Module 5 transformations
filters_applied: dict # extracted + ACL filters
retrieved: list[dict] # per chunk: chunk_id, doc_id,
# doc_version, content_hash, score
# -> version pinning is the key part
model: str # "claude-sonnet-5"
prompt_version: str # "grounded-v7"
answer: str
citations: list[dict] # marker -> chunk_id, url, page
latency_ms: int
tokens_in: int
tokens_out: int
Two fields in that record carry the majority of the practical weight. principals records precisely the access the user genuinely had at that specific moment, so an answer given shortly before a subsequent access revocation reads as fully explainable in an audit rather than as inherently suspicious. doc_version combined with content_hash pins the actual evidence used: documents drift over time as Lesson 6 established, and without this version pinning you can name which chunk ID was used but cannot reliably reproduce what text it actually contained on that day. Regulated deployments go one step further and snapshot the evidence text itself into genuinely immutable storage at answer time; that costs real storage money, and it’s the only approach that guarantees you can reconstruct an answer’s exact evidence verbatim after the underlying source document has since changed.
Retention policy for this record then pulls in two opposing directions simultaneously: audit and legal-hold rules push retention long, while privacy regulations push retention short, and prompts containing full retrieved text are among the most sensitive logs your system produces, arguably more sensitive than the original documents themselves since they represent exactly what a specific user was shown. Decide the concrete retention number together with legal counsel rather than unilaterally in engineering, encrypt the audit store at rest, restrict access to it as tightly as you’d restrict access to the underlying documents, and make sure it’s explicitly included in the deletion map discussed above rather than treated as exempt from it.
Governance is now covered across all three lessons of this module. What remains is the ongoing operational discipline that keeps all eight modules of this course honest over time, as the corpus and the user base both keep changing: measuring whether the system actually works as intended, what it genuinely costs to run, and when it quietly starts to degrade without anyone noticing. That’s Module 8, the final module of this course.