# Engineering example: permissioned retrieval

Run a synthetic retrieval example with tenant and document access checks. Inspect permitted context, denied requests and the boundaries still untested.

Author: TeqEngine
Published: 2026-09-11
Updated: 2026-09-12
Canonical: https://teqengine.ai/insights/permissioned-retrieval-example

## The decision

This synthetic lexical retrieval example checks tenant and reader access before selecting content, checks citations at read time and invalidates cached evidence when a source is revoked, deleted or versioned. It demonstrates specific permission boundaries without claiming to be a complete RAG product.

- Nine tests cover tenant, reader, cache and citation behavior.
- The fixture uses current in-memory permissions and no model.
- Authentication and distributed permission synchronization are outside its scope.

## The problem: a correct answer can be an unauthorized answer

The fixture contains five documents in two tenants. Alice can read alpha’s finance document; Bob cannot. Carol can read beta’s finance document. The query “forecast” is the same for all three callers, but the allowed evidence differs. Access is applied before lexical ranking and context assembly.

The example follows a basic authorization principle: the application checks the requested resource for the current trusted caller. A query string or model output does not establish the caller’s tenant or group membership.

Sources: [OWASP: Authorization Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html).

A second scenario caches Alice’s answer, revokes her finance-document access and asks again. The cached answer cannot survive the changed dependency. A direct citation request also checks current access rather than bypassing the retrieval path.

## Run and inspect the fixture

[Download the runnable source archive](https://teqengine.ai/evidence/retrieval-v2/source.zip). Python standard library only. Includes source, behavioral tests, recorded results and an MIT license.

[Inspect the implementation](https://teqengine.ai/evidence/retrieval-v2/example.py). Read the exact Python source included in the archive.

[Inspect the observed results](https://teqengine.ai/evidence/retrieval-v2/observed-results.json). Deterministic output captured from the packaged source.

[Read the MIT license](https://teqengine.ai/evidence/retrieval-v2/license.txt). Reuse terms for this synthetic example. The archive includes the same license.

[Verify the archive checksum](https://teqengine.ai/evidence/retrieval-v2/source.zip.sha256). SHA-256 of the published source archive.

**From the extracted retrieval-example directory**

```sh
python3 -B -m unittest -v
python3 -B example.py
```

Use Python 3.10 or later. The example uses an in-memory corpus and the standard library. There is no model, vector service, network access or credential. The Principal objects represent already-authenticated caller context supplied by a trusted application; constructing one is not an authentication mechanism.

Version 2 prevents callers from changing cached source lists through an edited response. It also tests discovery after a new access grant when the previous answer had no accessible evidence. The original versioned download remains available for reproducibility; use this revised version for the current example.

## How the boundaries are represented

**Fixture components**

| Component | Behavior |
| --- | --- |
| Principal | Immutable tenant and user fields supplied by the trusted test harness. |
| Document | Tenant, allowed readers, content and version. |
| Search | Check access before scoring and selecting document IDs. |
| Citation | Recheck current access before returning source text. |
| Answer cache | Key by caller/query and record source-version dependencies. |
| Revocation | Remove the reader and increment the source version. |

On cache reuse, every source must still exist, remain readable and match the recorded version. Otherwise the answer is rebuilt from currently accessible evidence. Empty answers are not cached, so newly accessible evidence can be discovered after an earlier no-answer result.

## Captured result from this source

**Observed scenario output**

```json
{
  "alice_after_revocation": {
    "cached": false,
    "sources": [],
    "text": "No accessible evidence"
  },
  "alice_before_revocation": {
    "cached": false,
    "sources": [
      "a-finance"
    ],
    "text": "Confidential revenue forecast"
  },
  "alice_cached_read": {
    "cached": true,
    "sources": [
      "a-finance"
    ],
    "text": "Confidential revenue forecast"
  },
  "beta_result": {
    "cached": false,
    "sources": [
      "b-finance"
    ],
    "text": "Beta revenue forecast"
  },
  "bob_result": {
    "cached": false,
    "sources": [],
    "text": "No accessible evidence"
  },
  "fixture": "synthetic five-document lexical corpus; no model"
}
```

Alice initially receives a-finance and can reuse that cached result. Bob receives no accessible evidence. Carol receives only b-finance. After revocation, Alice receives no accessible evidence and the previous cached text is not returned. These are deterministic fixture outcomes, not a retrieval-quality benchmark.

## What the nine tests establish

- The same query returns tenant-scoped results.
- A different reader in the same tenant cannot receive restricted content.
- Revocation invalidates cached evidence.
- Direct citation access follows current permissions.
- A deleted source cannot survive through a cached answer.
- A changed source version rebuilds the answer.
- An unknown principal receives no evidence.
- Edits to returned source lists cannot alter cached evidence.
- A previously empty answer does not conceal a new access grant.

These tests inspect returned content and source identifiers, not only a boolean authorization response. A system can deny a document endpoint while still leaking its content through a snippet, summary or cached answer.

## What a live design still needs

The fixture has synchronous in-memory policy and data. Real ingestion, identity, index and cache systems can have different update delays. Product-specific document-permission features may require source synchronization or explicit refresh; they should not be described as immediate revocation without verifying that contract.

Sources: [Microsoft: Document-level access control in Azure AI Search](https://learn.microsoft.com/en-us/azure/search/search-document-level-access-overview).

The example checks cached dependencies. Discovery of newly relevant documents, concurrent policy changes, distributed caches, ingestion attacks, prompt injection and generator output controls are each designed and evaluated as their own requirements.

Use the code to inspect a bounded principle: access is enforced before content is returned, and reused evidence is revalidated. Extend it only after defining the actual authority source, freshness requirement and failure behavior.

[Design permission-aware RAG](https://teqengine.ai/insights/permission-aware-rag). Apply the principle to source ACLs, query filters, derived content, caches and citations.

## Sources and scope

Technical references inform the cited statements. The decision frameworks and synthetic examples are TeqEngine’s editorial guidance.

- [OWASP: Authorization Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html)
- [Microsoft: Document-level access control in Azure AI Search](https://learn.microsoft.com/en-us/azure/search/search-document-level-access-overview)

## Continue reading

- [RAG quality evaluation](https://teqengine.ai/insights/rag-evaluation)
- [Action authorization](https://teqengine.ai/insights/ai-agent-authorization)
