feat(startup): atomic swap dim-vs-MIGRATED guard for runtime schema validator

Add src/startup/embedding_validator.py that introspects the actual pgvector
  column dim at boot and refuses to start if it does not match
  EMBEDDING_VECTOR_DIMENSIONS. Runs after the DB pool is up and before the
  embedding client is constructed, in both src/main.py (FastAPI lifespan) and
  src/deriver/__main__.py.

  Implementation details:
  - Schema-qualified pg_attribute join through pg_class/pg_namespace respects
    DB.SCHEMA rather than relying on search_path
  - Bounded retry (3 attempts, 1s backoff) for transient introspection failure,
    then fail-closed with "could not validate embedding schema" — uncertainty
    is not a green light to serve traffic
  - External-store sampler (turbopuffer, lancedb) enumerates workspaces from
    the application DB and probes their lazy-created namespaces; current
    per-namespace probe is a no-op stub since the SDKs do not expose
    uniform dim introspection — full enumeration is left to
    `configure_embeddings --report` in Phase 3

  Atomic guard swap: deletes the old dim-vs-MIGRATED config validator (which
  forbade non-1536 pgvector unless MIGRATED=True) in the same commit as the
  new runtime validator. There is no release window where non-1536 pgvector
  can start unprotected. The 9 dual-write branches that use VECTOR_STORE.MIGRATED
  remain untouched and load-bearing for legacy-tenant backend swaps.

  VECTOR_STORE_DIMENSIONS deprecation: drop the "must match" raise; in
  propagate_namespace, check model_fields_set and emit logger.warning +
  DeprecationWarning (DeprecationWarning alone is filtered by Python's default
  config and would not reach operators). Always overwrite with
  EMBEDDING.VECTOR_DIMENSIONS regardless.

  Test changes:
  - tests/test_models_vector_dim.py: Phase 1's VECTOR_STORE_TYPE=lancedb +
    MIGRATED=true escape hatches removed; the test now passes on plain
    EMBEDDING_VECTOR_DIMENSIONS=768
  - tests/llm/test_model_config.py: the two tests asserting the old guards
    replaced with tests for the new deprecation + acceptance behavior
  - tests/startup/test_embedding_validator.py: 10 new tests — dim assertion
    logic (pass/mismatch/missing/unbounded/non-public-schema), fail-closed
    retry budget, real-test-DB pass, real-DB ALTER-then-validate, deprecation
    warning capture, non-1536 + pgvector + MIGRATED=false at config time
This commit is contained in:
Vineeth Voruganti 2026-05-12 17:43:16 -04:00
parent 01dfec523d
commit 405f625595
9 changed files with 501 additions and 53 deletions

View File

