feat(vector_io): add tenant isolation for vector store metadata - #5782
Conversation
…uthorizedSqlStore Migrate OpenAIVectorStoreMixin metadata storage from KVStore to AuthorizedSqlStore for row-level tenant isolation. All 9 vector_io providers (2 inline, 7 remote) now accept a policy parameter and optional metadata_store config for SQL-backed, access-controlled metadata. Dual storage support preserves backward compatibility: providers without metadata_store configured continue using KVStore. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Francisco Javier Arceo <farceo@redhat.com>
Add access control tests verifying that vector stores created by one user are invisible to other users. Tests cover retrieve, update, delete, and listing isolation using the Alice/Bob two-user pattern. Update the auth CI workflow to configure metadata_store on faiss, add vector_stores to server stores, and add ABAC access policies for all 4 metadata tables. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Francisco Javier Arceo <farceo@redhat.com>
…ore metadata When a deployment upgrades from KVStore-only storage to the new SQL-backed metadata_store, this migration automatically copies all existing vector store data (stores, files, file contents, and batches) into the SQL tables on first server startup. The migration is idempotent — it only runs when the SQL tables are empty and KVStore data exists. Migrated records are stamped with owner_principal="" (unowned), making them accessible to all authenticated users, which is correct for pre-multi-tenancy data that had no ownership concept. Uses only the KVStore/SqlStore protocol interfaces, so it works with any backend (SQLite, Postgres, etc.) without backend-specific code. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Francisco Javier Arceo <farceo@redhat.com>
…lve_impls The vector_io KVStore->SQL migration runs during the provider adapter's initialize() inside resolve_impls and issues a fetch_all that lazily creates the shared SQLAlchemy engine. Because SqlAlchemySqlStoreImpl.create_table only registers a Table in metadata and defers actual creation to the first engine bind, any service whose initialize() runs after the engine is bound will register tables that are never created. This caused register_connectors to fail with "no such table: connectors" during startup of the auth integration tests. Move prompts, conversations, and connectors initialize() to before resolve_impls so their tables are registered in metadata prior to any provider initialize() triggering engine creation. Also add the missing metadata_store field and policy parameter to the inline qdrant provider for parity with the other inline vector_io providers. Signed-off-by: Charlie Doern <cdoern@redhat.com>
- Tighten internal-init loop in stack.py to keep file under 1000-line limit. - Apply ruff-format to inline qdrant __init__. - Regenerate inline_qdrant.mdx provider doc for new metadata_store field. Signed-off-by: Charlie Doern <cdoern@redhat.com>
skamenan7
left a comment
There was a problem hiding this comment.
LGTM, couple of comments. Thanks!
| sql_store = self.metadata_store.sql_store | ||
|
|
||
| existing = await sql_store.fetch_all(table=TABLE_VECTOR_STORES, limit=1) | ||
| if existing.data: |
There was a problem hiding this comment.
I think the migration guard might be a bit too coarse here -- it checks vector_stores for any row, so if startup dies after copying stores but before files/chunks/batches, the next boot skips everything. a per-table check or a migration-complete flag would make this safe to resume after a crash.
| resp = httpx.post( | ||
| f"{self.base_url}/v1/vector_stores", | ||
| headers=self._headers(), | ||
| json={"name": name}, |
There was a problem hiding this comment.
might be missing something, but it looks like line 60 only sends {"name": name} and the auth workflow doesn't set default_embedding_model? the oauth2_token job is showing 400 on create, which would make sense if the router needs embedding config before it gets to tenant isolation. would passing embedding fields in the request body work here?
…store hook Merge upstream/main which added prompts tenant isolation and an AuthorizedSqlStore enforcement pre-commit hook. Both prompts and vector store ABAC policies coexist in the auth CI workflow. Migrated all 9 vector_io providers from direct sqlstore_impl usage to the authorized_sqlstore() convenience function to satisfy the new pre-commit hook. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Francisco Javier Arceo <farceo@redhat.com>
…thub.com/franciscojavierarceo/llama-stack into multi-tenancy/pr4-vector-store-isolation
Fix CI auth workflow failures and address review feedback: - Add vector_store::* routing table ABAC rules alongside existing sql_record::vector_stores::* rules to cover both authorization layers - Remove stale agent_state and metadata_store configs from the CI auth workflow to prevent SQLAlchemy lazy-init race with the responses table - Add embedding_model parameter to vector store access control tests - Use per-table migration guards in kvstore-to-sql migration so a crash mid-migration can resume from the last incomplete table on next boot Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Francisco Javier Arceo <farceo@redhat.com>
bf33fce to
820a843
Compare
…initialization. Signed-off-by: Sébastien Han <seb@redhat.com>
Signed-off-by: Sébastien Han <seb@redhat.com>
leseb
left a comment
There was a problem hiding this comment.
pushed some commits in this branch since you're away :)
Signed-off-by: Sébastien Han <seb@redhat.com>
Summary
OpenAIVectorStoreMixinmetadata storage from KVStore toAuthorizedSqlStorefor row-level tenant isolation across all 9 vector_io providers (faiss, sqlite-vec, chroma, qdrant, milvus, pgvector, weaviate, elasticsearch, infinispan)vector_storesSqlStoreReference toServerStoresConfigandmetadata_storeconfig field to each provider for SQL-backed metadatametadata_storeconfigured continue using KVStore unchangedContext
This is PR4 in the multi-tenancy series:
Design
The
OpenAIVectorStoreMixinstores 4 metadata domains (vector stores, files, file contents, file batches) that previously used KVStore with no access control. This PR adds a dual-path storage system:metadata_store(AuthorizedSqlStore) is configured → SQL tables withowner_principalandaccess_attributescolumns for ABAC tenant isolationkvstoreis configured → existing KVStore behavior (backward compat)Each provider factory function (
get_provider_impl/get_adapter_impl) now accepts an optionalpolicyparameter that the resolver auto-injects via signature introspection (same mechanism used for prompts and connectors in #5757).Provider-specific storage (Faiss indices, Qdrant collections, pgvector tables, etc.) is unchanged — only the shared metadata layer is migrated.
KVStore→SQL Migration
When deploying with
metadata_storeconfigured for the first time, the server automatically migrates existing vector store data from KVStore to SQL duringinitialize():owner_principal=""(unowned) — accessible to all authenticated users, which is correct for pre-multi-tenancy dataFiles Changed (42 files)
Core (3):
datatypes.py,stack.py,storage/datatypes.py— addvector_storesSqlStoreReferenceMixin (1):
openai_vector_store_mixin.py— dual-path SQL/KV metadata methods + KV→SQL migrationInline providers (6): faiss + sqlite-vec
{config, __init__, adapter}.pyRemote providers (21): 7 providers ×
{config, __init__, adapter}.pyGenerated docs (9): provider doc pages with new
metadata_storefieldIntegration tests (1):
test_vector_stores_access_control.py— 5 tenant isolation testsCI (1):
integration-responses-conversations-auth-tests.yml— vector store ABAC policies + test executionTest plan
uv run pytest tests/unit/ -x --tb=short(2277 passed)🤖 Generated with Claude Code