qp-vault supports multi-tenant isolation via tenant_id on all operations.
vault = Vault("./knowledge")
# Add with tenant isolation
vault.add("Tenant A document", tenant_id="site-123", trust_tier="canonical")
vault.add("Tenant B document", tenant_id="site-456", trust_tier="working")
# Search scoped to tenant
results = vault.search("document", tenant_id="site-123")
# Only returns site-123 resources
# List scoped to tenant
resources = vault.list(tenant_id="site-123")
# Verify scoped to tenant (Merkle tree per tenant)
result = vault.verify() # Vault-wideFor stricter isolation, lock the vault to a single tenant at construction:
vault = Vault("./knowledge", tenant_id="site-123")
# All operations auto-inject tenant_id="site-123"
vault.add("doc") # tenant_id="site-123" applied automatically
vault.search("query") # scoped to site-123
# Mismatched tenant_id is rejected
vault.add("doc", tenant_id="site-456") # Raises VaultError: tenant mismatchWhen a vault is tenant-locked:
- Operations with no
tenant_idauto-inject the locked tenant - Operations with a matching
tenant_idproceed normally - Operations with a different
tenant_idraiseVaultError
Limit the number of resources per tenant:
from qp_vault.config import VaultConfig
config = VaultConfig(max_resources_per_tenant=1000)
vault = Vault("./knowledge", config=config)
vault.add("doc", tenant_id="site-123") # OK until quota reached
# After 1000 resources: raises VaultError("Tenant site-123 has reached the resource limit")Quotas are enforced with an atomic COUNT(*) query at the storage layer. No TOCTOU race condition window.
tenant_id is stored as a column in the resources table with an index for efficient filtering.