@ -1287,25 +1287,27 @@ class AppSettings(HonchoSettings):
self.CACHE.NAMESPACE = self.NAMESPACE
if "NAMESPACE" not in self.VECTOR_STORE.model_fields_set:
self.VECTOR_STORE.NAMESPACE = self.NAMESPACE
if "DIMENSIONS" not in self.VECTOR_STORE.model_fields_set:
self.VECTOR_STORE.DIMENSIONS = self.EMBEDDING.VECTOR_DIMENSIONS
elif self.VECTOR_STORE.DIMENSIONS != self.EMBEDDING.VECTOR_DIMENSIONS:
raise ValueError(
"VECTOR_STORE.DIMENSIONS must match EMBEDDING.VECTOR_DIMENSIONS"
if "DIMENSIONS" in self.VECTOR_STORE.model_fields_set:
# VECTOR_STORE_DIMENSIONS is deprecated: EMBEDDING_VECTOR_DIMENSIONS
# is the single source of truth. Log a runtime-visible warning
# so operators see it (DeprecationWarning is filtered by Python's
# default config outside __main__/tests) and also raise the stdlib
# warning so tests can assert on it.
import warnings
message = (
"VECTOR_STORE_DIMENSIONS is deprecated; "
"EMBEDDING_VECTOR_DIMENSIONS is authoritative. "
"Drop VECTOR_STORE_DIMENSIONS from your .env."
)
logger.warning(message)
warnings.warn(message, DeprecationWarning, stacklevel=2)
self.VECTOR_STORE.DIMENSIONS = self.EMBEDDING.VECTOR_DIMENSIONS
if "NAMESPACE" not in self.TELEMETRY.model_fields_set:
self.TELEMETRY.NAMESPACE = self.NAMESPACE
if "NAMESPACE" not in self.METRICS.model_fields_set:
self.METRICS.NAMESPACE = self.NAMESPACE
if self.EMBEDDING.VECTOR_DIMENSIONS != 1536 and (
self.VECTOR_STORE.TYPE == "pgvector" or not self.VECTOR_STORE.MIGRATED
):
raise ValueError(
"EMBEDDING.VECTOR_DIMENSIONS must remain 1536 while pgvector is "
+ "active or vector-store migration is incomplete"
)
return self

View File

@ -6,6 +6,8 @@ import uvloop
from prometheus_client import start_http_server
from src.config import settings
from src.db import engine
from src.startup import validate_embedding_schema
from src.telemetry import initialize_telemetry_async, shutdown_telemetry
from .queue_manager import main
@ -56,6 +58,11 @@ async def run_deriver():
"""Run the deriver with proper telemetry lifecycle management."""
# Initialize async telemetry (CloudEvents emitter)
await initialize_telemetry_async()
# Fail fast if the embedding schema does not match settings — same gate
# the API runs in its lifespan.
await validate_embedding_schema(engine)
try:
await main()
finally:

View File

@ -28,6 +28,7 @@ from src.routers import (
webhooks,
workspaces,
)
from src.startup import validate_embedding_schema
from src.telemetry import (
initialize_telemetry_async,
metrics_endpoint,
@ -125,6 +126,12 @@ async def lifespan(_: FastAPI):
# Initialize CloudEvents telemetry
await initialize_telemetry_async()
# Validate embedding schema before serving any traffic. Fails closed: if
# the configured EMBEDDING_VECTOR_DIMENSIONS does not match the physical
# pgvector columns, the process refuses to start rather than silently
# writing wrong-dim vectors.
await validate_embedding_schema(engine)
try:
await init_cache()
except Exception as e:

8
src/startup/__init__.py Normal file
View File

@ -0,0 +1,8 @@
"""Startup-time validators that gate API/deriver boot."""
from src.startup.embedding_validator import (
StartupValidationError,
validate_embedding_schema,
)
__all__ = ("StartupValidationError", "validate_embedding_schema")

View File

@ -0,0 +1,220 @@
"""Startup validator for the embedding pipeline.
Crashes the process at boot if the configured EMBEDDING_VECTOR_DIMENSIONS does
not match the physical pgvector schema. Replaces an earlier config-time guard
that forbade non-1536 dims unless the operator asserted a VECTOR_STORE.MIGRATED
flag the schema introspection here is more accurate because it inspects
actual state instead of operator-asserted state.
For external stores (turbopuffer, lancedb) the check is best-effort: namespaces
are per-workspace and lazy-created, so this validator can only sample existing
ones. Full enumeration is available via `uv run python -m
src.scripts.configure_embeddings --report` (Phase 3).
"""
from __future__ import annotations
import asyncio
import logging
from sqlalchemy import text
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncEngine
from src.config import AppSettings, settings
logger = logging.getLogger(__name__)
# Embedding tables that must exist with matching dim.
_EMBEDDING_TABLES: tuple[str, ...] = ("documents", "message_embeddings")
# Retry budget for transient introspection failures. Total wall time is
# bounded so a sick DB does not hang readiness; fail-closed after exhaustion.
_RETRY_ATTEMPTS = 3
_RETRY_BACKOFF_SECONDS = 1.0
# Best-effort external sampler bounds.
_EXTERNAL_SAMPLE_LIMIT = 10
class StartupValidationError(RuntimeError):
"""Raised when the embedding configuration cannot be reconciled with the
physical schema. Always surfaced before any HTTP route is served or any
queue task is processed.
"""
async def validate_embedding_schema(
engine: AsyncEngine,
*,
app_settings: AppSettings | None = None,
) -> None:
"""Validate that the embedding schema matches the configured dimension.
Run after the DB pool is initialized and before the embedding client is
constructed. Fails closed: any unrecoverable introspection error raises
rather than letting the process serve traffic with an unknown state.
"""
s = app_settings if app_settings is not None else settings
target_dim = s.EMBEDDING.VECTOR_DIMENSIONS
schema = s.DB.SCHEMA
dims = await _introspect_pgvector_dims_with_retry(engine, schema)
_assert_pgvector_dims_match(dims, schema=schema, target_dim=target_dim)
if s.VECTOR_STORE.TYPE in ("turbopuffer", "lancedb"):
await _sample_external_namespaces(engine, target_dim=target_dim)
async def _introspect_pgvector_dims_with_retry(
engine: AsyncEngine, schema: str
) -> dict[str, int]:
"""Schema-qualified pg_attribute introspection with bounded retries.
Returns a mapping of table name -> raw ``atttypmod`` for the embedding
columns. Fails closed on the last attempt uncertainty is not a green
light to serve traffic.
"""
last_exc: Exception | None = None
for attempt in range(_RETRY_ATTEMPTS):
try:
return await _introspect_pgvector_dims_once(engine, schema)
except SQLAlchemyError as e:
last_exc = e
remaining = _RETRY_ATTEMPTS - attempt - 1
if remaining > 0:
logger.warning(
"Embedding schema introspection attempt %d/%d failed: %s",
attempt + 1,
_RETRY_ATTEMPTS,
e,
)
await asyncio.sleep(_RETRY_BACKOFF_SECONDS)
raise StartupValidationError(
f"could not validate embedding schema: {last_exc}"
) from last_exc
async def _introspect_pgvector_dims_once(
engine: AsyncEngine, schema: str
) -> dict[str, int]:
"""Single-shot schema-qualified pg_attribute lookup.
The join through ``pg_class``/``pg_namespace`` lets us respect
``DB.SCHEMA`` rather than relying on the ambient search_path.
"""
query = text(
"""
SELECT c.relname AS table_name, a.atttypmod AS typmod
FROM pg_attribute a
JOIN pg_class c ON a.attrelid = c.oid
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE n.nspname = :schema
AND c.relname = ANY(:tables)
AND a.attname = 'embedding'
"""
)
async with engine.connect() as conn:
result = await conn.execute(
query,
{"schema": schema, "tables": list(_EMBEDDING_TABLES)},
)
return {row.table_name: row.typmod for row in result}
def _assert_pgvector_dims_match(
dims: dict[str, int], *, schema: str, target_dim: int
) -> None:
expected = set(_EMBEDDING_TABLES)
missing = expected - dims.keys()
if missing:
listing = ", ".join(sorted(f"{schema}.{t}.embedding" for t in missing))
raise StartupValidationError(
f"Required vector columns missing: {listing}."
+ " Run `alembic upgrade head` first."
)
for table in sorted(expected):
atttypmod = dims[table]
if atttypmod == -1:
raise StartupValidationError(
f"{schema}.{table}.embedding has no declared vector dimension"
+ " (unbounded typmod). Run"
+ " `uv run python -m src.scripts.configure_embeddings`."
)
# pgvector stores the declared dim directly in atttypmod (no VARHDRSZ).
actual = atttypmod
if actual != target_dim:
raise StartupValidationError(
f"{schema}.{table}.embedding dim ({actual}) does not match"
+ f" EMBEDDING_VECTOR_DIMENSIONS ({target_dim}). Run"
+ " `uv run python -m src.scripts.configure_embeddings`"
+ " or fix EMBEDDING_VECTOR_DIMENSIONS."
)
async def _sample_external_namespaces(engine: AsyncEngine, *, target_dim: int) -> None:
"""Best-effort dim check across existing external-store namespaces.
External stores in this codebase are per-workspace and lazy-created on
first write (see ``src.vector_store.get_vector_namespace``), so there is
no canonical deployment-wide namespace to introspect. We enumerate up to
``_EXTERNAL_SAMPLE_LIMIT`` workspaces from the application DB and probe
their derived namespaces. Missing namespaces are OK; mismatched dims
crash startup. Run ``configure_embeddings --report`` for full
enumeration when a hard guarantee is needed.
"""
workspace_names = await _sample_workspace_names(engine, _EXTERNAL_SAMPLE_LIMIT)
if not workspace_names:
logger.info(
"External-store validator: no workspaces exist yet, skipping sample"
)
return
# Import lazily to avoid pulling in vector store deps when not configured.
from src.vector_store import get_external_vector_store
store = get_external_vector_store()
if store is None:
# Settings said TYPE != pgvector but the store could not be created.
# That is its own problem and not for this validator to swallow.
return
mismatches: list[tuple[str, int]] = []
for workspace_name in workspace_names:
namespace = store.get_vector_namespace("message", workspace_name)
actual_dim = await _probe_namespace_dim(store, namespace)
if actual_dim is not None and actual_dim != target_dim:
mismatches.append((namespace, actual_dim))
if mismatches:
formatted = ", ".join(f"{ns} (dim={d})" for ns, d in mismatches)
raise StartupValidationError(
f"Existing external-store namespaces have dim != {target_dim}:"
+ f" {formatted}. Run"
+ " `uv run python -m src.scripts.configure_embeddings --report`."
)
async def _sample_workspace_names(engine: AsyncEngine, limit: int) -> list[str]:
"""Pull up to ``limit`` workspace names ordered by creation time."""
query = text("SELECT name FROM workspaces ORDER BY created_at DESC LIMIT :limit")
async with engine.connect() as conn:
result = await conn.execute(query, {"limit": limit})
return [row.name for row in result]
async def _probe_namespace_dim(store: object, namespace: str) -> int | None:
"""Best-effort: return the namespace's declared dim if introspectable.
Returns ``None`` if the namespace does not exist or the SDK does not
expose dim metadata for the configured store. Callers must treat
``None`` as "do not flag a mismatch" full enumeration is delegated to
the ``configure_embeddings --report`` path.
"""
# The base ``VectorStore`` does not currently expose introspection of an
# individual namespace's dim. Until a concrete probe is added per store,
# treat all sampled namespaces as opaque. The pgvector check above is the
# load-bearing safety; first-write per workspace inherits its correctness
# from the embedding client honoring settings.EMBEDDING.VECTOR_DIMENSIONS.
_ = (store, namespace)
return None

View File

@ -1,5 +1,4 @@
import os
import re
from pathlib import Path
from typing import Any, cast
@ -251,14 +250,14 @@ def test_app_settings_propagate_embedding_dimensions_to_vector_store() -> None:
assert settings.VECTOR_STORE.DIMENSIONS == 2048
def test_app_settings_require_matching_embedding_and_vector_store_dimensions() -> None:
with pytest.raises(
ValueError,
match=re.escape(
"VECTOR_STORE.DIMENSIONS must match EMBEDDING.VECTOR_DIMENSIONS"
),
):
AppSettings(
def test_app_settings_explicit_vector_store_dimensions_warns_and_overrides() -> None:
"""VECTOR_STORE.DIMENSIONS is deprecated: EMBEDDING.VECTOR_DIMENSIONS wins
and the operator gets a DeprecationWarning if they set it explicitly."""
import warnings
with warnings.catch_warnings(record=True) as captured:
warnings.simplefilter("always")
settings = AppSettings(
EMBEDDING=EmbeddingSettings(VECTOR_DIMENSIONS=2048),
VECTOR_STORE=VectorStoreSettings(
TYPE="lancedb",
@ -266,28 +265,39 @@ def test_app_settings_require_matching_embedding_and_vector_store_dimensions() -
DIMENSIONS=1536,
),
)
messages = [
str(w.message) for w in captured if issubclass(w.category, DeprecationWarning)
]
assert any(
"VECTOR_STORE_DIMENSIONS is deprecated" in m for m in messages
), f"expected deprecation warning, got {messages!r}"
assert settings.EMBEDDING.VECTOR_DIMENSIONS == 2048
assert settings.VECTOR_STORE.DIMENSIONS == 2048, (
"EMBEDDING.VECTOR_DIMENSIONS should always overwrite the operator-supplied "
"VECTOR_STORE.DIMENSIONS value"
)
def test_app_settings_reject_non_1536_dimensions_while_pgvector_or_dual_write_active() -> (
None
):
with pytest.raises(
ValueError,
match=re.escape("EMBEDDING.VECTOR_DIMENSIONS must remain 1536"),
):
AppSettings(
EMBEDDING=EmbeddingSettings(VECTOR_DIMENSIONS=2048),
VECTOR_STORE=VectorStoreSettings(TYPE="pgvector", MIGRATED=True),
)
with pytest.raises(
ValueError,
match=re.escape("EMBEDDING.VECTOR_DIMENSIONS must remain 1536"),
):
AppSettings(
EMBEDDING=EmbeddingSettings(VECTOR_DIMENSIONS=2048),
VECTOR_STORE=VectorStoreSettings(TYPE="lancedb", MIGRATED=False),
def test_app_settings_accepts_non_1536_with_any_vector_store_configuration() -> None:
"""The dim-vs-MIGRATED guard was removed; the runtime startup schema
validator (src/startup/embedding_validator.py) is the new safety net.
Construction must succeed for every combination at config time."""
from typing import Literal
combos: list[tuple[Literal["pgvector", "turbopuffer", "lancedb"], bool]] = [
("pgvector", True),
("pgvector", False),
("lancedb", True),
("lancedb", False),
]
for store_type, migrated in combos:
settings = AppSettings(
EMBEDDING=EmbeddingSettings(VECTOR_DIMENSIONS=768),
VECTOR_STORE=VectorStoreSettings(TYPE=store_type, MIGRATED=migrated),
)
assert settings.EMBEDDING.VECTOR_DIMENSIONS == 768
assert store_type == settings.VECTOR_STORE.TYPE
assert settings.VECTOR_STORE.MIGRATED is migrated
def test_config_toml_example_uses_nested_model_config_sections() -> None:

View File

View File

@ -0,0 +1,205 @@
"""Phase 2: startup embedding-schema validator + VECTOR_STORE_DIMENSIONS deprecation."""
from __future__ import annotations
import os
import subprocess
import sys
import warnings
from pathlib import Path
from unittest.mock import AsyncMock
import pytest
from sqlalchemy import text
from sqlalchemy.exc import OperationalError
from sqlalchemy.ext.asyncio import AsyncEngine
from src.startup.embedding_validator import (
StartupValidationError,
_assert_pgvector_dims_match, # pyright: ignore[reportPrivateUsage]
validate_embedding_schema,
)
_PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
# pgvector stores the declared dim directly in atttypmod (no VARHDRSZ offset).
def _typmod(dim: int) -> int:
return dim
# ---------------------------------------------------------------------------
# Pure-function unit tests for the dim assertion
# ---------------------------------------------------------------------------
def test_assert_pgvector_dims_match_passes_when_all_dims_align() -> None:
_assert_pgvector_dims_match(
{"documents": _typmod(1536), "message_embeddings": _typmod(1536)},
schema="public",
target_dim=1536,
)
def test_assert_pgvector_dims_match_raises_on_dim_mismatch() -> None:
with pytest.raises(StartupValidationError, match="dim .* does not match"):
_assert_pgvector_dims_match(
{"documents": _typmod(1536), "message_embeddings": _typmod(768)},
schema="public",
target_dim=1536,
)
def test_assert_pgvector_dims_match_lists_all_missing_columns() -> None:
with pytest.raises(StartupValidationError) as excinfo:
_assert_pgvector_dims_match(
{"documents": _typmod(1536)},
schema="public",
target_dim=1536,
)
msg = str(excinfo.value)
assert "message_embeddings" in msg
assert "alembic upgrade head" in msg
def test_assert_pgvector_dims_match_raises_on_unbounded_typmod() -> None:
with pytest.raises(StartupValidationError, match="unbounded typmod"):
_assert_pgvector_dims_match(
{"documents": -1, "message_embeddings": _typmod(1536)},
schema="public",
target_dim=1536,
)
def test_assert_pgvector_dims_match_respects_non_public_schema() -> None:
with pytest.raises(StartupValidationError, match="my_schema.documents"):
_assert_pgvector_dims_match(
{},
schema="my_schema",
target_dim=1536,
)
# ---------------------------------------------------------------------------
# Fail-closed retry behavior
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_validator_fails_closed_when_introspection_keeps_failing(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""After the retry budget exhausts, the validator crashes — uncertainty
is not a green light to serve traffic."""
call_count = 0
async def always_raise(_engine: AsyncEngine, _schema: str) -> dict[str, int]:
nonlocal call_count
call_count += 1
raise OperationalError("SELECT 1", {}, Exception("DB unreachable"))
monkeypatch.setattr(
"src.startup.embedding_validator._introspect_pgvector_dims_once",
always_raise,
)
# Make backoff effectively instant for the test.
monkeypatch.setattr("src.startup.embedding_validator._RETRY_BACKOFF_SECONDS", 0.0)
with pytest.raises(StartupValidationError, match="could not validate"):
await validate_embedding_schema(engine=AsyncMock())
assert call_count == 3, "should exhaust the retry budget before failing"
# ---------------------------------------------------------------------------
# Integration: real test DB
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_validator_passes_against_test_database(
db_engine: AsyncEngine,
) -> None:
"""The test DB is provisioned at the default dim (1536); the validator
should accept it without raising."""
await validate_embedding_schema(db_engine)
@pytest.mark.asyncio
async def test_validator_raises_when_schema_dim_diverges_from_settings(
db_engine: AsyncEngine,
) -> None:
"""ALTER one of the embedding columns to a non-1536 dim and confirm the
validator raises with an actionable message."""
async with db_engine.begin() as conn:
await conn.execute(
text(
"ALTER TABLE documents"
+ " ALTER COLUMN embedding TYPE vector(768) USING NULL"
)
)
try:
with pytest.raises(StartupValidationError, match="dim .* does not match"):
await validate_embedding_schema(db_engine)
finally:
async with db_engine.begin() as conn:
await conn.execute(
text(
"ALTER TABLE documents"
+ " ALTER COLUMN embedding TYPE vector(1536) USING NULL"
)
)
# ---------------------------------------------------------------------------
# VECTOR_STORE_DIMENSIONS deprecation + dim-vs-MIGRATED guard removal
# ---------------------------------------------------------------------------
def test_vector_store_dimensions_explicit_set_warns(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Setting VECTOR_STORE_DIMENSIONS explicitly should trigger a deprecation
warning. EMBEDDING_VECTOR_DIMENSIONS remains authoritative."""
monkeypatch.setenv("PYTHON_DOTENV_DISABLED", "1")
monkeypatch.setenv("VECTOR_STORE_DIMENSIONS", "1536")
with warnings.catch_warnings(record=True) as captured:
warnings.simplefilter("always")
from src.config import AppSettings
AppSettings()
messages = [
str(w.message) for w in captured if issubclass(w.category, DeprecationWarning)
]
assert any(
"VECTOR_STORE_DIMENSIONS is deprecated" in m for m in messages
), f"expected deprecation warning, got {messages!r}"
def test_non_1536_pgvector_without_migrated_no_longer_raises_at_config_time() -> None:
"""Phase 2 removed the dim-vs-MIGRATED guard. Constructing AppSettings
with non-1536 + default pgvector + MIGRATED=false should now succeed
(the runtime schema validator at startup is the new safety net)."""
env = {
**os.environ,
"PYTHON_DOTENV_DISABLED": "1",
"EMBEDDING_VECTOR_DIMENSIONS": "768",
}
# Use a subprocess so the global settings singleton in this test
# process is not perturbed and is re-evaluated freshly in the child.
snippet = (
"from src.config import AppSettings\n"
"s = AppSettings()\n"
"print(s.EMBEDDING.VECTOR_DIMENSIONS, s.VECTOR_STORE.TYPE, s.VECTOR_STORE.MIGRATED)\n"
)
result = subprocess.run(
[sys.executable, "-c", snippet],
env=env,
cwd=str(_PROJECT_ROOT),
check=True,
capture_output=True,
text=True,
)
last_line = result.stdout.strip().splitlines()[-1]
assert last_line == "768 pgvector False"

View File

@ -54,16 +54,5 @@ def test_models_uses_default_1536_when_no_env_override() -> None:
def test_models_honors_explicit_embedding_vector_dimensions() -> None:
# Phase 1 still respects the dim-vs-MIGRATED guard at src/config.py:1278.
# Set VECTOR_STORE_TYPE=lancedb + VECTOR_STORE_MIGRATED=true to satisfy it.
# lancedb is chosen over turbopuffer because turbopuffer requires an
# additional VECTOR_STORE_TURBOPUFFER_API_KEY env var. Phase 2 deletes
# the guard and these escape-hatch envs become unnecessary.
dims = _run_in_fresh_interpreter(
{
"EMBEDDING_VECTOR_DIMENSIONS": "768",
"VECTOR_STORE_TYPE": "lancedb",
"VECTOR_STORE_MIGRATED": "true",
}
)
dims = _run_in_fresh_interpreter({"EMBEDDING_VECTOR_DIMENSIONS": "768"})
assert dims == {"message_embedding_dim": 768, "document_dim": 768}