Skip to content

Organization isolation

Organization scope must not depend on discipline in every query. mic.tenancy associates context with the current execution flow and exposes a relational view that rejects unbounded operations.

from mic.tenancy import TenantScopedRelationalStore, tenant_context

projects = TenantScopedRelationalStore(
    raw_store,
    tenant_field="organization_id",
)

with tenant_context(organization_id):
    rows = projects.find(Project, query)

Secured-view guarantees

TenantScopedRelationalStore protects insert, get, find, count, and delete:

  • without tenant_context, the operation raises TenantLeak;
  • reads and counts always receive the organization filter;
  • a caller-supplied filter on the organization field is rejected;
  • an inserted record whose organization differs from context is rejected;
  • get and delete never see a row from another organization.

The context uses ContextVar: it follows the current asynchronous flow and is restored when the block exits. The HTTP, gRPC, or worker transport remains responsible for opening the context from an already authenticated identity.

Make bypass inaccessible

The guarantee applies to the injected view. If a repository also receives the raw store, it can bypass that boundary. Composition must therefore construct the raw store at bootstrap and pass only TenantScopedRelationalStore to multi-organization modules.

For highly sensitive data, complement this application boundary with a database-level policy such as PostgreSQL Row-Level Security. MIC neither creates nor operates that policy, and it does not replace isolation tests on the real target dialect.

Intentional limits

  • the field defaults to organization_id and remains configurable;
  • engine-specific operations use the SQL escape hatch and must explicitly apply the same scope;
  • MIC chooses neither partitioning strategy, organization hierarchy, nor cross-organization administration rules.

See also the explicit SQL escape hatch.