# Build Permissioned Retrieval

Search Engineering · Implementation Guide · Chapter 9

Interactive edition: https://mutapa.dev/search-engineering/build/build-permissioned-retrieval

Authorize records before ranking, scope derived artifacts and caches, and measure filtered-vector recall against an exact eligible baseline.

## Outcome

This chapter adds a deterministic authorization seam before candidate ranking. The runnable fixture returns only eligible records, a stable cache-scope fingerprint, and a minimal decision event. It also keeps eligible recall separate from access enforcement.

The film certifications and household policies are synthetic teaching fixtures. Do not add them to the publication corpus until its separate provenance and licensing decision is complete.

## Define the request boundary

Production policy engines differ, but the retrieval adapter needs the same inputs: a verified principal, action, resource attributes, request context, and policy version. Keep credentials out of the query and model prompt. Resolve them in the application harness, then pass a decision or authorized resource scope into retrieval.

`authorize_candidates()` filters before sorting. That ordering is the central contract:

```python
from chapter_08 import authorize_candidates

result = authorize_candidates(
    records=films,
    principal_id="family",
    allowed_certifications={"G", "PG"},
    policy_version="household-v17",
    limit=10,
)
```

The fixture accepts an allowed set to keep the example local. In production, obtain that scope from a trusted policy decision point or execute the equivalent row, document, or partition predicate at the protected store.

## Authorize before ranking

The complete implementation lives in `code/chapters/chapter_08.py`:

```python
# The course excerpt is generated from region:authorize_candidates.
```

The returned decision event contains references and a policy version. It deliberately omits the raw allowed-classification set because decision traces should not become a second identity or policy database.

Apply the same scope to BM25, vector, and hybrid candidate paths. Field controls must also cover values used in embeddings, features, snippets, prompts, and generated evidence. Removing a field only at response serialization leaves its earlier influence intact.

## Scope caches and derivatives

The fixture computes a fingerprint from principal, allowed classifications, and policy version. A real key should include every stable input that changes eligibility, such as tenant, relationship revision, locale, schema, or purpose. You can partition physically instead when that boundary is easier to audit.

Do not log the complete cache key if it contains sensitive identity data. A one-way authorization-class reference is enough to detect cross-scope reuse.

## Measure eligible recall

Exact search over the eligible subset is the denominator for filtered approximate-nearest-neighbor recall:

```python
from chapter_08 import eligible_recall

recall = eligible_recall(approximate_ids, exact_eligible_ids)
```

Report boundary violations and eligible recall as separate metrics. A query can have perfect authorization with weak recall, or excellent recall with a severe policy failure.

## Run it

```bash
.venv/bin/python code/chapters/chapter_08.py
cd code/chapters && ../../.venv/bin/python -m unittest test_chapter_08.py
```

The next chapter carries the decision context into caches, memory, delegation, and checkpoint resume.

## Success criteria

- Restricted fixture IDs never reach the ranked candidate list.
- Two authorization classes produce different cache-scope fingerprints.
- The decision event carries references and a policy version, and omits the raw allowed-classification set.
- Eligible recall is measured against an exact authorized baseline and reported separately from boundary violations.

## Further reading

- [Diagnose this stage in an existing system](https://mutapa.dev/search-engineering/reference/permissioned-retrieval.md)
- [Next chapter: Chapter 10, Carry Policy Context](https://mutapa.dev/search-engineering/build/carry-policy-context.md)
