feat: opt-in tenant isolation across all six blocks - #58
Merged
Conversation
Closes the multi-tenant storage-isolation gap: a namespace is a physical store, so two tenants both scoped to the same namespace name previously shared data. With `tenant_isolation: true` each request's storage namespace is qualified with the caller's capability tenant, so tenants sharing a namespace name get physically separate data. Enforcement lives in ONE place — the service layer, via auth.QualifyNamespace(tenant, namespace, isolate), before the driver is ever touched — so a driver can't leak across tenants even by accident, and no driver or schema changes were needed for these five blocks. - Off by default: storage is byte-for-byte identical to before, so single-tenant deployments and existing data are unaffected. Opt in via `tenant_isolation`. - Each block service gained the flag (threaded from config → server.Deps); its resolve helper now returns the qualified storage namespace and every driver data call uses it, while auth checks + registry resolution keep using the config namespace. - lease no longer echoes the (qualified) storage namespace back to clients — the wire handle carries the config namespace, so the internal prefix never leaks. - semantic is bound per-namespace and needs a driver-level tenant column (not yet implemented), so config validation refuses tenant_isolation + a semantic namespace rather than silently sharing semantic data. Verified: a two-tenant kv service test (isolated get/scan/delete when on; shared last-write-wins when off), config guard tests, full go test -race, golangci-lint, and the docs build all pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completes tenant isolation across all six blocks. Semantic is bound per-namespace (the driver has no namespace parameter to qualify), so instead of the service-layer namespace prefix the five other blocks use, it carries a tenant dimension in the driver: (tenant, id) is a record's identity, and every read and write is tenant-scoped. Two tenants can reuse the same record id without colliding. Removes the startup guard that previously refused isolation + semantic. - driver.go: Tenant on Record + SearchOptions/DeleteOptions/ExpireOptions. - service.go: threads auth.StorageTenant(cap.Tenant, isolation) — set on each upserted record and passed to search/delete/expire. - memory driver: records keyed by the composite (tenant, id); Search/Delete/ Expire/supersede all scope to the tenant. - postgres driver: a `tenant text NOT NULL DEFAULT ''` column and a composite (tenant, id) primary key; every dense/sparse/hybrid search (via the shared filterConds), materialize, upsert (incl. CAS + supersede), delete, and expire is tenant-scoped. ensureSchema adds the column and idempotently upgrades a pre-isolation (id)-only primary key to (tenant, id); existing rows land under tenant='' (the un-isolated partition, matching pre-isolation reads). - New conformance case (both harnesses) proves two tenants sharing an id are isolated on search and delete. Verified: memory conformance, the full semantic conformance on real pgvector (all existing cases + the new isolation case), and the idempotent PK-upgrade path (old id-only PK → (tenant, id), re-runnable, old row preserved). go build/ vet, go test -race, golangci-lint, and the docs build all pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Opt-in
tenant_isolationconfig flag (default off = byte-identical to before). Two mechanisms:auth.QualifyNamespace(tenant, ns, isolate)(tenant \x1f ns); centrally enforced, zero driver/schema change.tenantcolumn: composite(tenant, id)PK, tenant-scoped queries, idempotent PK-upgrade DO block.Non-breaking (default-off, existing data unaffected). Verified: kv 2-tenant service test; full semantic conformance on real pgvector incl. a new TenantIsolation case; PK-upgrade path verified via psql.
🤖 Generated with Claude Code