Filter Tenant Policy Before Ranking
Week 3 of 10 · Make cross-tenant retrieval and effects impossible by construction • Public source target: https://github.com/ZGTR/enterprise-access-agent-evals/tree/v1.0.2
System map · Day 03
Whole-system design
Five stable layers. Today's work is expanded and linked; the rest stays in context.
Entry and authorization control
Covered — Request entry · Approval gate
Tenant and policy authority
Source-backed today
Supplies tenant scope from runtime context rather than prompt text.
Compute and execution
Covered — Typed tool broker
Agent runtime compute
Source-backed today
Consumes only tenant-scoped candidates and immutable citation identifiers.
Policy retrieval compute
Source-backed today
Filters before ranking and preserves stale or conflicting versions for evaluation.
Enterprise resource boundary
Simulated enterprise system
Source-backed today
Makes colliding Acme and Globex identifiers unable to cross the adapter boundary.
Storage and state
Covered — Run and effect state
Evaluation evidence store
Source-backed today
Stores ten collision attacks with paired same-tenant positive controls.
Evidence and release control
Ahead — Evaluation harness compute · Trace and diagnosis plane · Release gate
Traversed today
Build colliding Acme and Globex corpora
Friendly fixtures rarely reveal isolation bugs, so both tenants need the same names, resource aliases, document IDs, and policy phrases. Give Acme and Globex an employee named Sarah, a salesforce-standard permission, and a POL-17 document whose rule differs. The tenant namespace, not identifier uniqueness, carries isolation.
Tagged retrieval.py constrains candidates before scoring:
scoped = (policy for policy in policies if policy.tenant_id == tenant_id)
terms = {term.casefold() for term in query.split()}
ranked = sorted(
scoped,
key=lambda policy: (
-sum(term in policy.text.casefold() for term in terms),
not policy.active,
policy.citation_id,
),
)
return tuple(ranked[:limit])
Each returned passage carries immutable tenant_id, document_id, version, and chunk ID. Stale or conflicting same-tenant documents stay available as controlled fixtures so later graders can distinguish retrieval from reasoning. Foreign documents never enter the ranker, prompt, logs, or citations.
Carry scope through agent and adapter boundaries
Filtering policy search alone is insufficient if employee lookup or state adapters accept a model-supplied tenant. Every boundary derives tenant from the same trusted context and verifies returned objects before use.
Tagged agent.py provides a deterministic normalization seam. A provider-backed runtime must consume opaque citation and employee references without synthesizing identifiers or rewriting tenant ownership.
class DeterministicProvider:
def normalize(self, request: RestoreAccessRequest) -> RestoreAccessRequest:
return request
The simulated authority in state.py keys all state by tenant and rejects a mismatched employee/resource pair with non-enumerating absence. “Not found” must not reveal whether the same ID exists in another tenant.
Attack collisions and measure unaffected controls
Ten attacks cover prompt-selected tenant, foreign employee ID, foreign resource ID, colliding document ID, globally ranked passage, citation substitution, stale foreign approval, cross-tenant retry, error-message enumeration, and poisoned policy text asking the agent to switch scope. Each attack expects zero foreign identifiers and zero effects.
The dataset grows to 40 cases. Each attack is paired with a same-tenant positive control so a broken search service cannot “pass” isolation by returning nothing to everyone. Record actor, resource, scope, precondition, expected/observed result, environment, timestamp, and immutable run ID.
Inject one bug that filters after ranking. The attack should fail because a foreign passage reached intermediate evidence even if final output hides it. Repair by moving the predicate into candidate construction, rebuild the index namespace, rerun attacks and controls, then clean up only the disposable corpus version.
The scored checkpoint supplies one collision trace; the unknown is allow or deny. Correct evidence contains no foreign IDs/effects and a passing same-tenant control. Misconception feedback contrasts output scrubbing with authorization-before-ranking. Decline any answer that invents tenant from prompt text.
Spend 1.5 hours on the Hugging Face Agentic RAG unit and RAGAS concepts, 4–5 hours on corpora, adapters, attacks, and repair, and 1 hour explaining why namespace constraints precede relevance. The handoff is R3-tenant-proof, D40-cases, and policy corpus v1. Week 4 adds traces capable of locating the earliest causal failure.