Agentic systems / Pattern 12 / Build the working path

Build a tenant-aware evidence store with cache checks

Use explicit principals, tenant-scoped keys, independent quotas, and cache invalidation in a small deterministic Python module.

By Sandeep Belgavi · 23 October 2026 · Architecture and design patterns

Run the module without connecting an identity provider

Download pattern_12.py and test_pattern_12.py. With Python 3.11 or later, macOS commands are python3 -B pattern_12.py and python3 -B -m unittest -v test_pattern_12.py. Windows launcher equivalents are py -3 -B pattern_12.py and py -3 -B -m unittest -v test_pattern_12.py. The code is standard-library only and performs no model or network calls.

The output contains distinct evidence for tenants a and b and cross_tenant_denied set to true. These are synthetic records. A successful run proves the specified local checks under the supplied fixtures; it does not prove authentication, distributed isolation, or protection from a caller who can directly modify the Python process.

Architecture and request sequence

Component diagram for Build a tenant-aware evidence store with cache checks
Original reference architecture by Sandeep Belgavi. Arrows show the responsibilities and transitions described below.
Read the architecture as text
  • Principal fixture: Subject, tenant memberships and scopes
  • authorize method: Check before any cache access
  • Scoped tuple key: Tenant and incident identify record
  • Rows and cache: Write invalidates matching cached value
  • Principal fixture → authorize method: Validated input
  • authorize method → Scoped tuple key: Bounded processing
  • Scoped tuple key → Rows and cache: Recorded result
Request sequence for Build a tenant-aware evidence store with cache checks
Illustrative sequence for this design; failure and recovery variants are explained in the article.
Read the sequence as text
  1. Caller → Boundary: Supply trusted principal
  2. Boundary → Store: Authorize scope
  3. Store → Boundary: Lookup scoped cache
  4. Caller → Boundary: Update permitted record
  5. Boundary → Store: Invalidate cached version

Inspect the trusted-principal seam

Principal is an immutable dataclass containing a subject, tenant memberships, and scopes. The store requires an instance with a nonempty subject, then checks membership and the requested scope. This is a deliberately explicit seam for an authentication adapter. A production server would construct the object from verified identity and current policy; it must not deserialize a client-provided object and call that verification.

The example's authorize method distinguishes read and write access. Tests confirm that a read-only principal cannot write and that a plain dictionary is rejected. These checks expose ordering and interface expectations, not cryptographic identity proof. The model adapter belongs downstream of authorization and should receive only the evidence that the verified principal is permitted to use.

Follow a read through authorization and cache

The read method checks permission before looking in the cache. It constructs a tuple from tenant and incident identifiers, then fills the cache from the corresponding row if necessary. A missing row raises KeyError without inserting a false cache entry. Because the cache key includes tenant, two organizations can use INC-1 without sharing a value.

The authorization-before-cache test first warms tenant b's cache through an allowed principal, then attempts the same lookup through tenant a's principal. That ordering reproduces a common failure condition: data exists in a fast path even though the current caller must not receive it. The revoked-membership test uses a new principal with no membership to verify that a warm cache does not preserve old access.

Follow a write through quota and invalidation

The write method authorizes first and validates the incident and text. New records consume the tenant's independent count. Updating an existing record does not consume another slot. A full tenant is rejected without preventing another tenant from creating its first record. This is a record-count demonstration, not a complete resource scheduler or billing system.

Every write removes the corresponding cached value. The update test reads an old value into the cache, writes a replacement, and confirms that the next read returns the new value while the count remains unchanged. In a distributed cache, invalidation needs a versioning or delivery strategy and tests under concurrent readers. The local dictionary operation does not establish that broader consistency contract.

Add an API and storage adapter deliberately

A proposed API accepts an incident identifier and an allowed tenant selection, but obtains the principal from middleware. It passes the resolved context to the repository rather than letting a model-generated tool argument override it. Use a single repository interface for retrieval, updates, and exports so scope checks cannot be accidentally skipped by a secondary route. Error responses should avoid revealing whether an unauthorized tenant's incident exists.

For a reference database deployment, represent tenant ownership in persistent keys and enforce database-side policy with an appropriately restricted service role. PostgreSQL's row-security documentation describes role exceptions that the deployment must account for. No PostgreSQL server, container platform, or cloud identity service is installed by this example. The diagram shows the proposed integration boundary, not deployed infrastructure.

Know what the tests leave open

Eight tests exercise identifier collision, unauthorized cached access, scope denial, per-tenant capacity, invalidation, membership change, untrusted input type, and missing records. They do not cover persistence, concurrency, token expiry, impersonation, encrypted storage, or support access. Add those tests when the corresponding adapters exist rather than creating passing placeholders that do not exercise a real boundary.

Before exposing the service, test authorization failures through the actual HTTP entry point and the actual worker identity. Review the cache, log exporter, backup system, and administrative tools as data paths. A repository class can be correct while an alternative endpoint leaks the same information. The operational article turns these broader concerns into failure drills and evidence an owner can review.

Keep authorization decisions distinct from data validation errors so an API adapter can return consistent responses without leaking whether another tenant owns the requested record.