One policy enforcement point applies tenant-scoped row filters to queries, search, exports, jobs, and aggregates, fails closed on missing context, and never treats a client-supplied tenant id as authority.
Package status: reference context ready for human review. The contract and test scenarios are complete, but no claim is made that an adopting implementation has passed them.
Decision
Multi-tenant APIs authorize rows through a single policy enforcement point that every data plane must call. Tenant authority comes from the authenticated session or service credential, never from a client-supplied header, query parameter, or body field. The same enforcement point wraps SQL queries, search index reads, CSV and object exports, background jobs, and aggregate counts so neighbor rows cannot leak through a secondary path. Missing tenant context fails closed. Denial evidence is written using the related audit-log package when that package is adopted.
Scope
- Enforcement architecture for multi-tenant-apis: who is the authority, where filters attach, and which planes are in scope.
- Search, exports, jobs, caches, and aggregates as first-class consumers of the same policy.
Outside this block
- Integrity and retention of audit logs (related audit-log package).
- Role catalogs beyond tenant membership, physical database-per-tenant isolation, and cross-tenant document sharing.
Contract
- Tenant id used for filtering is taken from the session or trusted workload identity; client-supplied tenant fields are ignored as authority.
- Every listed plane (query, search, export, job, aggregate) goes through the same enforcement point; a repository method that skips it is a defect.
- Aggregates, including total counts and uniqueness checks, are computed inside the tenant predicate so neighbor cardinality cannot be inferred.
- Caches and search partitions are keyed by tenant; a cache fill from one tenant cannot be served to another.
- If tenant context is missing, the operation returns RLS_CONTEXT_MISSING and performs no scan.
- Background jobs persist the actor tenant at enqueue time and the worker refuses to run with a different tenant.
Implementation guidance
- Prefer database policies or a query interceptor that injects the predicate once. Search must use a tenant partition or a mandatory filter the client cannot unset.
- Jobs copy tenant from the session at enqueue.
Failure handling and safeguards
- A query without context must fail, not return an empty list. Error messages omit neighbor ids and counts.
- Crash of the enforcement library fails closed rather than running unfiltered SQL.
Verification and operations
- Seed two tenants and hit list, search, stats, export, and a job. Mutation tests that unset the search filter must fail CI.
The executable-looking examples in this package are fixtures and acceptance contracts. Run
python tools/validate.py from the collection root to check package structure and metadata; then
implement and execute the scenarios in the target repository.
Adoption assumptions
- Names and numeric values in
example.yamlare an adoption profile, not universal defaults. - The adopting team must map actors, data classes, error vocabulary, and ownership to its system.
References
No external normative source is required; this package defines a project decision.